Skip to content

Commit 57c4f3e

Browse files
refactor(table): thread dispatch concurrency via invocation instead of persisting it
1 parent e5d2bd5 commit 57c4f3e

7 files changed

Lines changed: 25 additions & 17400 deletions

File tree

apps/sim/background/table-run-dispatcher.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ const logger = createLogger('TableRunDispatcherTask')
77

88
export interface TableRunDispatcherPayload {
99
dispatchId: string
10+
/** Invoker's plan-resolved window size. Absent on payloads from before the
11+
* field existed → dispatcher falls back to the legacy cap. */
12+
concurrency?: number
1013
}
1114

1215
/**
@@ -26,9 +29,9 @@ export const tableRunDispatcherTask = task({
2629
concurrencyLimit: 8,
2730
},
2831
run: async (payload: TableRunDispatcherPayload) => {
29-
const { dispatchId } = payload
32+
const { dispatchId, concurrency } = payload
3033
try {
31-
await runDispatcherToCompletion(dispatchId)
34+
await runDispatcherToCompletion(dispatchId, concurrency)
3235
} catch (err) {
3336
logger.error(`[${dispatchId}] dispatcher loop failed`, { error: toError(err).message })
3437
throw err

apps/sim/lib/table/dispatcher.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,6 @@ export interface DispatchRow {
8080
limit: DispatchLimit | null
8181
/** Units of `limit.type` already consumed (eligible rows dispatched). */
8282
processedCount: number
83-
/** Rows executed in parallel per window, resolved from the payer's plan at
84-
* creation. Null on pre-column rows → legacy cap of 20. */
85-
concurrency: number | null
8683
isManualRun: boolean
8784
/** User who triggered the run (for usage attribution); null for auto-fire. */
8885
triggeredByUserId: string | null
@@ -188,8 +185,6 @@ export async function insertDispatch(input: {
188185
mode: DispatchMode
189186
scope: DispatchScope
190187
limit?: DispatchLimit | null
191-
/** Per-window parallelism from the payer's plan (see `resolveTableDispatchConcurrency`). */
192-
concurrency: number
193188
isManualRun: boolean
194189
triggeredByUserId?: string | null
195190
}): Promise<string> {
@@ -202,7 +197,6 @@ export async function insertDispatch(input: {
202197
mode: input.mode,
203198
scope: input.scope,
204199
limit: input.limit ?? null,
205-
concurrency: input.concurrency,
206200
status: 'pending',
207201
// -1 = "haven't started." First window's filter `position > -1` matches
208202
// position 0; subsequent iterations advance to `lastPosition` which then
@@ -292,7 +286,6 @@ export async function listActiveDispatches(tableId: string): Promise<DispatchRow
292286
cursor: row.cursor,
293287
limit: (row.limit as DispatchLimit | null) ?? null,
294288
processedCount: row.processedCount,
295-
concurrency: row.concurrency,
296289
isManualRun: row.isManualRun,
297290
triggeredByUserId: row.triggeredByUserId,
298291
requestedAt: row.requestedAt,
@@ -317,7 +310,6 @@ export async function readDispatch(dispatchId: string): Promise<DispatchRow | nu
317310
cursor: row.cursor,
318311
limit: (row.limit as DispatchLimit | null) ?? null,
319312
processedCount: row.processedCount,
320-
concurrency: row.concurrency,
321313
isManualRun: row.isManualRun,
322314
triggeredByUserId: row.triggeredByUserId,
323315
requestedAt: row.requestedAt,
@@ -326,14 +318,23 @@ export async function readDispatch(dispatchId: string): Promise<DispatchRow | nu
326318

327319
/** Drive `dispatcherStep` to completion. Shared between the trigger.dev task
328320
* wrapper (`tableRunDispatcherTask`) and the in-process inline path so both
329-
* runtimes use identical loop semantics + error logging. */
330-
export async function runDispatcherToCompletion(dispatchId: string): Promise<void> {
331-
while ((await dispatcherStep(dispatchId)) === 'continue') {}
321+
* runtimes use identical loop semantics + error logging. `concurrency` is the
322+
* invoker's plan-resolved window size (see `resolveTableDispatchConcurrency`),
323+
* threaded via the task payload; absent on payloads from before the field
324+
* existed → legacy cap. */
325+
export async function runDispatcherToCompletion(
326+
dispatchId: string,
327+
concurrency?: number
328+
): Promise<void> {
329+
while ((await dispatcherStep(dispatchId, concurrency)) === 'continue') {}
332330
}
333331

334332
/** Run one window of the dispatcher state machine. Caller re-invokes (via the
335333
* trigger.dev task wrapper) until the returned status is `'done'`. */
336-
export async function dispatcherStep(dispatchId: string): Promise<DispatcherStepResult> {
334+
export async function dispatcherStep(
335+
dispatchId: string,
336+
concurrency?: number
337+
): Promise<DispatcherStepResult> {
337338
const dispatch = await readDispatch(dispatchId)
338339
if (!dispatch) {
339340
logger.warn(`[${dispatchId}] dispatch row missing — aborting`)
@@ -383,10 +384,10 @@ export async function dispatcherStep(dispatchId: string): Promise<DispatcherStep
383384
})
384385
}
385386

386-
// Window size = the dispatch's plan-resolved parallelism, so one window
387+
// Window size = the invoker's plan-resolved parallelism, so one window
387388
// saturates the cell pool before the next is loaded — yields a row-major
388-
// scan-line crawl. Pre-column dispatches fall back to the legacy cap.
389-
const windowSize = dispatch.concurrency ?? TABLE_CONCURRENCY_LIMIT
389+
// scan-line crawl. Payloads without the field fall back to the legacy cap.
390+
const windowSize = concurrency ?? TABLE_CONCURRENCY_LIMIT
390391

391392
const filters = [
392393
eq(userTableRows.tableId, dispatch.tableId),
@@ -800,7 +801,6 @@ export async function markActiveDispatchesCancelled(
800801
cursor: row.cursor,
801802
limit: (row.limit as DispatchLimit | null) ?? null,
802803
processedCount: row.processedCount,
803-
concurrency: row.concurrency,
804804
isManualRun: row.isManualRun,
805805
triggeredByUserId: row.triggeredByUserId,
806806
requestedAt: row.requestedAt,

apps/sim/lib/table/workflow-columns.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -749,9 +749,8 @@ export async function runWorkflowColumn(opts: {
749749
runDispatcherToCompletion,
750750
} = await import('./dispatcher')
751751

752-
// Per-window parallelism follows the payer's plan, resolved once here and
753-
// persisted on the dispatch row so the dispatcher loop reads a stable value
754-
// across checkpointed waits and task retries.
752+
// Per-window parallelism follows the invoker's plan, resolved once here and
753+
// threaded through the dispatcher invocation (task payload / loop arg).
755754
const concurrency = await resolveTableDispatchConcurrency({
756755
workspaceId,
757756
actorUserId: triggeredByUserId,
@@ -780,7 +779,6 @@ export async function runWorkflowColumn(opts: {
780779
: {}),
781780
},
782781
limit,
783-
concurrency,
784782
isManualRun,
785783
triggeredByUserId,
786784
})
@@ -884,14 +882,14 @@ export async function runWorkflowColumn(opts: {
884882
])
885883
await tasks.trigger<typeof tableRunDispatcherTask>(
886884
'table-run-dispatcher',
887-
{ dispatchId },
885+
{ dispatchId, concurrency },
888886
{ concurrencyKey: dispatchId, region: await resolveTriggerRegion() }
889887
)
890888
} else {
891889
// Local / no-trigger.dev: drive the same loop in-process, fire-and-forget
892890
// so the HTTP request returns instantly (mirrors the trigger.dev path's
893891
// async fan-out).
894-
void runDispatcherToCompletion(dispatchId).catch((err) =>
892+
void runDispatcherToCompletion(dispatchId, concurrency).catch((err) =>
895893
logger.error(`[${requestId}] dispatcher loop failed`, {
896894
dispatchId,
897895
error: toError(err).message,

packages/db/migrations/0263_brief_warhawk.sql

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)