Auto-commit on
The default that acknowledges messages the application has not finished processing
group.id=my-group enable.auto.commit=true max.poll.interval.ms=300000 auto.offset.reset=latest
Paste a consumer .properties file and get the rebalance, offset and delivery problems named with the exact line: the batch that cannot finish inside max.poll.interval.ms, the auto-commit that quietly gives you at-most-once, the default that skips your whole topic, and the assignor change that cannot be rolled out in one deploy. Validity first, then the analysis. It runs in this tab and nothing is uploaded, which matters for a file that usually carries a SASL password.
Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.
The answer appears here
Paste on the left and press Lint. Nothing leaves this tab.
Nothing else to flag.
No formatting problems, and nothing the rules object to. Worth remembering what that covers: this reads the file you pasted, not the account or cluster it will be applied to.
No finding matches that filter.
Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.
The default that acknowledges messages the application has not finished processing
group.id=my-group enable.auto.commit=true max.poll.interval.ms=300000 auto.offset.reset=latest
A consumer with no group, which cannot commit offsets or participate in a rebalance
enable.auto.commit=true max.poll.records=5000 session.timeout.ms=45000
These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.
It commits on a timer, so offsets advance for records the application has not finished processing. A crash then skips them, silently.
Instead:Commit manually after processing, or accept at-most-once and say so in the code.
More records per poll means longer between polls. Exceed the interval and the broker considers the consumer dead and rebalances, mid-batch, repeatedly.
Instead:Size the two together: worst-case per-record time times max.poll.records must fit inside the interval.
It only applies when there is no committed offset, so it fires on a brand new group or after retention removed the committed position. latest silently skips a backlog; earliest silently reprocesses everything.
Instead:Set it deliberately and monitor for it firing, because when it does something else has already gone wrong.
Validity first: a unit suffix that Kafka rejects, a key one character off the real one and silently ignored, a duplicate that means the line you are reading is not the line in force. Then the six behaviours that decide whether this consumer keeps its partitions and keeps your records.
max.poll.interval.ms is a deadline on your code, not on the network, and it defaults to 300000. poll() hands you up to max.poll.records, which defaults to 500. That is 600 ms per record before the coordinator decides this member is gone, revokes its partitions and gives them away. Your code hears about it at the next commit, as CommitFailedException. The tool shows the division, and if you add a comment line reading # ms-per-record=25 it does the multiplication against your own number and tells you the largest safe max.poll.records.
Heartbeats have run on a background thread since 0.10.1, which split liveness in two. session.timeout.ms catches a process that has died: the container was killed, the network went. max.poll.interval.ms catches a process that is alive and heartbeating happily while wedged inside your own code. Conflating them is the most common misreading in this whole area, and it is why raising the session timeout never fixes a slow-processing rebalance. The tool names which is which, checks the heartbeat is under a third of the session, and checks the session sits inside the broker's own group.min and group.max limits, because outside them the join fails with an error that quotes neither number.
enable.auto.commit defaults to true. Auto-commit happens inside poll(), committing offsets for records earlier polls already returned, so a crash part way through a batch skips records that were fetched and never processed. No error, no lag, no retry: they are simply gone. Nearly everyone running this configuration believes they have at-least-once. This is the highest-value line in the file and it is usually the line that is not there.
auto.offset.reset defaults to latest, so a brand-new group attaches to the end of the log and receives nothing that is already in it. The bug report reads my consumer receives no messages. earliest is the other direction and replays everything inside the retention window, which reads as a flood, and is what a typo in group.id produces because a misspelled group is a new group. The setting also applies when a committed offset has fallen off the log through retention, which is how a consumer that was down over a weekend loses its backlog.
Incremental rebalancing avoids the stop-the-world revoke, and you cannot switch to it in one deploy. Eager and cooperative members cannot share a group: if no assignor is common to every member the join fails with InconsistentGroupProtocolException and the group does not form, which takes the application down rather than slowing it. Phase one adds cooperative-sticky alongside the old assignor and rolls. Phase two removes the old one and rolls again. The tool tells you which phase the pasted file is, including the case where somebody stopped after phase one and has been paying for it since.
An inline password in sasl.jaas.config, or an ssl.keystore.password, is reported as a fact about the file rather than about Kafka: that file gets committed, baked into images, mounted from ConfigMaps and pasted into tickets. The value is never echoed back, not in a finding, not in an excerpt, not in the exported report. The fix named is Kafka's own indirection, ${file:/path:key}, which lets the properties file reference the secret without containing it.
This reads one file. It does not know your broker's group.min.session.timeout.ms, your topic's max.message.bytes, how many partitions this group is assigned, or how long your code takes per record, so where those matter it says which number to go and look up rather than guessing one. It also cannot see your commit calls: a consumer with enable.auto.commit=false that never commits looks identical here to one that commits correctly. For the running group, kafka-consumer-groups.sh --describe is the tool that answers what this page cannot.
Almost every consumer setting is about one of three questions: does the group still believe you are alive, where does reading start, and when is an offset written down. The failures are hard to debug because none of them produce an error where the cause is. A rebalance loop looks like a broker problem, a skipped topic looks like a broken producer, and lost records look like nothing at all.
One member of the group holds an assignment of partitions granted by the group coordinator, a broker. The assignment survives only as long as the coordinator believes the member is healthy, and since 0.10.1 it judges that in two independent ways. A background thread sends a heartbeat every heartbeat.interval.ms and the coordinator gives up after session.timeout.ms without one. Separately, the coordinator expects the application to call poll() again within max.poll.interval.ms, and gives up if it does not. The first clock catches a dead process. The second catches a live process stuck in your code, which is the case where heartbeats keep arriving and everything looks fine.
defaults, modern client
session.timeout.ms 45000 no heartbeat for this long -> member is dead
heartbeat.interval.ms 3000 a third of it, so two can be lost safely
max.poll.interval.ms 300000 no poll() for this long -> member is stuck
max.poll.records 500 records handed to you per poll()
the number nobody computes
300000 / 500 = 600 ms per record, for everything you do
with it, including the retry A committed offset is a record in the internal __consumer_offsets topic, keyed by group, topic and partition. It is not where the consumer is reading: that is the position, which lives in memory and moves on every poll. The gap between the two is what a crash costs, and the setting that decides the gap is enable.auto.commit. On, the gap is one auto.commit.interval.ms of records committed possibly before you processed them, which loses records. Off and committing after processing, the gap is one batch reprocessed, which duplicates them. There is no configuration that gives exactly-once on its own: that needs read_committed plus a transactional producer writing the offsets in the same transaction as the output.
enable.auto.commit=true commit happens inside poll(), on a timer
poll -> 500 records
poll -> commits those 500 <- your code may still be on record 3
crash records 3..500 never processed, offsets gone
= at most once, silently
enable.auto.commit=false you commit after processing
poll -> 500 records
process all 500
commitSync()
crash before the commit the 500 arrive again on restart
= at least once, so make it idempotent It is not where the consumer starts. It is what happens when there is no committed offset for a partition, or when the committed offset no longer exists in the log. Three situations reach it: the first run of a new group, a partition added to a topic after the group started, and a group whose committed offset was deleted by retention because it was away too long. In all three the default, latest, discards whatever is sitting in the log, and none of them logs anything that looks like a problem.
group has a committed offset? -> resume there, this setting is ignored
no committed offset, and:
latest start at the end the backlog is skipped, silently
earliest start at the beginning the whole retained topic replays
none throw NoOffsetForPartitionException
new group + latest is the reason a working consumer receives nothing
a typo in group.id + earliest is the reason one suddenly floods Under the eager protocol every member revokes every partition at the start of a rebalance and waits for a new assignment, so the whole group stops consuming for the duration. Cooperative rebalancing revokes only the partitions that are actually moving, so most members never stop. The catch is in how the assignor is chosen: the coordinator picks the most preferred assignor that every member supports, so a group holding both eager-only and cooperative-only members has no common choice and fails to form. That is why the change is two rolling restarts and never one. Since 3.0 the client default is [RangeAssignor, CooperativeStickyAssignor], which still resolves to Range and therefore to eager, so having the cooperative entry in the list is not the same as using it.
phase one, roll every member with both:
partition.assignment.strategy=\
org.apache.kafka.clients.consumer.CooperativeStickyAssignor,\
org.apache.kafka.clients.consumer.RangeAssignor
the group keeps using Range, because the members not yet
restarted support nothing else. nothing improves yet.
phase two, roll again with only:
partition.assignment.strategy=\
org.apache.kafka.clients.consumer.CooperativeStickyAssignor
now every member supports it, so it is chosen.
skipping phase one: InconsistentGroupProtocolException,
and the group does not form at all A transactional producer marks its records committed or aborted after the fact. Filtering the aborted ones out is the consumer's job, and the default does not do it: read_uncommitted returns everything in the log, including work that was explicitly rolled back. Setting read_committed also changes what the consumer can see at all, because it reads only up to the last stable offset. An open transaction on a partition therefore blocks progress on it until the producer commits or transaction.timeout.ms expires, and reported lag includes records the consumer is not yet allowed to read. Both of those are normal here and both get debugged as consumer problems.
read_uncommitted (default) every record, aborted ones included
read_committed up to the last stable offset only
symptom under read_committed: lag stops moving on one partition
cause: a producer died mid-transaction
fix: nothing on the consumer, wait for transaction.timeout.ms