Singleton
Prevent overlapping runs for the same key from duplicating work or overwriting results.
Keep two syncs for the same user from running at once. Singleton applies to the entire function run for that key; choose whether a new event skips its run or replaces the active run.
Singleton controls the whole function run. A concurrency limit of 1 controls executing steps, so it does not provide the same run-level rule.
singleton: { key: "event.data.user_id", mode: "skip" }- Event
- Skipped
- Step executing
- Sleeping (no slot)
- Completed
All events are for one user. Run 1 holds the singleton lock for its whole duration, including the sleep, so events 2 and 3 are skipped. Event 4 arrives after run 1 completes and starts a new run.
Configure a singleton function
This TypeScript v4 function keeps one active sync run per user. If another data-sync.start event arrives for that user while the run is active, mode: "skip" skips the new run and lets the current run continue. Replace the step body with your synchronization code.
import { Inngest } from "inngest";
const inngest = new Inngest({ id: "customer-sync" });
export const syncUser = inngest.createFunction(
{
id: "sync-user",
triggers: { event: "data-sync.start" },
singleton: {
key: "event.data.user_id",
mode: "skip",
},
},
async ({ event, step }) => {
await step.run("sync-user-data", async () => {
console.log(`syncing user ${event.data.user_id}`);
});
}
);
The required key is a Common Expression Language (CEL) expression string evaluated against each triggering event. Different key values can have separate active runs. In this example, user A and user B can each have one active sync run.
Choose what a new event does
| Mode | When another event arrives with the same key | Use it when |
|---|---|---|
skip | Inngest skips the new run and preserves the active one. | The work already in progress should finish. |
cancel | Inngest cancels the active run and starts a run for the new event. | The newest event should replace older work. |
Cancel mode: Set mode: "cancel" in the example to use the second behavior. Cancellation does not undo steps that already completed. A currently executing step.run finishes before cancellation takes effect. During a rapid burst of matching events, cancel mode can skip some new runs rather than cancel each previous run.
singleton: { key: "event.data.user_id", mode: "cancel" }- Event
- Step executing
- Sleeping (no slot)
- Completed
- Cancelled
Each new event cancels the active run and starts a new one. Run 1 is sleeping, so it's cancelled right away. Run 2 is executing a step, so the step finishes before the cancellation takes effect.
A run that fails and is retrying still occupies its singleton slot. In skip mode, new matching runs stay skipped while the original run retries.
Use it with other flow controls
- Step concurrency controls active step execution. Singleton already implies one active run per key. Treat an additional concurrency setting with care because it can change scheduling without replacing the run-level rule.
- Rate limiting limits run starts over a specified period. Singleton uses the duration of an active run.
- Throttling queues excess runs. Singleton skip mode discards overlapping runs; cancel mode replaces the active one.
- Debounce can be combined with singleton when you want to wait for a quiet period before applying the run-level rule.
- Batching cannot be combined with singleton. Function registration fails when both are set.