Topic 04

Flow Control and Congestion

Communication

In October 1986, the link between Lawrence Berkeley Laboratory and the University of California at Berkeley, two sites 400 yards apart, fell from 32 kilobits a second to 40 bits a second. Nothing had broken. Every sender was retransmitting into full queues, and the network spent its capacity carrying copies that would be dropped again. Sending faster had made everyone slower, by a factor of almost a thousand.

A sliding window says how much a sender may have in flight, but not how large that window should be. Two things limit it, and they are different in kind. The receiver's buffer is a limit the receiver knows and can state. The network's queues are a limit nobody states, which the sender has to discover by probing. Keeping the two apart leads to additive increase and multiplicative decrease, the rule that has kept the second one from collapsing since 1988.

Flow Control: The Receiver's Limit

A receiver has a buffer of fixed size, and a slow consumer drains it slowly. With every acknowledgement the receiver advertises how much room it has left, and the sender never has more unacknowledged data in flight than that. When Lantern falls behind applying feed messages, its advertised room shrinks toward zero, and every branch slows down to match instead of overrunning it.

Flow control: the receiver states its room, the sender stays inside it
Senderwindow ≤ room
→
Networkdata out, acks back
→
Receiver"room for 64 KB"

The same idea runs through every bounded queue in software, under the name backpressure. A producer that finds a bounded queue full has to wait or give up, so the slow stage sets the pace of the fast one. The ring buffer in Chapter 5 is one such queue when its policy for a full buffer is to make the writer wait.

Congestion: The Network's Limit

Between sender and receiver sit routers whose queues are shared with everyone else's traffic. When packets arrive at a router faster than its outgoing link can send them, the queue grows, and when the queue is full the router drops. No router tells a sender what rate the path can carry. The sender has to infer it, from loss or from delay that rises as queues fill.

That inference is the hard part. A sender that sees no loss cannot tell whether the path has room to spare or is one packet from overflowing. The only way to find the limit is to go slightly over it, see the loss and back off.

Congestion Collapse

The 1986 collapse was a failure of that feedback. Senders answered loss by resending sooner and harder, and every resend arrived at a queue that was already full. The network filled with retransmissions of packets that would be dropped again, so useful throughput fell toward zero while every link stayed busy.

Van Jacobson and Michael Karels described the fix in "Congestion Avoidance and Control", presented in 1988. Senders were made to treat loss as a signal of congestion and to back off, and a new connection was made to start slowly and grow. The internet has run on that behaviour ever since.

AIMD

The rule is additive increase, multiplicative decrease. While things go well, the sender adds a small, fixed amount to its window every round trip. On a loss, it halves the window. The result is the sawtooth below: a slow climb toward what the path can carry, a sharp drop when it overflows and another climb.

One sender's window: slow start, then the AIMD sawtooth
what the path can carry020406001020304050round tripswindow (packets)slow start: doubles+1 per round trip, halve on loss

The simulation in the figure starts with slow start, which doubles the window every round trip so that a new connection finds its rate in a few round trips instead of minutes. It overshoots the path's capacity of 40 packets at round trip 6 and loses. From then on the window grows by one packet per round trip and halves at each loss, at round trips 16 and 38, hovering around the capacity line.

The deeper reason for this particular pair came from Dah-Ming Chiu and Raj Jain in 1989. Two senders sharing a link both add the same amount when they increase, which keeps the gap between them the same, and both halve when they decrease, which halves the gap. Every cycle shrinks the difference, so the senders converge on equal shares whatever their starting point. Additive decrease would keep the gap, and multiplicative increase would widen it.

Two senders, one link: AIMD drives them to equal shares
full linkequal sharesflow A's rateflow B'sratestart: A has 10 times B

The figure plots the two senders' rates against each other. The pair starts with flow A at ten times flow B's rate. Each increase moves it diagonally up toward the full-link line, and each halving moves it straight toward the origin, which shrinks the gap. After a few cycles the path is running along the equal-shares diagonal.

Queues That Are Too Big

A buffer that never drops hides congestion as delay. A home router with seconds of buffering keeps a large upload flowing at full speed while every video call on the same line lags by a second, because each call's packets wait behind the upload's. Jim Gettys named this bufferbloat around 2010. Loss-based senders fill any buffer they are given, since they increase until something drops, so a bigger buffer only makes the delay longer.

