Cap jobs running at once
Say exports are heavy, and you want each customer workspace to run at most 3 at once. The 4th should wait or be refused, and a crashed export mustn’t keep its slot forever.
1. Create the limit
Section titled “1. Create the limit”import { createClient } from "@limitry/sdk";
const limitry = createClient();
await limitry.limits.create({ name: "Exports at once", subjectKind: "workspace", action: "export", amount: 3, window: "concurrent",});from limitry import Limitry
limitry = Limitry()
limitry.limits.create( name="Exports at once", subject_kind="workspace", action="export", amount=3, window="concurrent",)limitry limits create --name "Exports at once" --subject-kind workspace \ --action export --amount 3 --window concurrentcurl -X POST https://api.limitry.com/v1/limits \ -H "Authorization: Bearer $LIMITRY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Exports at once","subjectKind":"workspace","action":"export", "amount":3,"window":"concurrent"}'2. Take a slot for the job
Section titled “2. Take a slot for the job”hold takes a slot before the job starts and gives it back when the job
ends, whether it succeeds or throws:
import { LimitExceeded } from "@limitry/sdk";
try { await limitry.hold( { subject: { kind: "workspace", id: workspaceId }, action: "export", ttlSeconds: 600, }, async (held) => { for (const chunk of chunks) { await exportChunk(chunk); await held.extend(); // still running: keep the slot for 300 s from now } }, );} catch (err) { if (err instanceof LimitExceeded) { // All 3 slots are taken. Try again in err.retryAfterSeconds. return requeue(job, err.retryAfterSeconds); } throw err;}from limitry import LimitExceeded
try: with limitry.hold( subject={"kind": "workspace", "id": workspace_id}, action="export", ttl_seconds=600, ) as held: for chunk in chunks: export_chunk(chunk) held.extend() # still running: keep the slot for 300 s from nowexcept LimitExceeded as err: # All 3 slots are taken. Try again in err.retry_after_seconds. requeue(job, err.retry_after_seconds)3. If the worker dies
Section titled “3. If the worker dies”A slot is a reservation, and it lasts ttlSeconds. A job that’s still
running extends it as it goes. A worker that crashes stops extending,
and the slot frees itself within ttlSeconds. Pick a ttlSeconds
comfortably longer than the gap between your extend calls. An
extend() with no argument keeps the slot for 300 seconds from the
call; pass a number of seconds to keep it longer.
Combine with a daily limit
Section titled “Combine with a daily limit”A concurrency limit doesn’t count use. To also cap exports per day, add
a second limit on the same action with the window day. The hold
above then has to fit both.
Related: Rolling windows and concurrency, Reservations.