-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
fix: unflatten crash on large numeric keys and fair-queue age ranking #4496
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@trigger.dev/redis-worker": patch | ||
| --- | ||
|
|
||
| Fix fair queue tenant selection so that, when a maximum tenant count is set, the tenants that have been waiting the longest are picked first instead of being ranked by their raw timestamp. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@trigger.dev/core": patch | ||
| --- | ||
|
|
||
| Fix a crash when unflattening attributes that hold an object whose keys are all large numbers, such as millisecond timestamps. Those values now come back as an object instead of throwing an "Invalid array length" error. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -150,7 +150,7 @@ export class WeightedScheduler extends BaseScheduler { | |
|
|
||
| // Apply maximum tenant count if configured | ||
| if (this.maximumTenantCount > 0) { | ||
| rawQueues = this.#selectTopTenantQueues(rawQueues); | ||
| rawQueues = this.#selectTopTenantQueues(rawQueues, now); | ||
| } | ||
|
|
||
| // Build tenant data | ||
|
|
@@ -230,7 +230,7 @@ export class WeightedScheduler extends BaseScheduler { | |
| return queues; | ||
| } | ||
|
|
||
| #selectTopTenantQueues(queues: QueueWithScore[]): QueueWithScore[] { | ||
| #selectTopTenantQueues(queues: QueueWithScore[], now: number): QueueWithScore[] { | ||
| // Group by tenant and calculate average age | ||
| const queuesByTenant = new Map<string, QueueWithScore[]>(); | ||
| for (const queue of queues) { | ||
|
|
@@ -239,9 +239,11 @@ export class WeightedScheduler extends BaseScheduler { | |
| queuesByTenant.set(queue.tenantId, tenantQueues); | ||
| } | ||
|
|
||
| // Calculate average age per tenant | ||
| // Calculate average age per tenant. A queue's score is its oldest message | ||
| // timestamp, so age is now - score. Older queues have a higher age and | ||
| // should get more weight when we pick the top tenants. | ||
| const tenantAges = Array.from(queuesByTenant.entries()).map(([tenantId, tQueues]) => { | ||
| const avgAge = tQueues.reduce((sum, q) => sum + q.score, 0) / tQueues.length; | ||
| const avgAge = tQueues.reduce((sum, q) => sum + (now - q.score), 0) / tQueues.length; | ||
|
Comment on lines
+242
to
+246
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 Age-based weighting can drive freshly-enqueued tenants to weight 0 With Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| return { tenantId, avgAge }; | ||
| }); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Objects keyed by second-precision timestamps still expand into billion-slot lists
The cutoff for treating numeric keys as list positions is set at roughly 4.29 billion (
maxIndex < MAX_ARRAY_INDEXatpackages/core/src/v3/utils/flattenAttributes.ts:356), so data keyed by second-precision timestamps (about 1.7 billion) is still turned into a list with billions of empty slots, so displaying or saving that data can exhaust memory.Impact: A task payload or output that uses second-based timestamps (or any other large-but-under-4-billion number) as its keys can make the server run out of memory when that data is written or rendered.
Why the 2^32 threshold does not cover the realistic key range
unflattenAttributesonly avoids the array rebuild when the largest numeric key is >= 2^32-1. A unix timestamp in seconds (e.g.1700000000) is below that, soArray(1700000001)is still allocated and every key is written into it. Downstream this value is serialized, e.g.JSON.stringify(unflattenAttributes(...))inapps/webapp/app/v3/eventRepository/clickhouseEventRepository.server.ts:697, which would materialize ~1.7 billionnull,entries and blow up memory. A density/size-based heuristic (e.g. only rebuild the array whenmaxIndex + 1is within a small multiple of the number of keys, or below a modest constant like 100k) would cover both the millisecond and second timestamp cases.Prompt for agents
Was this helpful? React with 👍 or 👎 to provide feedback.