80 lines
3.5 KiB
Markdown
80 lines
3.5 KiB
Markdown
[← Consuming](05-consuming.md) · [Module README](../README.md) · [Testing →](07-testing.md)
|
|
|
|
# 6. Acknowledgement, and a property that is not where you look for it
|
|
|
|
The container's default `AckMode` is **`BATCH`**: commit the offsets of the whole `poll()` batch
|
|
after the listener has returned for every record in it. Confirmed at runtime by
|
|
[`EffectiveConfigTest`](../src/test/java/com/ankurm/kafkabasics/EffectiveConfigTest.java):
|
|
|
|
```
|
|
=== listener container ===
|
|
ackMode BATCH
|
|
groupId orders-basic
|
|
```
|
|
|
|
That gives at-least-once delivery. A crash after processing and before the commit redelivers the
|
|
batch, which is why your listener must be idempotent — and why
|
|
[the error-handling article](https://ankurm.com/spring-kafka-4-1-error-handling-dlt-retry-topics/)
|
|
is a necessary sequel rather than an optional one.
|
|
|
|
## Where `enable.auto.commit` actually lives
|
|
|
|
This one is worth the detour, because every debugging session about commits starts in the wrong
|
|
place.
|
|
|
|
- The **kafka-clients default** for `enable.auto.commit` is `true`.
|
|
- **Spring Boot does not set it.** It is absent from `ConsumerFactory.getConfigurationProperties()`,
|
|
before the containers start and after.
|
|
- **`ConsumerFactory.isAutoCommit()` returns `true`** on a stock Boot 4.1 application, because it
|
|
reads that same absent key and falls back to the client default.
|
|
- And yet **no consumer auto-commits**, because `ListenerConsumer.determineAutoCommit` checks
|
|
whether the factory config contains the key and, when it does not, calls
|
|
`setProperty("enable.auto.commit", "false")` on the **per-container `Properties`** handed to
|
|
`createConsumer`.
|
|
|
|
So the shared factory never learns, its public accessor answers the opposite of the truth, and
|
|
the real value lives in an override map you cannot reach from application code. All four of
|
|
those statements are asserted in the test, including the counter-intuitive one:
|
|
|
|
```java
|
|
assertThat(this.consumerFactory.isAutoCommit()).isTrue();
|
|
```
|
|
|
|
If you ever need to know whether a running consumer auto-commits, read the consumer's own
|
|
metrics or its startup log line, not the factory.
|
|
|
|
## Manual acknowledgement
|
|
|
|
```yaml
|
|
spring:
|
|
kafka:
|
|
listener:
|
|
ack-mode: MANUAL
|
|
```
|
|
|
|
```java
|
|
@KafkaListener(topics = "orders")
|
|
void onOrder(OrderEvent event, Acknowledgment acknowledgment) {
|
|
process(event);
|
|
acknowledgment.acknowledge();
|
|
}
|
|
```
|
|
|
|
Nothing commits until `acknowledge()` runs, which is what you want when the work must be durable
|
|
before the offset moves. Two things to know:
|
|
|
|
- Asking for `MANUAL` while auto-commit is genuinely enabled is an
|
|
`IllegalStateException` at container start. It does not happen on a stock configuration —
|
|
despite `isAutoCommit()` returning `true` — because `determineAutoCommit` sets the container
|
|
property to `false` before the check runs. [`ManualAckTest`](../src/test/java/com/ankurm/kafkabasics/ManualAckTest.java)
|
|
pins that down, because it is exactly the kind of interaction that would otherwise be a
|
|
surprise in production.
|
|
- **An `Acknowledgment` you forget to call stalls the partition.** Not immediately — the
|
|
container keeps polling until `max.poll.records` of un-acknowledged records accumulate. So the
|
|
symptom is a consumer that works for a while and then stops, which reads like a broker problem.
|
|
|
|
`MANUAL_IMMEDIATE` commits synchronously on the consumer thread instead of at the end of the
|
|
batch. It is slower and it is the right choice when redelivery is genuinely expensive.
|
|
|
|
[Testing →](07-testing.md)
|