Exactly-Once vs Effectively-Once
Concepts covered: Idempotency
Exactly-once is one of the most loaded phrases in streaming. Vendor marketing has used it for so long that the engineering meaning has eroded. The honest framing: exactly-once is achievable inside a closed system where the engine controls every read, write, and offset commit. End-to-end exactly-once across systems is generally not achievable; what gets advertised under that name is more precisely called effectively-once, which is at-least-once delivery combined with idempotent consumers. The distinction is not pedantry; it determines what failure modes are possible and where the deduplication work lives.
Three Delivery Guarantees
| Guarantee | Meaning | Typical Where |
|---|---|---|
| At-most-once | Each event is delivered zero or one times | Fire-and-forget logging; metrics where some loss is tolerable |
| At-least-once | Each event is delivered one or more times | Default for Kafka, Kinesis, Pub/Sub; strong guarantee that no event is lost |
| Exactly-once | Each event is processed once and only once at the boundary in question | Achievable inside a single engine; harder across boundaries |
What Exactly-Once Actually Guarantees
Inside a single Flink job with checkpointed state and a transactional sink, exactly-once means that the effect of each input event on the engine's output is applied once even across failures. The mechanism is two-phase commit: the engine writes to the sink in a transaction, the transaction is committed only when the corresponding checkpoint completes, and a failure rolls back any uncommitted output. Kafka Streams provides a similar guarantee when both source and sink are Kafka topics participating in the same transaction. The guarantee is real, but it is bounded to the systems participating in the transaction.
What Effectively-Once Means
Effectively-once is the system-level outcome where each event affects the final state once, achieved by combining at-least-once delivery with idempotent processing. The broker may deliver an event multiple times. The consumer may apply it multiple times. The destination is shaped so that the second application has no effect (UPSERT on a primary key, dedup table keyed on event ID, monotonic counter that ignores duplicates). The end-state property is the same as exactly-once. The implementation is fundamentally different. The implementation difference matters because effectively-once requires the destination to participate; exactly-once requires the engine to participate. The honest characterization for most production deployments is 'effectively-once,' not the stricter engine-level claim.
- Exactly-Once: Two-phase commit between the engine and the sink. Bounded to systems that participate in the transaction. The engine bears the complexity.
- Effectively-Once: At-least-once plus idempotent processing on the consumer side. Works across system boundaries because the destination owns deduplication.
- At-Least-Once: Default for Kafka, Kinesis, Pub/Sub. Guarantees no event loss. Duplicates are possible and must be handled downstream.
Reading Vendor Claims
When a vendor advertises exactly-once, the right question is 'across which boundary.' Kafka exactly-once is exactly-once across Kafka topics within a Kafka transaction; it does not cover writes to a non-Kafka sink. Flink exactly-once is exactly-once for the engine's state; it covers external sinks only when those sinks are configured to participate in two-phase commit, which is a non-trivial configuration that many teams skip. Spark Structured Streaming exactly-once depends on the sink supporting idempotent writes, typically via a checkpoint-aware connector. The marketing word is the same; the engineering reality is per-deployment.
When Effectively-Once Is the Right Choice
Effectively-once is often the better engineering choice even when exactly-once is available. The complexity is lower because the destination owns deduplication. The latency is lower because there is no two-phase commit handshake. The system is more portable because it does not depend on engine-specific transaction semantics. The cost is the engineering work to make destinations idempotent, but that work is local to the destination and reusable across producers. A system with idempotent destinations and at-least-once delivery is operationally simpler than a system with exactly-once delivery to mutable destinations.
-- An idempotent sink: UPSERT on the event_id
-- Combined with at-least-once delivery, produces effectively-once
MERGE INTO daily_revenue AS target
USING (
SELECT
event_id,
revenue_date,
merchant_id,
amount
FROM stream_buffer
) AS source
ON target.event_id = source.event_id
WHEN NOT MATCHED THEN INSERT (event_id, revenue_date, merchant_id, amount)
VALUES (source.event_id, source.revenue_date, source.merchant_id, source.amount);The MERGE inserts each event_id once. A duplicate delivery of the same event_id matches and does nothing. The destination is idempotent, the broker is at-least-once, and the system as a whole is effectively-once. No two-phase commit is required. The engineering investment is in maintaining the dedup index on event_id, which is small relative to the operational simplification.
- Exactly-Once via 2PC
- Engine-level guarantee tied to specific connectors
- 100ms or more added latency per checkpoint
- Configuration is fragile; downgrades are possible without alerts
- Bounded to systems that support the engine's transaction protocol
- Effectively-Once via Idempotent Sink
- Destination owns deduplication via UPSERT or dedup keys
- No transaction overhead; latency unchanged
- Broker delivers at-least-once; configuration is the default
- Works across any combination of producer and consumer systems
Effectively-once What most production systems labeled exactly-once actually deliver: At-least-once broker plus idempotent consumer is the dominant pattern at scale
Exactly-once is a property of a closed system. Effectively-once is a property of an open system that has been engineered to absorb duplicates. The honest characterization of most production deployments labeled exactly-once is closer to 'effectively-once,' and naming it precisely makes the failure modes legible.
- Do
- Default to at-least-once delivery plus idempotent sinks for cross-system pipelines
- Reserve engine-level exactly-once for closed-system patterns where the engine owns both source and sink
- Audit exactly-once claims against the actual sink configuration in production
- Avoid
- Believe the marketing word without naming the boundary it applies across
- Configure two-phase commit on a sink that does not support it; the guarantee silently downgrades
- Skip dedup logic on the destination because the broker promises exactly-once; downstream changes invalidate the assumption
About this lesson
This section is part of the Idempotent Reruns: Advanced lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.