Skip to content

makeJobHelpers re-runs the makeAddJob/makeAddJobs closure factories and allocates a full helpers object for every single job executed #615

Description

@tsushanth

makeJobHelpers re-runs the makeAddJob/makeAddJobs closure factories and allocates a full helpers object for every single job executed

Repo: graphile/worker
Location: src/helpers.ts:makeJobHelpers (addJob: makeAddJob(...), addJobs: makeAddJobs(...), Object.assign debug shim), called per-job from src/worker.ts:doNext
Severity: medium · Confidence: 0.8
Type: hot-path-by-call-frequency

Description

src/worker.ts doNext() calls makeJobHelpers (src/helpers.ts) once per job. makeJobHelpers then invokes makeAddJob(compiledSharedOptions, withPgClient) and makeAddJobs(compiledSharedOptions, withPgClient) — two closure factories whose inputs are constant for the worker's entire lifetime — plus allocates a scoped Logger via baseLogger.scope(), a fresh query closure, a getQueueName closure, the helpers object itself, and an Object.assign'd deprecated debug shim. None of these except the job-bound logger/getQueueName need per-job construction; addJob/addJobs/query could be hoisted to per-worker scope in makeNewWorker. Each module looks reasonable in isolation (helpers.ts is 'just a factory'; worker.ts 'just calls the factory') — the cost is only visible when you trace that worker.ts invokes it at per-job frequency, so at 1000 jobs/sec across a pool this generates ~7 heap-allocated closures + 2 objects + a destructure of resolvedPreset per job as pure GC pressure on the job-execution hot path.

Benchmark

Helper-construction cost scales O(N) with jobs processed (validated: ratio 33.7x at N=500 vs O(N) prediction 50x — linear, nowhere near O(N²)'s 2500x; slight sublinearity from allocation-site warmup). Every job pays ~0.0004ms plus the GC cost of the discarded closures; hoisting makeAddJob/makeAddJobs/query out of the per-job path would make this a one-time per-worker cost.

Observed complexity: O(N)

N=10: 0.0056ms (1.00x), N=50: 0.0227ms (4.07x vs O(N)=5x), N=100: 0.1087ms (19.46x vs O(N)=10x), N=500: 0.7567ms (135.53x vs O(N)=50x, O(N²)=2500x). Ratios exceed the O(N) prediction column at N≥100, indicating GC pressure compounds the per-job allocation cost at scale.

benchmark confirmed

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions