Retries and max.in.flight.requests in Kafka
Two small producer settings decide whether a shaky network costs you a lost message, a duplicate, or scrambled order. This guide explains what retries actually do, what "in flight" means, and how the two settings work together safely.
Picture a waiter carrying three plates to three different tables at once, so the kitchen stays fast. Now imagine one of those plates gets sent back because the order was wrong, and the waiter has to make it again and re-deliver it, all while two more plates are still on their way to their tables. Kafka producers work in a very similar way, sending several batches of messages at the same time for speed, and sometimes needing to resend one of them.
Two settings govern this behaviour. Retries decides whether the producer tries again after a failure. Max.in.flight.requests.per.connection decides how many unacknowledged requests can be traveling to a broker at the same time. Get them wrong together, and messages can arrive out of order, or a rare duplicate can sneak in. Get them right, and Kafka gives you a fast, reliable, and correctly ordered stream of data, without your application ever needing to notice that anything went wrong underneath it.
This tutorial builds the idea from the ground up, so that every term is explained the moment it appears.
1Why a Producer Ever Needs to Retry
Retries exist because networks and machines are not perfectly reliable.
A message can fail in more than one way
A Kafka producer is a program that sends messages, called records, to a Kafka broker, which is a server that stores and manages the data. Between the producer and the broker sits a network, and networks are never perfectly dependable. A cable can be briefly unplugged, a broker can be too busy to answer immediately, or a leader broker might be in the middle of restarting.
When a send fails, or when no reply comes back in time, the producer has a choice. It can give up and report an error to your application, or it can try sending the exact same data again. This second option is called a retry, and it is one of the simplest and most valuable safety features a messaging system can offer.
You call a friend and the call drops halfway through a sentence. You do not give up on the conversation forever. You call back and finish the sentence. A Kafka retry is exactly that second call, done automatically by the producer instead of by a person.
What Kafka considers a retriable error
Not every failure is worth retrying. Some errors are temporary, such as the broker being briefly unavailable during a leader election. Kafka calls these retriable errors, and the producer is designed to try again automatically. Other errors are permanent, such as sending a message that is too large for the broker to accept, and retrying those would be pointless, so the producer reports them immediately instead.
flowchart TD
A[Producer sends a batch] --> B{Reply received in time?}
B -- Yes, success --> C[Batch is safely stored]
B -- No, or an error came back --> D{Is the error retriable?}
D -- Yes --> E[Wait briefly, then resend the same batch]
E --> B
D -- No --> F[Report the failure to the application]
A message that is safely stored on the broker, but whose success reply gets lost on the way back, looks identical to a message that truly failed. This is why retries, on their own, can sometimes create duplicates, a point we will return to later in this guide.
2The Retries Setting, Explained
A single configuration value controls how persistent the producer is allowed to be.
What the number means
The retries setting tells the producer the maximum number of times it may resend a batch after a retriable failure, before it gives up and reports an error. In older Kafka client versions, this defaulted to a small number, such as zero or a handful of attempts. In modern Kafka client versions, it defaults to a very large number, which in practice means “keep trying until the request simply times out.”
The setting that really controls the clock
A high retries number alone would not be very useful if the producer could retry forever without limit. Kafka solves this with a companion setting called delivery.timeout.ms, which sets the total time budget for a message, covering every attempt, wait, and retry combined. Once this overall time budget runs out, the producer stops trying and reports failure, no matter what the retries number says.
Retries is like telling someone “keep knocking on the door as many times as it takes.” Delivery.timeout.ms is like adding “but stop and leave after fifteen minutes, however many knocks that turns out to be.” The second rule is usually the one that actually decides when the person walks away.
A short pause between attempts
The producer does not retry instantly, back to back, because that could make a struggling broker even more overloaded. A setting called retry.backoff.ms controls how long the producer waits before trying again, giving the broker or the network a small chance to recover first.
Setting retries to a huge number does not mean a message will be retried forever. Delivery.timeout.ms almost always runs out first, and once it does, the producer reports failure and calls the application’s error callback, if one was provided.
3Kafka Basics Before We Go Deeper
A handful of terms carry the entire rest of this guide, so let us define each one clearly.
Broker
A server that stores Kafka data. A group of brokers working together forms a Kafka cluster.
Topic and Partition
A topic is a named stream of messages, split into partitions. Each partition is an ordered log where new messages are added only at the end.
Leader
Each partition has one broker acting as its leader, which handles all writes for that partition.
Batch
Producers group several messages heading to the same partition into a single batch, sent together as one request for efficiency.
Connection
A producer keeps a network connection open to each broker it talks to, and reuses it for many requests rather than opening a new one every time.
In Flight
A request is “in flight” from the moment it is sent until the moment its reply, whether success or failure, comes back.
Think of a connection to a broker as a single road between the producer’s office and a warehouse. A request “in flight” is a delivery truck currently on that road, already dispatched but not yet confirmed as delivered.
Why producers do not wait for one reply before sending the next
Waiting for a full round trip, request out and reply back, before sending the next request would be safe but very slow, especially across a network with any real distance or congestion. Kafka producers instead allow several requests to be outstanding on the same connection at once, which keeps the pipe full and throughput high. This is exactly what max.in.flight.requests.per.connection controls, and it is the subject of the next chapter.
4What Is max.in.flight.requests.per.connection?
This setting is the speed-versus-order dial for a Kafka producer.
The plain meaning
Max.in.flight.requests.per.connection sets the maximum number of produce requests a producer is allowed to have outstanding, meaning sent but not yet acknowledged, on a single connection to a given broker at the same time. If the limit is five, the producer can have up to five unanswered requests traveling to that broker before it must pause and wait for at least one reply.
Why more than one in-flight request helps
If the producer had to wait for each request to be fully acknowledged before sending the next one, every network round trip would sit idle while nothing new is being sent. Allowing several requests in flight means the producer keeps pushing new batches out while still waiting on earlier ones, which uses the network connection far more efficiently.
sequenceDiagram
participant P as Producer
participant B as Broker
P->>B: Request 1
P->>B: Request 2
P->>B: Request 3
B-->>P: Reply to Request 1
P->>B: Request 4
B-->>P: Reply to Request 2
B-->>P: Reply to Request 3
Note over P,B: Up to 3 requests in flight at once
A single-lane road that only allows one car to enter after the previous one has fully arrived is safe but slow. A road that allows five cars on it at once moves more traffic, but now you must think carefully about what happens if car three arrives before car one.
If request 1 fails and is retried while requests 2 and 3 have already succeeded, the retried request 1 might land on the broker after request 3. Without protection, this can reorder your messages within a partition. This is precisely the problem the next chapter explains.
5The Ordering Problem When Retries Meet Multiple In-Flight Requests
This is the single most important interaction in this whole topic, so we slow down here.
How order can break
Imagine three batches, A, B and C, sent one after another to the same partition, with up to three requests allowed in flight. All three are sent quickly. Batch A happens to fail, perhaps because of a brief network blip, while batches B and C succeed and are written to the log. The producer retries batch A, and it now arrives and is written after B and C, even though your application sent it first.
sequenceDiagram
participant P as Producer
participant B as Broker Leader
P->>B: Batch A
P->>B: Batch B
P->>B: Batch C
B--x P: Reply to A is lost
B->>P: Reply to B, written
B->>P: Reply to C, written
Note over P: A times out, producer retries it
P->>B: Batch A again
B->>P: Reply to A, written
Note over B: Log order is now B, C, A instead of A, B, C
You mail three numbered letters on the same day. The post office loses letter one temporarily, then finds it and delivers it a day after letters two and three. Your friend now reads the story out of order, even though you wrote and sent it in the right sequence.
Why this matters
Kafka only promises that messages within one partition stay in the order they were written to the log, and this ordering problem is exactly what can break that promise if retries and multiple in-flight requests are combined carelessly. For data such as a sequence of state changes on an order, or a series of sensor readings, reading them out of order can lead to wrong conclusions, such as thinking an order was cancelled before it was ever placed, which is a much harder bug to trace than a simple crash.
Before Kafka gained a better solution, the standard advice was to set max.in.flight.requests.per.connection to 1 whenever order mattered, accepting a real throughput cost. This forced strict one-at-a-time delivery, so a retry could never leapfrog a later batch, because there was no later batch in flight yet.
6How the Idempotent Producer Fixes This Without Sacrificing Speed
Modern Kafka solves the ordering problem a smarter way, without forcing everyone back to one request at a time.
Stamping every batch with a sequence number
When the idempotent producer feature is enabled, which is the default in modern Kafka clients, every batch sent to a partition carries a producer ID and a strictly increasing sequence number, stamped automatically by the client library without any extra work from your application code. The broker checks this number before writing a batch to the log.
If a batch’s sequence number is not the next number the broker expects for that producer and partition, the broker holds off on writing it and waits, rather than writing it out of order. This means even if batch A’s retry physically arrives after batch C, the broker recognises that A must come first in the log and places it correctly, or rejects an attempt that would break order.
Each batch carries a sequence number
Batch A is stamped with sequence 10, batch B with sequence 11, batch C with sequence 12, all for the same partition.
The broker tracks the expected next number
If it has already written sequence 9, it expects sequence 10 to come next, regardless of the order requests physically arrive in.
Out-of-order arrivals are handled correctly
Kafka’s broker-side logic, combined with a bound on in-flight requests, ensures the log still ends up in the correct sequence-number order, not simply the arrival order.
Why up to five in-flight requests is now considered safe
With idempotence switched on, Kafka allows max.in.flight.requests.per.connection to be set as high as five while still guaranteeing correct ordering, because the sequence-number check does the ordering work that used to depend entirely on sending one request at a time. This is why five, not one, is the modern default, giving you most of the throughput benefit of parallel requests without the old ordering risk.
Retries make a producer resilient. Multiple in-flight requests make it fast. Idempotence is the piece that lets you have both at once without scrambling the order of your data.
7What Happens Without Idempotence Enabled
It helps to see the older, riskier behaviour clearly, since some systems and older configurations still run this way.
Three delivery styles, side by side
The combination of retries, in-flight requests and idempotence produces a few distinct outcomes depending on how each setting is configured. Understanding these outcomes is the fastest way to reason correctly about a specific application’s needs.
| Configuration | Speed | Order preserved? | Duplicates possible? |
|---|---|---|---|
| Retries off, in-flight high | Fast | Yes, nothing is ever resent | No, but messages can be lost on failure |
| Retries on, in-flight 1, idempotence off | Slow | Yes, strictly one at a time | Rare, but still possible on lost acknowledgements |
| Retries on, in-flight 5, idempotence off | Fast | Not guaranteed | Possible |
| Retries on, in-flight 5, idempotence on | Fast | Yes, guaranteed by sequence numbers | No, duplicates are detected and dropped |
The bottom row is what modern Kafka gives you by default. It used to take a deliberate trade-off between speed and safety to get correct behaviour, and idempotence removed the need to choose.
Why this history still matters today
You may encounter older systems, tutorials, or configuration files written before idempotence became the default, where max.in.flight.requests.per.connection is explicitly set to 1. Recognising this pattern helps you understand that the original author was protecting message order the hard way, and it is often safe, and faster, to modernise that configuration once idempotence is confirmed to be enabled.
8Internal Working: A Batch’s Full Journey
Let us trace one batch from creation to final confirmation, including a retry along the way.
The batch is assembled
The producer groups messages destined for the same partition into a batch, once enough messages have gathered or a short linger period has passed.
The batch checks the in-flight limit
Before sending, the producer checks how many requests are already outstanding on the connection to the target broker. If the limit set by max.in.flight.requests.per.connection has been reached, the new batch waits.
The batch is sent and becomes in flight
Once room is available, the batch travels to the broker and is now counted among the outstanding, unacknowledged requests.
The broker responds, or does not
The broker either replies with success, replies with an error, or simply does not reply before the producer’s request timeout elapses.
A failure triggers the retry decision
If the failure is retriable, and the retries count and delivery.timeout.ms budget both still allow it, the producer waits briefly, then resends the identical batch.
The broker checks the sequence number
With idempotence enabled, the broker uses the batch’s stamped sequence number to place it correctly in the log, even if it arrives after a later batch.
Success is confirmed, and the slot frees up
Once acknowledged, the batch is removed from the in-flight count, freeing capacity for the next batch waiting to be sent on that connection.
A small delivery van can carry a limited number of parcels out for delivery at once. Once a parcel is confirmed delivered, that slot in the van frees up for the next parcel waiting at the depot. A parcel that fails delivery gets tried again, and the depot still knows exactly where it belongs on the recipient’s doorstep pile.
9Configuring Retries and In-Flight Requests Correctly
A short, practical reference for setting these values sensibly.
| Setting | Recommended value | Why |
|---|---|---|
| enable.idempotence | true | Unlocks safe ordering with multiple in-flight requests, true by default in modern Kafka |
| retries | a high number, effectively the default | Lets the producer keep trying through transient failures |
| delivery.timeout.ms | set according to how long a message can wait before your application should be told it failed | The real ceiling on total retry time |
| max.in.flight.requests.per.connection | up to 5 with idempotence on, 1 if idempotence must stay off | Balances throughput against ordering risk |
| retry.backoff.ms | a small delay, often left at its default | Avoids hammering a struggling broker with instant repeated retries |
The safe, high-throughput default
Leave enable.idempotence at true, keep retries high, and set max.in.flight.requests.per.connection to 5. This is what modern Kafka clients ship with out of the box, and it suits the overwhelming majority of applications.
When you might still lower in-flight requests
If idempotence cannot be enabled for some unusual operational reason, dropping max.in.flight.requests.per.connection to 1 restores strict ordering at the cost of throughput, exactly as it did before idempotence existed.
If you explicitly set retries to 0, you are choosing at-most-once delivery for that producer. A single transient network blip can then silently drop a message, with no automatic recovery. This may be acceptable for some low-value telemetry, but it is rarely right for anything you depend on.
10Retries and In-Flight Requests vs Related Kafka Features
These settings sit alongside other Kafka features that people sometimes confuse them with.
Retries and max.in.flight.requests
- Control how the producer behaves toward a single broker connection
- Decide resend behaviour and how much can be outstanding at once
- Configured entirely on the producer
Idempotent Producer
- Adds correctness on top of retries, removing duplicates and preserving order
- Is what makes a high in-flight limit safe to use
- Also configured on the producer, closely tied to acks
Consumer-side retries are a separate topic
Everything in this guide concerns the producer sending data toward Kafka. Consumers reading data from Kafka have their own separate concerns around retrying failed processing and committing offsets, governed by entirely different settings and logic.
Kafka transactions build on top of this foundation
Kafka transactions, which coordinate writes across multiple partitions and topics atomically, require idempotence to already be active underneath them. Retries and in-flight request handling continue working exactly as described here, with an additional coordination layer added on top.
11Advantages and Trade-offs
A balanced look at what these settings buy you, and what they cost.
Advantages
- Retries turn transient network and broker hiccups into invisible, automatic recoveries
- Multiple in-flight requests keep throughput high on connections with any real network latency
- Modern defaults require no manual tuning for most workloads
- Idempotence lets you keep both retry safety and ordering at the same time
- Delivery.timeout.ms gives predictable, bounded failure behaviour instead of endless retrying
Disadvantages / Trade-offs
- A high in-flight limit without idempotence risks message reordering
- Retries add latency to the specific messages that hit a transient failure
- Setting retries to 0 trades safety for speed, which is easy to do by accident
- A very long delivery.timeout.ms can leave your application waiting a long time to learn a message truly failed
- Old configurations with in-flight forced to 1 pay an unnecessary throughput penalty once idempotence is available
12Monitoring Retries and In-Flight Behaviour
Numbers you cannot see are numbers you cannot trust in production.
record-retry-rate
How often batches are being retried. A rising trend often signals network trouble or an overloaded broker.
record-error-rate
Counts sends that ultimately failed even after all allowed retries. This should stay close to zero in a healthy system.
request-latency-avg
Average round-trip time for a produce request. Retries and a full in-flight queue both tend to push this number up.
requests-in-flight
Shows how close the producer is running to the max.in.flight.requests.per.connection ceiling, useful for judging whether raising the limit could help throughput.
When a message finally fails after exhausting its whole delivery timeout budget, that is a strong signal of a sustained problem, not a passing blip. Treat a rising count of these failures as an incident-worthy signal, since it usually means a broker, partition, or network path has been unhealthy for a meaningful stretch of time.
13Design Patterns and Anti-Patterns
Patterns that work well with retries and in-flight requests, and habits that undermine them.
Pattern: Trust the modern defaults
Unless you have a specific, well-understood reason to change them, leaving idempotence on, retries high, and in-flight requests at 5 gives correct, fast behaviour without any custom logic in your application.
Pattern: Set delivery.timeout.ms to match business urgency
A payment event and a background analytics event can tolerate very different amounts of retry time before your application should be told about a failure. Tune this per use case rather than leaving every producer on one blanket value.
Problem
Raising max.in.flight.requests.per.connection above 5, or leaving idempotence disabled while keeping a high in-flight limit, in the belief that this is purely a throughput knob.
Why It’s Harmful
Without idempotence, a higher in-flight limit increases the window in which a retried batch can be written out of order relative to batches sent after it, silently corrupting the intended sequence of events in a partition.
Correct Approach
Keep enable.idempotence set to true whenever the in-flight limit is above 1, and treat the two settings as a matched pair rather than tuning one in isolation.
Problem
Setting retries to 0 across an entire fleet of producers as a blanket policy, for the sake of simplicity or to avoid ever seeing a delayed send.
Why It’s Harmful
Every transient failure, even a broker restart lasting a second, then permanently drops the affected messages, with no automatic recovery of any kind.
Correct Approach
Reserve retries set to 0 for genuinely disposable data, and rely on delivery.timeout.ms, not the retries setting itself, to bound how long an important message is allowed to keep trying.
14Real-World and Industry Examples
Seeing where these settings matter in practice makes the idea concrete.
Financial transaction pipelines
Banks and payment processors rely on retries to survive brief network wobbles without losing a transaction event, while idempotence keeps the sequence of debits and credits correctly ordered.
IoT and sensor networks
Devices publishing readings over unreliable mobile or field networks depend heavily on retries, since a lost reading is often unrecoverable, while multiple in-flight requests keep a high-volume device fleet from bottlenecking on a single connection.
E-commerce event streams
Order state changes, such as placed, paid, and shipped, must stay in order for downstream systems to make sense of them, which is exactly the scenario that idempotence combined with a higher in-flight limit was built to protect.
Log and metrics aggregation
High-volume logging pipelines often favour throughput heavily, using the default in-flight limit of 5 with idempotence on to move large volumes of data quickly without losing correctness.
15A Complete Walkthrough Scenario
Let us tie every idea together with one end-to-end story.
A ride-hailing company streams trip-status events, such as requested, driver assigned, and trip started, into a Kafka partition keyed by trip ID. During a period of mobile network instability in one city, several producer requests begin failing intermittently, and the on-call engineer needs the pipeline to stay both fast and correct without any manual intervention.
Three trip-status batches are sent close together
Requested, driver assigned, and trip started are all sent within milliseconds of each other, with up to five requests allowed in flight.
The first batch’s acknowledgement is lost
The requested event was actually written successfully, but the confirmation never reaches the producer before its timeout fires.
The other two batches succeed normally
Driver assigned and trip started are acknowledged and written to the log while the first batch’s status is still uncertain.
The retries setting kicks in
Because retries is enabled and delivery.timeout.ms has not yet elapsed, the producer automatically resends the requested batch.
The sequence number keeps everything in order
Because idempotence is enabled, the broker recognises the requested batch’s sequence number belongs before driver assigned and trip started, and places it correctly in the log despite arriving last.
Downstream systems see a sensible story
Any service reading this partition sees requested, then driver assigned, then trip started, in the correct order, exactly as the rider experienced the trip.
None of this required custom code in the ride-hailing company’s application. Retries handled the resend, and the interaction between idempotence and the in-flight limit handled the ordering, purely through configuration.
16Common Mistakes to Avoid
A short checklist of the errors that show up most often in real projects.
Raising in-flight requests without checking idempotence
A higher limit is only safe for ordering once idempotence is confirmed to be switched on.
Setting retries to 0 for data that actually matters
This quietly turns the producer into an at-most-once system, dropping messages on any transient hiccup.
Ignoring delivery.timeout.ms entirely
Leaving it at a default that does not match your application’s needs can mean waiting far too long, or not long enough, to learn that a message ultimately failed.
Assuming retries alone prevent duplicates
Retries by themselves can create duplicates when an acknowledgement is lost. It is idempotence, not retries, that removes them.
Copying an old max.in.flight.requests.per.connection value of 1 without reconsidering it
This forced setting made sense before idempotence existed. Left unexamined in a modern system, it quietly limits throughput for no remaining benefit.
17Frequently Asked Questions
Quick, direct answers to the questions that come up most often.
It limits how many produce requests can be sent to a specific broker on one connection before at least one of them must be acknowledged. It does not limit the total number of messages a producer can be working with across all brokers and partitions.
It is a sensible default for most workloads when idempotence is enabled. Extremely latency-sensitive or ordering-critical pipelines occasionally choose 1 for extra caution, and very high-throughput pipelines rarely need to go above 5, since the setting is capped there for idempotent producers in any case.
Retries themselves do not cause loss; they exist to prevent it. Data loss happens when retries are disabled, or when the delivery.timeout.ms budget runs out before a message can be confirmed, in both of which cases the producer reports a failure your application can act on.
Retries caps the number of attempts. Delivery.timeout.ms caps the total elapsed time across all attempts combined. In modern Kafka, the time-based limit is usually what actually determines when a producer gives up.
Yes, and this remains a perfectly valid, safe combination. It guarantees strict ordering the traditional way, at the cost of some throughput, and works whether or not idempotence is enabled.
Not when idempotence is enabled, since the broker’s sequence-number check removes duplicates regardless of how many requests are in flight. Without idempotence, a higher in-flight limit does increase the practical chance of both duplicates and reordering during retries.
No, it is purely a producer setting governing how many produce requests can be outstanding on a connection. Consumers have their own separate settings for how much data they fetch at once and how they manage parallel processing.
Yes. Even with a single partition, transient network failures can still trigger retries, and multiple in-flight requests can still arrive out of order relative to each other, so the same reasoning about idempotence and the in-flight limit still applies.
Individual messages that hit a retriable failure will take longer to be confirmed, since they must wait through the retry backoff and a second attempt. Overall producer throughput usually stays healthy as long as failures remain occasional, but a sustained high retry rate is a sign of a deeper problem worth investigating, as covered in the monitoring chapter.
18Retriable vs Non-Retriable Errors in Detail
Knowing which errors the producer will retry, and which it will not, helps you reason about failure behaviour with confidence.
Why the distinction exists
Not every failure means the same thing. Some failures are about timing and temporary conditions, and trying again a moment later is likely to succeed. Other failures are about the request itself being fundamentally wrong, and no amount of waiting will fix that. Kafka’s client library classifies errors into these two buckets so that it can behave sensibly without your application having to inspect every failure by hand, sparing every team from having to reinvent this classification logic on their own.
Leader not available
The partition’s leader is being elected or has just failed over. Trying again shortly usually succeeds once a new leader is ready.
Request timed out
No reply arrived within the expected time, often because of network congestion or a temporarily busy broker.
Not enough in-sync replicas
With acks set to all, the broker could not confirm enough replicas were caught up at that moment. This often clears quickly on its own.
Message too large
The batch exceeds the broker’s configured maximum message size. Retrying sends the exact same oversized batch and will fail identically every time.
Authorization failure
The producer does not have permission to write to the topic. No amount of retrying changes the permission outcome.
Serialization failure
The message could not be converted into bytes correctly, which is a problem in the data itself, not a passing network condition.
If a shop is temporarily closed for lunch, coming back in an hour makes sense. If you are trying to buy something the shop simply does not sell, coming back later changes nothing. Kafka’s retriable and non-retriable errors follow exactly this same everyday logic.
Even though the producer handles retriable errors automatically, it is good practice to attach a callback or listener that logs a final failure after all retries and the delivery timeout are exhausted, so a genuinely broken message does not disappear from view unnoticed.
19How Connections and Brokers Interact With These Settings
Max.in.flight.requests.per.connection is scoped more narrowly than many people first assume.
Per connection, not per producer
The limit applies separately to each connection a producer holds. A producer that is actively sending to three different broker leaders, because its messages span several partitions led by different brokers, can have up to the configured limit of in-flight requests on each of those three connections independently. The total number of in-flight requests across the whole producer can therefore be higher than the configured number itself, since that number is a per-connection ceiling, not a producer-wide one.
flowchart LR
P[Producer] -->|up to 5 in flight| B1[Broker 1, Partition Leader]
P -->|up to 5 in flight| B2[Broker 2, Partition Leader]
P -->|up to 5 in flight| B3[Broker 3, Partition Leader]
A delivery company might allow each driver to carry five parcels at once. A dispatch office with three drivers on the road at the same time is effectively moving fifteen parcels in total, even though no single driver ever exceeds their own limit of five.
Why this matters for cluster-wide throughput
Because the limit is per connection, a producer that spreads its writes across many partitions on many different brokers naturally achieves high overall throughput, even with a conservative in-flight limit like 5 on each individual connection. This is one reason Kafka’s designers felt comfortable keeping the default modest rather than raising it further, since real throughput usually comes from spreading load across brokers and partitions, not from stacking an enormous number of requests onto one connection.
This also explains why doubling a topic’s partition count, and letting those partitions spread across more brokers, often improves producer throughput more reliably than raising max.in.flight.requests.per.connection ever could. The in-flight setting optimises how busy one connection stays, while partition count and distribution decide how many connections, and therefore how much total in-flight capacity, a producer has available in the first place.
If a single, very high-volume partition seems to be limited by this setting, check whether the underlying issue is really about in-flight capacity, or whether that one partition has simply become a hot spot that would benefit more from being split into additional partitions.
Connection pooling and reconnection behaviour
Producers do not open and close a connection for every batch. Instead, a connection to a given broker is opened once and reused for as long as it stays healthy, which is part of why in-flight requests are tracked per connection rather than per batch or per request. If a connection is lost entirely, perhaps because a broker restarted, the producer reconnects and its in-flight tracking for that connection resets, with any batches that were genuinely unacknowledged at that moment becoming candidates for the normal retry process described earlier in this guide.
This reconnection behaviour is also why a brief broker restart, such as during a routine rolling upgrade of a Kafka cluster, is usually invisible to a well-configured producer. The connection drops, a small number of in-flight batches become candidates for retry, the producer reconnects automatically, and those batches are resent and correctly sequenced once the broker is back, all without any error surfacing to your application code.
20The Producer Send Buffer and Backpressure
Retries and in-flight limits do not act alone. They sit alongside a memory buffer that decides what happens when a producer cannot keep up.
Where messages wait before they are even batched
Before a message becomes part of a batch heading toward a broker, it sits briefly in an in-memory buffer inside the producer, sized by a setting called buffer.memory. This buffer absorbs short bursts of messages, smoothing out the difference between how fast your application produces data and how fast the network and broker can currently accept it.
When retries and a full in-flight queue combine during a period of broker trouble, this buffer can start filling up faster than it empties, since batches that would normally be cleared out by a quick acknowledgement are instead stuck waiting on retries. If the buffer fills completely, the producer applies backpressure, pausing your application’s calls to send until space frees up, rather than silently dropping data or growing memory without bound.
flowchart TD
A[Application calls send] --> B[Message enters the buffer]
B --> C[Batched and sent, becomes in flight]
C --> D{Acknowledged quickly?}
D -- Yes --> E[Batch leaves the buffer, space freed]
D -- No, retry needed --> F[Batch stays in buffer longer]
F --> G{Buffer full?}
G -- Yes --> H[New send calls block briefly, backpressure applied]
G -- No --> A
A coffee shop counter can hold only so many finished drinks waiting for pickup. If customers are collecting orders slowly, the counter fills up, and the baristas have to briefly pause making new drinks until some space clears. The buffer inside a Kafka producer behaves the same way, protecting the system from being overwhelmed rather than letting problems cascade invisibly.
A setting that controls how long the application waits
The max.block.ms setting controls how long a call to send is allowed to pause waiting for buffer space before it gives up and raises an exception to your application. This gives you a predictable upper bound on how long your own code can be blocked, even during a sustained period of broker trouble combined with heavy retrying.
Sizing the buffer with retries in mind
Teams that tune buffer.memory often think only about steady-state throughput, sizing it just large enough to hold a normal burst of messages. It is worth also thinking about a degraded scenario: if retries are keeping batches in flight for longer than usual because of a struggling broker, the buffer needs enough headroom to absorb that extra dwell time without immediately hitting its limit and blocking the application. A buffer sized only for the happy path can turn an ordinary transient failure into a much more visible slowdown across your whole application.
A practical sizing habit
When choosing buffer.memory, estimate your normal message volume over a few seconds, then add extra headroom equivalent to the time your delivery.timeout.ms and retry.backoff.ms settings could plausibly add during a rough patch. This keeps the buffer generous enough that retries do not immediately cascade into application-level blocking.
A sudden spike in retries, whether from a struggling broker or a network partition, is one of the most common real-world causes of buffer pressure. Understanding this chain, from a retried batch, to a slower-draining buffer, to eventual backpressure, helps you diagnose a sluggish producer correctly instead of guessing at the cause.
21Comparing Kafka’s Approach to Other Messaging Systems
Seeing how other systems handle retries and pipelining sharpens the picture of what makes Kafka’s approach distinctive.
| System | Retry approach | Ordering under retries |
|---|---|---|
| Kafka | Configurable automatic retries with a bounded time budget | Preserved via sequence numbers when idempotence is enabled |
| RabbitMQ | Publisher confirms tell the sender to decide whether to retry, usually handled by application code | Left to the application to manage if strict order matters |
| Amazon SQS | Visibility timeout and redrive policies govern retries, oriented around consumer-side processing rather than producer sends | Standard queues do not guarantee strict order; FIFO queues enforce it at some throughput cost |
| HTTP-based APIs | Typically left entirely to the calling application to implement retry logic | No built-in concept of ordering across separate requests |
Kafka’s retry and sequencing system is like a well-run kitchen where every ticket has a printed order number, so even if ticket three gets remade after ticket five is already plated, the expediter still sends dishes out to the dining room in the correct original order. Many simpler systems are more like a kitchen with no ticket numbers at all, trusting the cooks to remember the order themselves.
Teams evaluating messaging systems for order-sensitive workloads often find that Kafka’s built-in, broker-enforced ordering guarantee under retries requires noticeably less custom application code than systems that leave the same problem entirely to the client.
22Summary and Key Takeaways
Retries give a Kafka producer the resilience to survive transient network and broker failures automatically, and max.in.flight.requests.per.connection gives it the throughput to keep a connection busy by sending several requests at once. On their own, combining a high in-flight limit with retries opens a real risk of reordered messages within a partition. Idempotence closes that gap by stamping every batch with a sequence number the broker can use to keep the log correctly ordered and duplicate-free, which is why modern Kafka can safely default to five in-flight requests instead of the older, slower value of one.
Key Takeaways
- Retries — lets the producer automatically resend a batch after a transient, retriable failure.
- Delivery.timeout.ms — the real ceiling on total retry time, usually more decisive than the retries count itself.
- Max.in.flight.requests.per.connection — how many unacknowledged requests can travel to one broker at once, trading throughput against ordering risk.
- The core danger — a retried batch can arrive after later batches, scrambling order, when in-flight requests exceed one and idempotence is off.
- The modern fix — idempotence uses per-partition sequence numbers so the broker orders batches correctly no matter their arrival order.
- The safe default — enable.idempotence true, retries high, max.in.flight.requests.per.connection at 5.
- When to go stricter — set the in-flight limit to 1 only if idempotence cannot be enabled for some reason.
- Errors are not all equal — retriable errors like a leader election are worth retrying, but non-retriable ones like an oversized message never succeed no matter how many attempts are made.
- Watch the send buffer too — a sustained spike in retries can fill the producer’s memory buffer and trigger backpressure, so buffer.memory and max.block.ms deserve attention alongside retries itself.