How to store cross-task durable state¶
This guide shows you how to keep values that outlive any single task (a
poller's cursor, a high-water mark, a "have I already seen this?" record)
in dura's durable key-value store.
Read and write a single key¶
State is scoped by a namespace you choose, plus a key within it:
last_seen = engine.get_state(namespace="poller:orders", key="cursor", default=0)
# ... fetch orders newer than last_seen ...
engine.set_state(namespace="poller:orders", key="cursor", value=new_cursor)
set_state is an upsert: the latest write for a (namespace, key) wins.
get_state returns default (defaulting to None) if the key has never
been written.
Update a value atomically¶
If two workers might update the same key concurrently (incrementing a
counter, appending to a dedup set), don't do a get_state followed by a
set_state: there's a race between them. Use update_state instead. It
runs your function inside the write transaction.
def bump(current):
return current + 1
engine.update_state(namespace="stats", key="processed_count", fn=bump, default=0)
Keep the function pure and fast, with no I/O, since it runs while the database write lock is held.
Write many keys at once¶
Loop-calling set_state for a bulk import commits once per key. Use
set_state_many to write a whole batch in a single transaction:
List or check a namespace¶
engine.list_state("poller:orders") # {"cursor": 42, ...}
engine.has_state("poller:orders") # True, without loading any values
Delete a key¶
Remember: state is not cleaned up¶
Unlike checkpoints, durable state is never touched by cleanup(). It's
meant to survive the tasks that wrote it. If a value really is scoped to
one task's lifetime, use a checkpoint instead.