Idempotency¶
Signature retry cache is currently experimental. The API may change in future releases based on feedback.
Idempotency means that running the same operation multiple times produces the same result as running it once. In distributed systems, tasks can fail and retry at any point โ network errors, crashes, timeouts. If retrying a task causes side effects to repeat, you end up with duplicate work and inconsistent state.
The Problem¶
When a durable task retries, MageFlow re-executes the task function from the top. Every call to asign(), achain(), or aswarm() creates a new signature in Redis. On a retry, this means:
- Duplicate signatures โ a second set of signatures is created alongside the originals
- Orphaned state โ the original signatures from the failed attempt remain in Redis with no task tracking them
- Double execution โ callbacks and downstream tasks may trigger twice, once for each set of signatures
The Solution¶
MageFlow solves this with a signature retry cache. On the first execution, every signature created inside a durable task is recorded. On subsequent retries, the cached signatures are returned instead of creating new ones.
How It Works¶
First run (attempt 1):
sign("task-a") โ creates TaskSignature, caches it
chain([a, b]) โ creates ChainTaskSignature, caches it
Retry (attempt 2+):
sign("task-a") โ returns cached TaskSignature
chain([a, b]) โ returns cached ChainTaskSignature
The cache is keyed by workflow ID and stored in Redis with a 24-hour TTL. It is automatically cleaned up when the task finishes โ either on success or on final failure (no more retries).
Automatic for Durable Tasks¶
Idempotency is enabled automatically for all durable tasks. No configuration needed.
@hatchet.durable_task()
async def my_task(msg):
# These calls are automatically idempotent on retries
sig_a = await mageflow.asign("task-a", data="hello")
sig_b = await mageflow.asign("task-b", data="world")
workflow = await mageflow.achain([sig_a, sig_b])
return await workflow.aio_run(msg)
Regular (non-durable) tasks do not use the retry cache.
Signature Order Matters¶
The cache replays signatures in the same order they were created. If your task creates signatures conditionally or in a different order on retry, the cache will return the wrong signature.
Keep signature creation deterministic
Always create signatures in the same order across retries. Avoid branching logic that changes which signatures are created or their order.
# Good โ deterministic order
@hatchet.durable_task()
async def good_task(msg):
a = await mageflow.asign("task-a")
b = await mageflow.asign("task-b")
return await mageflow.achain([a, b])
# Bad โ order depends on external state
@hatchet.durable_task()
async def bad_task(msg):
if await some_external_check():
a = await mageflow.asign("task-a")
b = await mageflow.asign("task-b")