kafka: reject max_in_flight_requests != 1 with idempotent_write at config time - #4645
Merged
prakhargarg105 merged 1 commit intoAug 5, 2026
Conversation
josephwoodward
approved these changes
Aug 5, 2026
…nfig time franz-go rejects any value other than 1 for MaxProduceRequestsInflightPerBroker while idempotency is enabled, because it relies on a single in-flight request per broker to keep producer sequence numbers gapless. Connect applied the option unconditionally and validated only `< 1`, so the rejection surfaced from within Connect() as a retryable connection error: the config passed lint, the pipeline started, and it then retried "Failed to connect" forever while producing zero records. No fatal error, no non-zero exit. Add the missing cross-field validation in two places, mirroring how the sibling `acks` constraint is already handled: - A linter rule, so `lint` catches it before the pipeline runs. - A check in FranzProducerOptsFromConfig, which every affected component funnels through, for paths that bypass lint (--chilled, programmatic config). Also correct the field documentation, which claimed the value was "capped at 5 by the Kafka protocol" when idempotent_write is enabled. It is not capped, it must be exactly 1, so the documented configuration could never start. While there, note that this field is distinct from the output's `max_in_flight` and that 1 is not a throughput ceiling, since records from concurrent writes are coalesced into fewer, larger produce requests. Both points caused real confusion. The existing `acks` linter rule matched on `this.idempotent_write == true`, which does not fire when the field is left at its default of true. Use `.or(true)` on both rules so the common shape (setting only the offending field) is caught. This surfaces an existing error earlier rather than introducing a new one, as FranzProducerOptsFromConfig already rejected those configs at construction time. Affects all six components inheriting FranzProducerFields(): the redpanda and kafka_franz outputs, redpanda_migrator, the global redpanda logs/status writer, the redpanda tracer and the ockam_kafka output. Fixes CON-522 Fixes DOC-2404 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
prakhargarg105
force-pushed
the
fix/max-in-flight-requests-idempotency
branch
from
August 5, 2026 17:20
94a34af to
0332404
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Setting
max_in_flight_requeststo any value other than1whileidempotent_writeis enabled (the default) produced a pipeline that passedlint, started, and then produced zero records forever:No fatal error, no non-zero exit — just an endlessly retried connection error at zero throughput.
franz-go is behaving correctly here: it rejects anything but
1while idempotency is on (kgo/config.go:232) because it relies on a single in-flight request per broker to keep producer sequence numbers gapless. The defect is on our side — we applied the option unconditionally and validated only< 1, so a permanent config error surfaced from insideConnect()where the framework treats it as retryable.Made more likely by our own field documentation, which described the invalid configuration as valid ("capped at 5 by the Kafka protocol").
Changes
1. Validate at config time, mirroring how the sibling
acksconstraint is already handled:FranzWriterConfigLints(), solintcatches it before the pipeline runs.FranzProducerOptsFromConfig, which all affected components funnel through, for paths that bypass lint (--chilled, programmatic config construction).2. Correct the field documentation. It said the value is "capped at 5 by the Kafka protocol" when
idempotent_writeis enabled. It is not capped — it must be exactly1, so the documented configuration could never start. While there, added two clarifications that caused real confusion:max_in_flight. That one counts message batches written in parallel (default 256 on theredpandaoutput); this one counts unacknowledged produce requests per broker connection.1is not a throughput ceiling — records from concurrent writes are coalesced into fewer, larger produce requests.3. Fixed a latent gap in the existing
acksrule. It matched onthis.idempotent_write == true, which does not fire when the field is left at its default oftrue, soacks: leaderwithidempotent_writeomitted was never caught at lint. Both rules now use.or(true). This only surfaces an existing failure earlier —FranzProducerOptsFromConfigalready rejected those configs at construction time — but it is strictly an addition to the reported bug and easy to split out if preferred.Affected components
All six inheriting
FranzProducerFields():redpandaandkafka_franzoutputs,redpanda_migrator, the globalredpandalogs/status writer, theredpandatracer, and theockam_kafkaoutput. The older sarama-basedkafkaoutput is unaffected (different client, no such field).Verification
go build ./...clean.go test ./internal/impl/kafka/... ./internal/impl/redpanda/... ./internal/impl/ockam/...— all pass.task lint— 0 issues.task fmtapplied.task docs— regenerated exactly the 6 expected pages, one line each, no unrelated drift.idempotent_write: false+max_in_flight_requests: 5and plain default configs still lint clean.New tests: 5 cases added to
TestKafkaFranzOutputBadParams(covering both the explicit and the defaultedidempotent_writeshapes) plusTestFranzProducerOptsIdempotencyLimits, which asserts the opt-construction path rejects the combination at config parse time rather than at connect time.Notes
Found while tuning a customer ingest pipeline (Zendesk 7161), where raising this value was recommended as a tuning step based on the field documentation and had to be retracted after testing.
Fixes CON-522
Fixes DOC-2404
🤖 Generated with Claude Code