Akka ReliableDelivery retries: where should deduplication state live to keep writes unique?
0 reputation · 14 Feb 2020, 19:57 UTC
0 reputation · 14 Feb 2020, 19:57 UTC
We are designing a typed Akka service where a producer actor sends commands to a consumer that performs a database write. Since Akka messaging is at-most-once by default, any retry mechanism (supervision restart, explicit resend, or ReliableDelivery redelivery) can deliver the same logical command more than once, so the write must not be duplicated.
The plan is to use akka.actor.typed.delivery.ReliableDelivery and attach a unique message ID to each command. What remains undecided is where the deduplication record should live and how long it must be retained. Options under consideration: keeping processed IDs in the consumer's durable state via Akka Persistence, relying on a unique constraint in the database, or combining the ReliableDelivery sequence number with a processed-ID log.
Constraints: the consumer may restart or be rebalanced, so purely in-memory tracking is not sufficient. Assume a recent Akka 2.7/2.8 typed setup.
Is a database unique constraint on the message ID alone enough to guarantee no duplicate writes across retries and restarts, or should the consumer also track seqNr in persistent state? How long must processed message IDs be retained before they can be safely discarded? Does ReliableDelivery's sequence tracking survive a producer restart without additional durable state?
A database unique constraint on the message ID alone guarantees that no duplicate write can succeed, provided the write and the constraint check happen in the same transaction.
ReliableDelivery provides at‑least‑once delivery; duplicates are expected.seqNr) live in memory and are lost on a producer restart unless you enable the durable‑queue variant (EventSourcedProducerQueue).seqNr (via the controller’s confirm flow) only helps with in‑flight deduplication while the producer stays alive; it does not survive producer restarts or rebalancing.UNIQUE constraint on that identifier.ReliableDelivery (via ConsumerController.confirm), and do not retry the write.ReliableDelivery flow control enabled so the consumer can back‑pressure and avoid unnecessary redelivery while the producer is alive.Because the DB row persists until you explicitly delete it, the identifier is retained for as long as the row exists. You may safely purge old IDs only after you are certain the producer will never resend that command – for example, after a configured maximum number of redelivery attempts multiplied by the retry interval, or after a business‑defined “safe window”.
Does your write operation execute the uniqueness check and the actual insert/update in a single database transaction? If not, a duplicate could slip through between the check and the write, and you would need to move the constraint into the transaction or use a retry‑on‑conflict loop.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.