Skip to content

getQueueName's $$cache on compiledSharedOptions grows one entry per distinct queue id with no eviction, for the life of the process #616

Description

@tsushanth

getQueueName's $$cache on compiledSharedOptions grows one entry per distinct queue id with no eviction, for the life of the process

Repo: graphile/worker
Location: src/helpers.ts:getQueueName — cache[queueId] = name (the $$cache Record on compiledSharedOptions), reached via worker.ts doNext → makeJobHelpers → helpers.getQueueName
Severity: medium · Confidence: 0.75
Type: unbounded-growth

Description

src/helpers.ts getQueueName() lazily creates a Record under the $$cache symbol on compiledSharedOptions and stores one entry per distinct queueId ever resolved (cache[queueId] = name after the batched getQueueNames lookup in src/sql/getQueueNames.ts). compiledSharedOptions is the long-lived per-runner context shared by every worker (created once in lib.ts and threaded through runner.ts → main.ts → worker.ts → makeJobHelpers), so the cache lives as long as the runner. There is no eviction, no size cap, and no TTL — entries are only ever overwritten, never removed (the failure path sets undefined, but successful lookups persist forever). In workloads that use ephemeral serialized queues (a common graphile-worker pattern: one queue per user/order/entity to serialize their jobs) where tasks call helpers.getQueueName(), a long-running worker accumulates unbounded string entries; a numeric-keyed dictionary-mode object also degrades insert performance as it grows.

Benchmark

Retained heap grows linearly with distinct queue ids seen and never shrinks (validated: 2.06MB at 10k ids → 20.7MB at 100k → 103.8MB at 500k, cleanly O(N)); insert time also grows and turns super-linear at large N (182x at 50x ids) as the numeric-keyed object transitions to dictionary mode. An LRU or size-capped cache would plateau both curves.

Observed complexity: O(N) heap, super-linear (between O(N) and O(N²)) insert time

N=10k: 1.110ms, 2.06MB; N=50k: 3.984ms (3.59x), 10.32MB; N=100k: 16.670ms (15.02x), 20.74MB; N=500k: 263.132ms (237.14x), 103.75MB. Heap tracks O(N) cleanly (ratios ~1x/5x/10x/50x); time is super-linear, far exceeding O(N) prediction (50x) and falling between O(N) and O(N²).

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