perf(cleaner-history): parallelize batch fetching in searchBatches

Replace sequential for-loop with Promise.all so that matched batches
are fetched concurrently instead of one-by-one, reducing total query
latency from O(n) serial round-trips to a single parallel batch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-04-17 12:42:07 +08:00
parent 622543fff4
commit f49f99fc0c

View File

@@ -1068,63 +1068,66 @@ export class CleanerOperationHistoryDAO {
return { batches: [], totalMatches: 0 } return { batches: [], totalMatches: 0 }
} }
// Fetch full nested data for each matched batch // Fetch full nested data for each matched batch (parallel)
const batches: CleanerSearchBatchResult[] = [] const batchResults = await Promise.all(
limitedBatchIds.map(async (batchId) => {
try {
const details = await this.getBatchDetails(batchId)
for (const batchId of limitedBatchIds) { if (details.executions.length === 0) {
try { return null
const details = await this.getBatchDetails(batchId) }
if (details.executions.length === 0) { // Derive batch stats from execution records
continue const latestExec = details.executions.reduce((a, b) =>
} a.attemptNumber > b.attemptNumber ? a : b
)
// Derive batch stats from execution records const batch: CleanerBatchStats = {
const latestExec = details.executions.reduce((a, b) => batchId,
a.attemptNumber > b.attemptNumber ? a : b userId: latestExec.userId,
) username: latestExec.username,
operationTime: latestExec.operationTime.toISOString(),
status: latestExec.status,
totalAttempts: details.executions.length,
totalOrders: latestExec.totalOrders,
ordersProcessed: latestExec.ordersProcessed,
totalMaterialsDeleted: latestExec.totalMaterialsDeleted,
totalMaterialsFailed: latestExec.totalMaterialsFailed,
successCount: details.orders.filter((o) => o.status === 'success').length,
failedCount: details.orders.filter((o) => o.status === 'failed').length,
isDryRun: latestExec.isDryRun
}
const batch: CleanerBatchStats = { // Fetch materials for each order
batchId, const ordersWithMaterials = await Promise.all(
userId: latestExec.userId, details.orders.map(async (order) => {
username: latestExec.username, const materials = await this.getMaterialDetails(
operationTime: latestExec.operationTime.toISOString(), batchId,
status: latestExec.status, order.attemptNumber,
totalAttempts: details.executions.length, order.orderNumber
totalOrders: latestExec.totalOrders, )
ordersProcessed: latestExec.ordersProcessed, return { order, materials }
totalMaterialsDeleted: latestExec.totalMaterialsDeleted, })
totalMaterialsFailed: latestExec.totalMaterialsFailed, )
successCount: details.orders.filter((o) => o.status === 'success').length,
failedCount: details.orders.filter((o) => o.status === 'failed').length,
isDryRun: latestExec.isDryRun
}
// Fetch materials for each order return {
const ordersWithMaterials = await Promise.all( batch,
details.orders.map(async (order) => { executions: details.executions,
const materials = await this.getMaterialDetails( orders: ordersWithMaterials
batchId, } satisfies CleanerSearchBatchResult
order.attemptNumber, } catch (error) {
order.orderNumber log.error('Error fetching batch data for search result', {
) operationType: 'SELECT',
return { order, materials } batchId,
error: error instanceof Error ? error.message : String(error)
}) })
) return null
}
})
)
batches.push({ const batches = batchResults.filter((r): r is CleanerSearchBatchResult => r !== null)
batch,
executions: details.executions,
orders: ordersWithMaterials
})
} catch (error) {
log.error('Error fetching batch data for search result', {
operationType: 'SELECT',
batchId,
error: error instanceof Error ? error.message : String(error)
})
}
}
log.info('Search batches completed', { log.info('Search batches completed', {
operationType: 'SELECT', operationType: 'SELECT',