While transactional systems (like Flink 2PC or Kafka Transactions) provide end-to-end exactly-once semantics, many external target systems (such as legacy REST APIs, external cloud databases, and search clusters) do not support distributed two-phase commit.
In these environments, achieving effective Exactly-Once Processing relies on a practical engineering rule:
Combine At-Least-Once Event Delivery with an Idempotent Storage Sink.
An operation is idempotent if applying it multiple times produces the exact same system state as applying it once ().
1. Natural Keys vs Deterministic Composite Deduplication Keys
To make an arbitrary event stream idempotent, every event must possess a Deterministic Idempotency Key:
Why Random UUIDs Break Idempotency:
If an application generates a random UUID.randomUUID() every time it retries sending an event, the two attempts have different IDs. Downstream sinks cannot recognize them as duplicates, resulting in double-processing.
Always derive idempotency keys deterministically from immutable business domain attributes.
2. Relational Database Sinks: Atomic Upsert Semantics
In relational databases (PostgreSQL, MySQL, SQLite), idempotent writes are implemented using Upserts (INSERT ... ON CONFLICT):
Production PostgreSQL Upsert Query with Monotonic Guard:
3. High-Throughput Batch Sink Implementation (TypeScript & PostgreSQL)
4. Go Implementation: Idempotent Redis Deduplication Sink
5. Summary & Key Principles
- Deterministic State Mutations: Avoid relative mutations (e.g.
balance = balance + 50); always compute absolute state idempotently or gate with unique idempotency keys. - Versioned Upserts: Always include a monotonic timestamp or LSN in your
ON CONFLICTupdate clauses to reject out-of-order replayed records. - Expire Deduplication Keys: Set appropriate Time-To-Live (TTL) horizons (e.g., 7 days) on deduplication caches to bound storage growth.