Idempotent @Retry vs Transactional Outbox for duplicate‑write prevention in Quarkus
0 reputation · 09 Feb 2021, 02:45 UTC
0 reputation · 09 Feb 2021, 02:45 UTC
Goal: guarantee that a Quarkus service annotated with @Retry does not create duplicate database rows when a transient exception forces a retry of the emitting method.
Constraint: the service processes only a few dozen messages per minute, so added latency or operational complexity from an extra outbox table and its processor must be weighed against the risk of incorrectly assuming idempotency.
Uncertainty: teams must decide whether to rely on application‑level idempotency checks (e.g., unique‑key pre‑read) or to adopt the transactional outbox pattern, and it is unclear which option yields lower end‑to‑end latency and storage overhead for low‑throughput workloads.
Which approach introduces less operational overhead for services handling fewer than 100 messages per minute? How does each pattern affect end‑to‑end latency when a retry is triggered? What are the trade‑offs in storage growth and failure recovery between the two patterns?
35275 reputation · 09 Feb 2021, 08:56 UTC
For services handling fewer than 100 messages per minute, Idempotent @Retry introduces significantly less operational overhead than the Transactional Outbox pattern. At this scale, the complexity of managing a separate outbox table and its associated polling processor outweighs the risks, provided that idempotency is enforced at the database level rather than through application-level pre-reads.
| Pattern | Success Path Latency | Retry/Failure Latency |
|---|---|---|
| @Retry | Lowest (direct write) | Increases linearly based on backoff intervals. |
| Outbox | Moderate (two writes) | Decoupled; primary write is fast, but delivery depends on relay polling frequency. |
The primary trade-off is between simplicity and guaranteed delivery. @Retry is ideal for transient network blips but can lead to "retry storms" if the failure is persistent. The Outbox pattern guarantees that a message will eventually be sent if the database transaction commits, regardless of the application's immediate state.
To prevent duplicate rows without the Outbox pattern, avoid "check-then-insert" logic, which is prone to race conditions. Instead, use atomic database constraints:
transaction_id) in your database schema.@Retry, configuring the maxRetries and delay.ConnectException) and not on UniqueConstraintViolationException, which should be treated as a successful duplicate detection.To verify this setup, manually trigger a transient failure (e.g., by briefly pausing the database network) during a request. Confirm that the @Retry mechanism triggers and that the database contains exactly one record for the business key despite multiple attempts.
Diagnostic Detail Needed: Are the "writes" in question strictly internal database inserts, or do they involve calling external third-party APIs? If external APIs are involved, @Retry alone cannot guarantee idempotency unless the external API also supports idempotent keys.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.