Free preview

Message Extraction: Pull versus Push

In one line: the requirements say consumers "retrieve messages." That verb hides a real design decision — does the consumer ask, or does the broker send? The answer determines who controls the rate, and rate control is what keeps the system from collapsing.

The two models

PullPush
Who initiatesThe consumer, when it has capacityThe broker, when a message arrives
Rate is controlled byThe consumerThe producer, indirectly
Latency when idleUp to one poll intervalImmediate
Cost when idleWasted empty pollsNone
Behavior under overloadConsumer simply asks for less; backlog stays in the queueBroker overwhelms the consumer unless flow control is added
BatchingNatural — ask for 100 at a timeRequires explicit support
Consumer must beNothing special — can sit behind a firewall or NATAddressable by the broker

Pull, and the polling problem

The naive pull implementation is short polling: the consumer asks repeatedly on a fixed interval.

Consumer:  any messages?  -> no
           (wait 1s)
           any messages?  -> no
           (wait 1s)
           any messages?  -> yes, here are 3

This trades two bad properties against each other with one dial:

  • Short interval → low latency, but most polls return empty — wasted requests, wasted broker CPU, wasted money on a metered service.
  • Long interval → few wasted polls, but a message can sit up to one full interval before anyone looks at it.

Long polling removes the trade almost entirely. The consumer's request blocks at the broker until either a message arrives or a timeout expires:

Consumer:  any messages? (I'll wait up to 20s)
Broker:    ...holds the connection...
           message arrives -> returns immediately

Long polling gets you push latency with pull semantics

This is the key insight, and it is why the pull-versus-push debate is less balanced than it first appears.

With long polling, a message is delivered as soon as it arrives — the same latency as push, because the connection is already open and waiting. But the consumer still initiated the request, so it retains control: it asked for this many messages, now, because it had capacity.

You get push's responsiveness and pull's flow control. The only real cost is a held connection per waiting consumer, which is cheap on modern servers.

Amazon SQS supports long polling with a wait time of up to 20 seconds and explicitly recommends it over short polling for exactly these reasons.

Why push is harder than it looks

Push sounds strictly better — no polling, no wasted requests, immediate delivery. The problem shows up under load.

Push has no natural backpressure

If the broker sends messages as fast as they arrive, and the consumer processes more slowly, the mismatch has nowhere to go. The messages are already in flight. The consumer's buffers fill, then it starts dropping, timing out, or running out of memory.

The queue was supposed to be the buffer. Under push, part of the backlog silently relocates into the consumer — the one component least equipped to hold it, because it has no durability.

Fixing this means adding flow control: the consumer advertises how much it can take (a prefetch count or credit limit), and the broker respects it. RabbitMQ calls this the prefetch count, and tuning it is a standard operational chore precisely because getting it wrong causes exactly this failure.

But notice what flow control is: the consumer telling the broker how much to send. That is pull, re-implemented on top of push. The pull model gets it for free.

Push also forces the broker to track per-consumer state

Under pull, the broker is close to stateless with respect to consumers — it answers requests. Under push, it must know who is connected, what each one is capable of, what is in flight to whom, and what to do when one disappears mid-delivery.

That state is what makes push-based brokers harder to scale and harder to make highly available: a failing broker has in-flight commitments that a stateless one does not. It also complicates adding and removing consumers, since the broker's view of the fleet must stay current — the same server-list problem distributed caching needed a configuration service to solve.

What real systems chose

SystemModelNotes
Apache KafkaPullConsumers fetch batches at their own pace and track their own offset. The design document explicitly argues that pull avoids overwhelming consumers and makes batching natural
Amazon SQSPullReceiveMessage with long polling up to 20 seconds
RabbitMQPush (with pull available)Delivers to subscribed consumers, bounded by a prefetch count for flow control. A basic.get pull API exists but is discouraged for throughput

Batching is the underrated reason pull won

Flow control gets the attention, but batching may matter more for throughput.

A pull request naturally says "give me up to 500 messages." One round trip, one syscall, one disk read that returns a contiguous run of messages. Under push the broker decides, and it typically decides one message at a time unless batching is explicitly built in.

That is the same finding as Redis pipelining in distributed caching: when per-message work is small, per-message overhead dominates, and amortizing it across a batch is worth more than any per-message optimization. Kafka's throughput reputation rests heavily on this.

Choosing

ChooseWhen
Pull with long pollingThe default. High throughput, variable consumer speed, consumers that autoscale, or many consumers. You get flow control and batching for free
PushLow, predictable message rates where latency matters more than throughput, consumers are few and stable, and you are willing to configure prefetch limits
Push to an endpoint (webhooks, SQS→Lambda)The consumer is serverless or event-triggered and has no process sitting there to poll. The platform absorbs the flow-control problem for you

Key takeaway

Pull with long polling is the default for good reason: the consumer keeps control of the rate, batching is natural, and push's latency advantage largely disappears. Push needs explicit flow control — which is pull, reimplemented — and forces the broker to hold per-consumer state.

Interview signal by level

LevelWhat a strong answer sounds like
L4"Consumers read messages off the queue."
L5Names the models and picks one: "pull, with long polling so we're not burning requests on empty responses but still get near-immediate delivery."
Staff+Argues from backpressure: "pull, because the consumer controls the rate — under push an overload relocates the backlog into the consumer, which has no durability. Push needs prefetch limits, and a prefetch limit is just pull reimplemented. Pull also batches naturally, which matters more than it sounds: per-message overhead dominates when the work per message is small."

Next: the architecture.

Enjoying the preview?

Create a free account to unlock the rest of this course, the in-browser judge, and live AI mock interviews.

Sign up free to continue