Idempotence without acks=all
A combination Kafka rejects at startup, with the reason
enable.idempotence=true acks=1
Paste a producer .properties file
and get the durability, ordering and throughput problems named with the exact
line: the settings that stop the client starting, the reordering that happens
with no error at all, and the defaults that changed in Kafka 3.0 so that the
same file now means two different things.
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.
A combination Kafka rejects at startup, with the reason
enable.idempotence=true acks=1
Acknowledged by the leader alone, so a leader failure loses the write
acks=1 retries=2147483647 enable.idempotence=false
These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.
Kafka rejects the combination at startup. Idempotence requires acks=all, because the guarantee depends on the full ISR acknowledging.
Instead:Set acks=all, or turn idempotence off deliberately.
The leader acknowledges before replicating. A leader failure immediately after the ack loses the write with no error anywhere.
Instead:acks=all with min.insync.replicas=2 on a replication factor of 3.
With retries and more than one in-flight request, a retried batch can be written after a later one, silently reordering records within a partition.
Instead:Enable idempotence, which handles this, or set max.in.flight.requests.per.connection=1.
Validity first: whether the client starts at all. Then the pitfalls, which are almost all relationships between two settings rather than one wrong value.
enable.idempotence became true by default, and it pulls acks to all and retries to Integer.MAX_VALUE with it. So an unspecified acks means 1 on a 2.x client and all on a 3.x one, and the same file describes two different durability profiles. Where the answer depends on the client version, the finding says so instead of picking one.
enable.idempotence=true with acks=1 is not a warning: the KafkaProducer constructor throws ConfigException and the process does not start. Same for retries=0 with idempotence, for more than five requests in flight with it, and for a delivery.timeout.ms below linger.ms plus request.timeout.ms. Those come first, before anything about durability.
max.in.flight.requests.per.connection above 5 is rejected whenever idempotence is on, and from 3.0 it is on unless you turned it off. Nothing else has to change for a working producer to stop starting after a client upgrade, and the exception names the in-flight setting rather than the default that turned on.
Without idempotence, more than one request in flight plus a retry reorders records within a partition: batch one fails, batch two lands, batch one is retried behind it. Partitioning by key does not save you. It guarantees the records go to the same partition, not the order they arrive in it, and there is no error and no log line when it happens.
Since 2.1 the producer gives up when delivery.timeout.ms expires, whichever comes first, and that clock covers waiting for metadata and sitting in the accumulator as well as retries. So retries=0 does not make a send fail fast, a large retries value does not mean the attempts happen, and lowering retries no longer shortens the time to a failed callback.
batch.size is a ceiling, not a target, so raising it while linger.ms stays at 0 changes nothing: the producer sends as soon as a sender thread is free. Compression works per batch for the same reason, so linger.ms=0 also weakens it. Both are reported as the pair they are rather than as two independent settings.
It returns a Future, so it looks non-blocking. When buffer.memory is full or the topic metadata has not arrived, the calling thread blocks for up to max.block.ms, 60 seconds by default, and then throws TimeoutException. On a request-handling thread pool that turns producer backpressure into a stalled service.
max.request.size is checked against the broker's message.max.bytes and the topic's max.message.bytes, and only the client side is in this file. Raising one of the three turns a local rejection into a RecordTooLargeException from the broker that looks like a client bug. Those settings are named where they matter rather than checked, because a config file cannot show them.
A real producer file usually carries
sasl.jaas.config with an
inline password, or a keystore password, or both. That is exactly the
kind of file nobody should paste into a stranger's backend, so this
page does not have one. There is no network request of any kind: the
parser and every rule are JavaScript in your browser, the file is
never uploaded, and nothing is written to storage. Close the tab and
there is nothing left to delete.
The credential is reported, because a password sitting in a file that is probably in git is worth knowing about. It is reported without its value: not in the finding, not in the copied report, and not in the source excerpt, which is why the excerpts here show a single line rather than the usual few lines of context. A tool that prints the password back to prove it found one has just copied it into the page, the clipboard and any screenshot of the result.
If a credential is in the file and the file has been committed anywhere, deleting the line is not the fix. Rotate the credential, and inject it at startup from a secret store or the environment instead.
Fifteen lines of properties decide whether a record survives a broker failure, whether two records for the same key stay in order, and how many requests per second your cluster has to serve for the same bytes. None of that is visible in the file, and most of it is a relationship between two settings rather than a value in one.
acks=0 means the producer treats a successful socket write as success: it does not wait for the leader, and the send callback has nothing to report because no answer was expected. acks=1 waits for the leader's own log and no further, so a leader failure in the replication window loses a record that was already reported as sent. acks=all waits for every replica currently in sync, which is where min.insync.replicas comes in: it is a broker or topic setting, and at its default of 1 a lone in-sync leader satisfies acks=all on its own.
acks=0 leader never asked loss is invisible
acks=1 leader's log only loss on leader failure
acks=all every in-sync replica loss only if the ISR shrank to one
acks=all + min.insync.replicas=1 -> one copy can be enough
acks=all + min.insync.replicas=2 -> writes fail rather than under-replicate
replication.factor=3 keeps them possible From Kafka 3.0 enable.idempotence defaults to true, which implies acks=all and retries=Integer.MAX_VALUE. The validation that follows behaves differently depending on whether you wrote enable.idempotence yourself. If you did, a contradiction throws. If you did not, the client backs down: it turns idempotence off and logs one INFO line. That second path is why a config can be upgraded to 3.x, keep working, and quietly not have the guarantee the upgrade advertised.
enable.idempotence=true
acks=1 ConfigException, the producer does not start
acks=1
(enable.idempotence unset) 3.0+: idempotence turned off, INFO log, starts
2.x : already off, nothing to log
max.in.flight.requests.per.connection=10
(enable.idempotence unset) 3.0+: ConfigException, does not start
2.x : starts, and can reorder on retry Kafka guarantees the order of a partition's log, not the order your sends reach it. With several requests in flight to one partition and a retry, the failed batch is re-sent after the batches behind it were already accepted, and the log order is now different from the send order. The idempotent producer fixes this properly: each batch carries a sequence number and the broker refuses one that arrives out of sequence, which is why five in flight is safe with idempotence and unsafe without it.
enable.idempotence=false
max.in.flight.requests.per.connection=5
send A -> in flight A fails, will be retried
send B -> in flight B is accepted, appended
retry A A is appended after B
log order: B, A same key, wrong order, no error anywhere
enable.idempotence=true
max.in.flight.requests.per.connection=5 order preserved by sequence number KIP-91 replaced the retry count with a deadline. delivery.timeout.ms, 120000 by default, bounds the whole time from send() returning to the callback firing: waiting for metadata, sitting in the accumulator, every attempt and every backoff. request.timeout.ms bounds one attempt inside that. The client also validates the relationship, and how it validates depends on whether you set the deadline yourself.
delivery.timeout.ms >= linger.ms + request.timeout.ms
set explicitly and smaller ConfigException at construction
left unset and smaller raised for you to the sum, WARN logged
retries=0 with the default deadline
a callback can still take up to 120 s, for example while the
partition leader is unknown. "fail fast" is delivery.timeout.ms,
not retries. The producer accumulates records per partition and sends a batch when it reaches batch.size or when linger.ms elapses, whichever comes first. At linger.ms=0 there is no waiting, so under anything short of saturation the batches are tiny: the byte count is the same and the request count is many times higher, which is broker CPU rather than bandwidth. Compression is applied per batch and the brokers store the batch compressed, so a batch of one record compresses to roughly nothing saved.
linger.ms=0 batch.size=1048576 batches stay small anyway
linger.ms=20 batch.size=65536 up to 20 ms of records per request
compression.type=none the default, and rarely the right answer for text
compression.type=lz4 cheap CPU, works everywhere
compression.type=zstd best ratio, needs brokers and consumers on 2.1+ buffer.memory, 32 MiB by default, is the pool records wait in before they are sent. When it is full, send() blocks the calling thread for up to max.block.ms and then throws TimeoutException, and the same happens while the metadata for a topic is still being fetched. Everything after that point is asynchronous and reaches you through the callback, which is why a failed send and a failed delivery are two different pieces of error handling.
send() -> metadata known? no: block, up to max.block.ms
-> buffer space? no: block, up to max.block.ms
-> appended to a batch returns a Future here
-> sent, retried, timed out callback, up to delivery.timeout.ms
max.block.ms default 60000 a request thread can stall for a minute
buffer.memory default 33554432 must exceed batch.size and max.request.size