Newer congestion control algorithms therefore watch delay as well as loss. Google's BBR, published in 2016, estimates the path's bandwidth and round trip directly and tries to keep queues short. The algorithms themselves, and how to choose between them, belong to Networking Deep Dive's topic on congestion control.

Congestion Control in Application Code

A client that retries every failure immediately is a sender without congestion control. When a dependency slows down, its retries multiply the load at the worst moment, and a thousand clients doing the same keep the dependency down long after the original problem is gone. Exponential backoff with random jitter, the practice Backend Deep Dive teaches in "Retries, Backoff and Jitter", is AIMD's cousin in application code.

An unbounded in-memory queue in a service is bufferbloat in software. Under sustained overload it grows, every request waits longer, and eventually the process runs out of memory. A bounded queue that rejects work when full is flow control, and backoff on failure is congestion control. Both replace a slow, invisible failure with a fast, visible "no".

Flow Control vs Congestion Control

Flow control protects the receiver. The receiver states its free buffer and the sender stays within it, so the limit is known exactly. Enlarge it when a fast path is limited by a small receive buffer.

Congestion control protects the network. Nobody states the network's capacity, so the sender probes upward and backs off on loss or delay. A connection runs at whichever limit is lower, and enlarging the receiver's buffer does nothing for a congested path.

Misconceptions
  • "Sending faster gets the data there sooner." Beyond the bottleneck's rate, extra sending only fills queues, adds delay and causes drops that must be resent. The fastest sender is the one that matches the bottleneck.
  • "Flow control and congestion control are the same thing." One is the receiver's stated limit, the other the sender's estimate of the network. A fast receiver on a congested path gains nothing from a bigger buffer.
  • "Bigger buffers mean fewer drops, so they are better." They trade drops for delay. A buffer holding seconds of traffic adds seconds of latency to every flow through it, while throughput stays the same.
  • "An unbounded queue protects my service from bursts." It turns overload into steadily rising latency and finally an out-of-memory crash. A bounded queue that rejects early keeps the work it accepts fast.
  • "Retrying immediately is the most responsive choice." Immediate retries from many clients synchronize into waves that keep an overloaded dependency down. Backoff with randomness lets it recover.
Why It Matters
  • Bound every queue and push back when it is full. A rejection now is cheaper than a timeout later.
  • Back off multiplicatively on failure and recover gradually. The shape that keeps the internet up keeps a struggling dependency up.
  • Add randomness to every retry schedule, so that clients do not synchronize. Clients on identical schedules retry in lockstep and arrive as one wave.
  • Watch queueing delay as well as loss and error rates. A queue that never drops can still be the problem.
RelatedBackpressure flow control between the stages of a pipelineRate limiting a fixed ceiling set in advance, not a probe (Backend Deep Dive)Circuit breakers stop sending entirely when the other side is failing, in Backend Deep Dive's "Circuit Breakers and Bulkheads"

Knowledge Check

In the 1986 collapse, throughput between two nearby sites fell by a factor of almost a thousand with no hardware fault. What caused it?

  • A software bug in one router that silently dropped every other packet
  • Senders resending into full queues, so links carried doomed copies
  • The receivers' buffers were far too small to hold the arriving traffic
  • The two sites were too far apart for a 32-kilobit link to reach

Two senders share a link, one at ten times the other's rate. Why does AIMD bring them to equal shares when additive decrease would not?

  • Additive increase gives the slower sender a larger step each time
  • Multiplicative decrease cuts only the faster sender's window
  • Halving both rates halves the gap between them, and equal increases keep it
  • A router tells the faster sender to slow down until they are equal

A home router's buffer is doubled. The link is saturated by one large upload. What happens to throughput and to latency?

  • Throughput doubles and latency halves, since fewer packets are lost
  • Both stay the same, since the extra buffer is never used
  • Throughput halves and latency stays the same as before
  • Throughput stays the same, and every packet waits longer in the queue

A fast server sends a large file to a fast receiver, but a congested link in between limits the transfer. Which change helps?

  • Enlarging the receiver's buffer and its advertised window
  • Nothing on the two ends; the congested path sets the rate
  • Turning off congestion control so the sender ignores loss
  • Retransmitting lost packets immediately, without a timeout

A service puts incoming jobs in an unbounded in-memory queue. Load stays above what it can process for an hour. What happens?

  • The queue absorbs the excess, and latency stays about the same
  • The service processes jobs faster as the queue grows longer
  • Latency climbs steadily for the whole hour until the process runs out of memory
  • The operating system slows the clients down to match automatically

You got correct