Skip to content

Durable changes and acknowledgements

Leani delivery is deliberately at-least-once. Exactly-once application effects come from an application transaction, not from pretending an HTTP response and a database commit are atomic.

Delivery sequence. History commits coverage through block 26,088,923. Live block #26,088,924 arrives, and its parent hash 0xee28…2bfb30 equals the hash of block 26,088,923, so the handoff is exact. Its header chain leads to an attested head and the core commits the branch. Leani publishes change seq 283 to your database, which applies it idempotently, persists the cursor and sequence, and commits in one transaction before acknowledging. The acknowledgement allows pruning up to seq 283.

Real blocks, hashes, and sequences from a recorded leani 0.1.0-rc.1 Mainnet session · interval and timing illustrative#26,088,924 · seq 283
Leani change ─> begin destination transaction
├─ apply idempotently
├─ persist opaque cursor + sequence
└─ commit
─> acknowledge Leani

If the acknowledgement response is lost, the same change may arrive again. The destination’s sequence key makes the mutation idempotent, and its committed cursor proves what it already applied. Leani’s acknowledgement separately tells the node what delivery history a required consumer still fences from pruning.

Leases and reset requirements

A durable consumer has a stable ID, a role, an acknowledgement, and a finite lease. One created through the consumer API also has a credential, which its delivery calls carry, and a backfill subscription’s consumer can have one; one declared in the node configuration has none. An acknowledgement cannot pass what the node delivered to that consumer. Active SDK iterators renew their lease. If a best-effort consumer falls behind retained history, the stream emits reset_required with coverage hints rather than skipping invisibly.

Query then follow

A consistent snapshot is held across pagination and returns the boundary cursor needed to continue with changes after the snapshot. Release the snapshot when finished. This closes the race between “last row queried” and “first live event received” without freezing ingestion.

See Consume changes durably or the complete PostgreSQL adapter.