Settlement
What ack, drop, retry and retry_after become on Kafka, which commits one offset per partition
instead of acknowledging records.
The diagram as text
- Stored position: 11 below the lowest running delivery → 11 settled: stored at
- Stored position: 11 below the lowest running delivery → Committed offset: 12 the record the group resumes from: auto-commit
- One partition, offsets as delivered
- 10 settled
- 12 running
- 13 settled
- 14 settled
- 15 running
- When 12 settles, the position moves to 14: 15 is still running
Kafka keeps one committed offset per partition and group. The subscription's
Commit mode decides how a handler's outcome reaches it.
Under Commit::Tracked, the crate turns librdkafka's
automatic offset store off and records every offset it delivers. A settlement stores the position
just below the lowest delivery still running, or the highest delivered offset once none is running.
Acks from parallel lanes can arrive in any order, and the stored position never passes unfinished
work. Offsets the consumer never receives, such as transaction markers or compacted records, do not
hold it back. librdkafka's timer commits the stored position as usual.
| Outcome | Commit::Auto (default) | Commit::Tracked | Commit::Transactional |
|---|---|---|---|
ack | Nothing: librdkafka stored the offset when it handed the record over | The offset settles and the stored position moves up | The offset settles; the pipeline commits it with its next window |
drop | Nothing, as for ack | The offset settles, as for ack | The offset settles, as for ack |
retry() | Refused (AckError::Unsupported) and logged: nothing brings the record back | The record is delivered again ahead of what is fetched next, and the position waits for it | The open window cannot commit: it aborts and is read again |
A rebalance or a seek that moves a partition discards its tracked state. A settlement of a delivery read before the move changes nothing, because the partition's next reader starts from the committed offset anyway.
Under Commit::Transactional the consumer commits
nothing itself. The EosPipeline of the same id adds the
settled positions to its producer transaction every commit_interval (100 milliseconds), so the
consumed offsets and the published records become visible together, as
Kafka transactions draws.
Retries
The diagram as text
- A record x-ruststream-retry-count says which attempt it is → The handler returns retry_after(delay), or retry() under a declaration: handled
- The handler returns retry_after(delay), or retry() under a declaration → max_attempts: is this attempt below the cap?: a retry
- max_attempts: is this attempt below the cap? → Below the cap: the original settles, a copy goes to the topic: below
- max_attempts: is this attempt below the cap? → At the cap: a copy goes to the dead-letter topic: at the cap
- max_attempts: is this attempt below the cap? → At the cap, no dead letter: the record settles, no copy
- Below the cap: the original settles, a copy goes to the topic → The copy is a new record at the end of its partition
- The delayed copy is held in the process: a crash before the timer fires loses it
Kafka holds no record back and counts no deliveries, so the runtime does both. A retry_after
outcome, and a retry() on a registration that declares
max_attempts or a dead-letter topic,
take this path in every commit mode. The attempt count travels in the copy's
x-ruststream-retry-count header, and the first delivery counts as one.
The copy goes to the address the descriptor reports. A
KafkaTopic subscription reports its own topic. A
KafkaTopics or
KafkaPartitions subscription reads a set no single
publish reaches, so its registration names the destination, or the app refuses to start.
The copy keeps the record's key, so it lands on the key's partition again, behind the records written since. Every consumer group reading the topic receives the copy, not only the one that retried.