@ultimat3/jobs 31.4.2 → 31.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -7
- package/src/limits.ts +21 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/jobs",
|
|
3
|
-
"version": "31.
|
|
3
|
+
"version": "31.6.0",
|
|
4
4
|
"description": "Durable background work: steps, transactional outbox, cron tasks, one driver interface",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ultimate",
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
"test": "bun test"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@ultimat3/core": "31.
|
|
49
|
-
"@ultimat3/db": "31.
|
|
50
|
-
"@ultimat3/entity": "31.
|
|
51
|
-
"@ultimat3/http": "31.
|
|
52
|
-
"@ultimat3/schema": "31.
|
|
53
|
-
"@ultimat3/time": "31.
|
|
48
|
+
"@ultimat3/core": "31.6.0",
|
|
49
|
+
"@ultimat3/db": "31.6.0",
|
|
50
|
+
"@ultimat3/entity": "31.6.0",
|
|
51
|
+
"@ultimat3/http": "31.6.0",
|
|
52
|
+
"@ultimat3/schema": "31.6.0",
|
|
53
|
+
"@ultimat3/time": "31.6.0"
|
|
54
54
|
}
|
|
55
55
|
}
|
package/src/limits.ts
CHANGED
|
@@ -136,6 +136,17 @@ const compact = (window: StartWindow): void => {
|
|
|
136
136
|
window.head = 0;
|
|
137
137
|
};
|
|
138
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Move the head past every stamp at or before `cutoff`. Spent stamps leave from the HEAD, never by
|
|
141
|
+
* copying the window: a filter per acquire made a window of n stamps O(n) a claim — 20k acquires
|
|
142
|
+
* were 3.9 s of event loop.
|
|
143
|
+
*/
|
|
144
|
+
const expire = (window: StartWindow, cutoff: number): void => {
|
|
145
|
+
while (window.head < window.stamps.length && (window.stamps[window.head] ?? 0) <= cutoff) {
|
|
146
|
+
window.head += 1;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
139
150
|
/** Tenant key resolution. Structural on purpose: `jobs` must not import the auth types. */
|
|
140
151
|
export function tenantKeyFrom(actor: { readonly orgId?: string } | undefined): string {
|
|
141
152
|
return actor?.orgId ?? NO_TENANT;
|
|
@@ -221,13 +232,19 @@ export function concurrencyLimiter(
|
|
|
221
232
|
* does the cap evict live state, and then the LEAST throttled tenants go first — discarding a
|
|
222
233
|
* full window is what would hand a tenant a free rate reset, which is the order
|
|
223
234
|
* `@ultimat3/http`'s `memoryRateLimitStore` evicts in and for the same reason.
|
|
235
|
+
*
|
|
236
|
+
* The drop, stated: past `maxTenants` tenants with a LIVE window, an evicted tenant regains the
|
|
237
|
+
* starts its window still held — at most `ratePerTenant.limit`, and by this order the fewest any
|
|
238
|
+
* tenant could. That is the price of a bounded map; the fleet gate is `JobDriver.leases`.
|
|
224
239
|
*/
|
|
225
240
|
const sweep = (at: number): void => {
|
|
226
241
|
lastSweepMs = at;
|
|
242
|
+
// Expired here, not only when the tenant's own key is next claimed: the cap below ranks by
|
|
243
|
+
// `live`, and an unread expired prefix made a mostly-spent window look like the busiest one.
|
|
227
244
|
for (const [tenant, window] of starts) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
245
|
+
expire(window, at - rateWindowMs);
|
|
246
|
+
if (live(window) === 0) starts.delete(tenant);
|
|
247
|
+
else compact(window);
|
|
231
248
|
}
|
|
232
249
|
for (const [key, refusal] of refusals) {
|
|
233
250
|
if (refusal.atMs <= at - REFUSAL_TTL_MS) refusals.delete(key);
|
|
@@ -263,14 +280,7 @@ export function concurrencyLimiter(
|
|
|
263
280
|
if (rate === undefined) return false;
|
|
264
281
|
const window = starts.get(tenant);
|
|
265
282
|
if (window === undefined) return rate.limit <= 0;
|
|
266
|
-
|
|
267
|
-
// window of n stamps O(n) a claim — 20k acquires were 3.9 s of event loop.
|
|
268
|
-
while (
|
|
269
|
-
window.head < window.stamps.length &&
|
|
270
|
-
(window.stamps[window.head] ?? 0) <= at - rate.windowMs
|
|
271
|
-
) {
|
|
272
|
-
window.head += 1;
|
|
273
|
-
}
|
|
283
|
+
expire(window, at - rate.windowMs);
|
|
274
284
|
// An empty window is a tenant with no rate state at all — deleted rather than kept, which is
|
|
275
285
|
// what kept one array per org that ever ran a job.
|
|
276
286
|
if (live(window) === 0) {
|