Head-of-Line Blocking Risk in OpenTelemetry Export Pipelines

Head-of-Line Blocking Risk in OpenTelemetry Export Pipelines

Why one slow delivery can freeze everything waiting behind it, how that quietly wrecks your traces, metrics and logs, and how to design pipelines that keep moving even when a destination slows down.

Picture a busy Friday evening. Your online shop is running, orders are flowing, and customers are happy. But the monitoring dashboard your team depends on has gone strangely quiet. The graphs are flat, the traces are missing, and nobody can tell whether the shop is healthy or on fire. The shop itself is fine. The problem is the delivery system that carries information about the shop to the dashboard. Somewhere along that route, one slow delivery got stuck, and every delivery behind it had to wait.

This is called head-of-line blocking, and it is one of the most common hidden failures in telemetry systems built with OpenTelemetry. It does not crash your application. It does not throw a loud error. It simply makes your monitoring go blind at the exact moment you need it most. In this guide you will learn what it is, where it hides, how to measure it with simple arithmetic, and how real teams design pipelines that avoid it.

You do not need any background in monitoring to follow along. Every new word is explained the first time it appears, and every big idea comes with an everyday comparison.

1Telemetry and OpenTelemetry in Plain English

Before we talk about blocking, we need to know what is being blocked. This chapter builds the vocabulary for everything that follows.

When you build a website, a mobile app, or any online service, it does not run on one computer sitting on your desk. It runs on many computers, often hundreds or thousands, spread across data centers. You cannot stand next to each machine and watch what it is doing. So the software has to describe its own behavior by sending out a steady stream of small notes. Those notes are called telemetry. The word comes from Greek roots meaning “measuring from far away.”

Everyday Analogy

Think of a hospital. Each patient has a monitor beside the bed that tracks heart rate, oxygen and temperature. Those monitors do not help much if the readings stay in the room. A cable carries every reading to a central nursing station, where one nurse can watch fifty patients at once. Telemetry is the set of readings. The export pipeline is the cable. If the cable jams, the nurse sees a blank screen even though the patients may be perfectly fine, or in serious trouble.

Telemetry comes in three main flavors, which OpenTelemetry calls signals.

Signal

Traces and Spans

A trace is the full story of one request, such as “customer taps Buy Now.” It is made of spans, and each span is one step in that story, like “check stock” or “charge card,” with a start time and a duration.

Signal

Metrics

Numbers measured over time, such as requests per second, memory used, or errors per minute. They are small, regular and good for graphs and alarms.

Signal

Logs

Text records of things that happened, such as “payment declined for order 4821.” They are the diary of the program.

Component

The SDK

SDK stands for software development kit. It is the library that lives inside your application, collects telemetry, groups it, and sends it out.

Component

The Collector

A separate program that receives telemetry from many applications, cleans it up, and forwards it to storage. Think of it as a sorting office.

Protocol

OTLP

The OpenTelemetry Protocol, the agreed language used to carry telemetry from one place to another over the network.

OpenTelemetry, usually shortened to OTel, is a free, open standard and a collection of tools for creating and moving telemetry in the same way no matter which programming language or monitoring product you use. It is a project of the Cloud Native Computing Foundation, the same non-profit home that hosts Kubernetes. Because it is vendor neutral, you can write your instrumentation once and change your monitoring product later without rewriting your code.

The OTel API is the small set of calls your code makes to say “a step started” or “a number changed.” The SDK is the engine behind that API. The Collector is an optional but very common middle stop. And the act of sending telemetry out of the application toward another place is called exporting. Exporting is where our story takes place.

T
Remember Three Words

Produce, process, export. Your code produces telemetry, the SDK processes it into groups, and an exporter ships it out. Head-of-line blocking happens between the processing step and the shipping step.

2The Export Pipeline: A Journey From Code to Dashboard

An export pipeline is a chain of stations. A problem at any station can slow every station before it.

Telemetry does not teleport. Each span, metric point or log line travels through a series of stations before a human can look at it on a screen. We call that whole chain an export pipeline. Let us follow one span, the record of a single step in a customer request, from birth to dashboard.

flowchart LR
A["Your App Code"] --> B["Tracer in the SDK"]
B --> C["Batch Processor Queue"]
C --> D["Exporter Worker"]
D --> E["Network Connection"]
E --> F["OpenTelemetry Collector"]
F --> G["Observability Backend"]
G --> H["Dashboard and Alerts"]
    
FIG 1. The journey of one span, from your code to a human looking at a dashboard

Here is what each station does, in simple terms.

  • Your app code does its normal work, like checking a shopping cart, and a span is created for that step.
  • The tracer is the part of the SDK that fills in the span with timing and labels, called attributes. When the step ends, the span is handed onward.
  • The batch processor is a holding area. It collects finished spans into a waiting line, called a queue, so they can be sent in groups instead of one at a time.
  • The exporter takes a group from the queue and sends it across the network.
  • The Collector receives groups from many applications, may filter or enrich them, and passes them on.
  • The backend is the storage and search system, such as Jaeger, Grafana Tempo, or a commercial monitoring product. It is where the dashboards read from.
Everyday Analogy

Imagine a small company’s mailroom. Employees drop finished letters into an outbox tray. A single courier comes by, scoops up a bag of letters, and walks them to the post office. Only after the courier returns can the next bag go out. The outbox tray is the queue, the courier is the exporter, and the post office is the Collector. If the post office has a long line one afternoon and the courier is stuck waiting, the tray fills up and new letters have nowhere to go. Keep this courier in mind. We will come back to him often.

Why batching exists at all

You might ask why the SDK does not simply send each span the moment it finishes. The answer is cost. Every network trip has fixed overhead: opening a connection, wrapping the data, waiting for a reply. If your service creates five thousand spans per second, five thousand tiny trips would waste huge effort. Grouping spans into batches of a few hundred makes each trip worth the overhead. This is a good idea, and it is also exactly what creates the conditions for head-of-line blocking, because the batch becomes the unit of waiting.

The pipeline repeats for every signal

Traces, metrics and logs each have their own path. Spans go through a batch span processor. Logs go through a batch log processor. Metrics usually go through a reader that wakes up on a timer, collects current values, and exports them. These paths are separate from each other inside the SDK, which turns out to be an important protection that we will use later.

3What Is Head-of-Line Blocking?

A plain definition, three everyday pictures, and the two ingredients that always cause it.

In any waiting line, the person at the very front is the head of the line. If that person takes a very long time, everyone behind them waits too, even if those people only need a few seconds. Head-of-line blocking, often shortened to HOL blocking, is exactly this effect in computer systems: one slow or stuck item at the front of a queue holds up every item queued behind it.

Everyday Analogy

You join the grocery checkout line holding a single carton of milk. The customer in front has two hundred items and a coupon that will not scan. Your milk takes three seconds to ring up, but you wait fifteen minutes. Nothing is wrong with you or your milk. The line has only one counter, and the slow customer is in front. That is head-of-line blocking.

Two ingredients are always present. First, the line is strictly ordered: items leave in the order they arrived, so a slow item cannot be skipped. Second, there is a single shared path, such as one counter or one courier, so nobody else can take a faster route. Remove either ingredient and the problem softens. Add more counters and the milk customer can move. Let customers with few items use an express lane and the milk customer escapes entirely.

A queue is only as fast as the slowest thing standing at its front.

Where engineers already know it

Head-of-line blocking is not new, and it is not unique to OpenTelemetry. Older versions of the web protocol, HTTP/1.1, could handle only one request at a time on each connection, so a slow download blocked the small ones behind it. Network switches can suffer it when packets for a busy destination hold up packets headed elsewhere. Even TCP, the reliable delivery layer under most internet traffic, delivers bytes strictly in order, so one lost packet makes all the later bytes wait until it is resent. Engineers invented newer protocols and designs such as HTTP/2, parallel connections and express lanes largely to fight this single problem.

i
Do Not Confuse These

Head-of-line blocking is about waiting behind a slow item. It is different from a destination simply being down, though the two often appear together. A dead destination is one cause. A slow but alive destination is often worse, because nothing fails fast enough to give you a clean error.

Why telemetry is especially vulnerable

Telemetry pipelines are built to be cheap and invisible to the application. They run on the same machines as the work that pays the bills. They buffer data in memory. They are usually designed with one worker per exporter. All of those good design choices produce the two ingredients above, an ordered queue and a shared path. And since telemetry is written when everything is busy and healthy, the pipeline faces its biggest load at the moments when something unusual is happening and people most want the data.

4Where Head-of-Line Blocking Hides in OpenTelemetry

Eight common hiding places, from inside your application all the way to the network.

The risk is not in one single spot. It shows up at many places along the pipeline, and each one has a different cause and a different fix. Here is a map of the eight most common ones. Later chapters take each in turn.

SDK

One Export at a Time

Most SDK batch processors send one batch, wait for the answer, then send the next. A slow answer holds back everything in the queue.

SDK

Synchronous Processors

A simple processor that exports each span as it ends can make your actual request thread wait on the network. The block reaches your users.

SDK

Slow Metric Cycles

A periodic reader exports on a timer. If one export runs longer than the interval, later readings are delayed or skipped.

Collector

Fan-out to Many Exporters

When one pipeline feeds several destinations without queues, they may be called one after another, and the slowest one sets the pace for all.

Collector

Retries That Hold a Worker

A worker that keeps retrying a failing request cannot serve anyone else until it succeeds or gives up.

Network

Pinned Connections

A long-lived connection that a load balancer glued to one Collector keeps sending to that one machine, even when it is the slow one.

Data

Giant or Poison Batches

One enormous or malformed batch can take far longer than normal ones and hold the courier hostage.

Policy

Mixed-Value Queues

Precious error traces share a line with worthless health-check spans, so when the line jams, the valuable ones are lost along with the junk.

!
Watch Out

Several of these can stack. A slow backend makes the Collector retry, retries hold workers, held workers fill the queue, and a full queue pushes errors back to the SDK, whose own single courier is now stuck waiting. One small slowdown can travel the whole chain in under a minute.

5Inside the SDK Batch Processor

The batch processor is the most common place where head-of-line blocking starts. Let us open it up.

The batch span processor sits between the code that finishes a span and the exporter that ships it. It has three jobs: accept spans quickly so the application is never slowed down, group them into batches, and hand each batch to the exporter. Its design follows the OpenTelemetry specification, so the behavior is very similar in Java, Go, Python, JavaScript and other languages.

flowchart TB
S["A span ends in your code"] --> Q{"Is the queue full?"}
Q -- "Yes" --> X["Span is dropped and counted"]
Q -- "No" --> W["Span waits in the queue"]
W --> T{"Batch size reached or timer fired?"}
T -- "No" --> W
T -- "Yes" --> E["Exporter sends one batch"]
E --> R{"Did the export finish?"}
R -- "Yes" --> N["Take the next batch"]
R -- "Slow or stuck" --> H["Everything behind it waits"]
H --> Q
    
FIG 2. Decision flow inside the batch span processor

The four settings that matter

Four settings control the whole behavior. Their commonly used default values are shown below. Defaults can differ slightly between languages and versions, so always check the documentation of the SDK you actually run.

SettingTypical DefaultWhat It Controls
Maximum queue size2,048 spansHow many finished spans can wait. When full, new spans are dropped.
Scheduled delay5 secondsHow often the processor wakes up and exports, even if the batch is small.
Maximum export batch size512 spansThe most spans placed into one network trip.
Export timeout30 secondsHow long one export may run before it is cancelled.
2,048
spans the waiting room holds by default
512
spans carried in one trip
30 s
how long one stuck trip can last

The sentence that explains the risk

The specification asks that a single exporter instance is not asked to run overlapping exports. In plain words: one courier, one bag at a time. This is a sensible rule. It keeps memory use predictable and avoids flooding the receiver. But look at the numbers in the table. If one trip hangs for the full 30 seconds, the queue has to absorb 30 seconds of new spans while it waits. And the queue only holds 2,048.

Everyday Analogy

The outbox tray on the mailroom shelf holds 2,048 letters. The courier carries 512 at a time. If the post office makes him wait half an hour, letters keep arriving all that time. After the tray is full, new letters are simply thrown away, because nobody is allowed to put a letter on the floor.

T
Production Example

The Jaeger project, originally created at Uber, now builds its second major version on top of the OpenTelemetry Collector. That means the same exporter and queue ideas in this guide apply to a very widely used tracing system, not only to custom setups.

6The Math of a Stuck Pipe

Simple division tells you how fast your pipeline can run and how quickly it fills when it slows down.

You do not need advanced mathematics to reason about head-of-line blocking. Two small formulas do almost all of the work.

Formula one: the speed limit of one courier

If the exporter sends one batch at a time, the most it can ever ship is the batch size divided by the time one trip takes. With the default batch of 512 spans, here is what different trip times allow.

Time for one exportMaximum shipping speedWhat it feels like
50 millisecondsabout 10,240 spans per secondHealthy, local Collector
100 millisecondsabout 5,120 spans per secondHealthy, nearby Collector
250 millisecondsabout 2,048 spans per secondCross-region, or a mildly busy receiver
1 secondabout 512 spans per secondA struggling receiver
5 secondsabout 102 spans per secondSerious trouble
30 secondsabout 17 spans per secondNearly stopped

Notice how brutally the speed limit falls. A trip that is merely ten times slower than normal makes the pipeline ten times slower. A service creating 3,000 spans per second runs happily at 100 milliseconds per trip, and drowns at one second per trip.

Formula two: how fast the waiting room fills

When shipping falls below the arrival rate, the difference piles up in the queue. In the worst case, when the courier is completely stuck, the time to fill a 2,048-span queue is simply the queue size divided by the arrival rate.

Spans arriving per secondTime to fill a 2,048 queue if fully stuck
500about 4.1 seconds
2,000about 1.0 second
5,000about 0.4 seconds
20,000about 0.1 seconds

This is a version of a famous result called Little’s Law: the number of items waiting equals the arrival rate multiplied by how long each item waits. It is a perfect fit for queues of any kind. If items arrive at 2,000 per second and each waits for one second, then about 2,000 are always sitting in line.

Two nasty surprises

Surprise 1: You lose the newest data, not the oldest

When the queue is full, the processor drops the span that just arrived. The 2,048 spans already inside, which are older, are kept. So after an outage you receive a neat block of stale data from the moment the trouble began, and nothing from the moments right after. Many people expect the opposite.

Surprise 2: Recovery can take far longer than the failure

Suppose the Collector heals. The courier now has a backlog to clear, but new spans keep arriving while he works. If the arrival rate is close to his maximum speed, the backlog barely shrinks. The system can stay clogged long after the original cause is fixed. Engineers call this a self-sustaining overload.

!
Design Rule

Your normal arrival rate should sit well below the shipping speed limit during a bad day, not a good day. If a slow day cuts the limit below your arrival rate, loss is guaranteed.

7Life of a Span During a Slow Backend

Let us replay an incident in slow motion, from the first hiccup to the long recovery.

Understanding the lifecycle of data during trouble is the best way to spot the problem early. The picture below shows the conversation between four actors: your application, the SDK queue, the exporter, and the Collector.

sequenceDiagram
participant App as Application
participant Q as SDK Queue
participant Ex as Exporter
participant Col as Collector
App->>Q: Spans arrive
Q->>Ex: Hand over batch 1
Ex->>Col: Send batch 1
Note over Col: The Collector becomes slow
App->>Q: More spans arrive
Note over Q: The queue keeps growing
App-xQ: New spans dropped when full
Col-->>Ex: Reply arrives after a long delay
Ex->>Q: Ready for batch 2
    
FIG 3. A slow reply from the Collector backs up the SDK queue

The six stages of a blocked pipeline

1

Calm

Exports finish in about 100 milliseconds. The queue is almost empty. Dashboards are fresh by a few seconds.

2

First slowdown

The Collector or backend starts replying in one or two seconds. Nobody notices yet. The queue starts to hold a few hundred spans.

3

The queue builds

Arrivals exceed the shipping speed from Chapter 6. The queue climbs toward its limit. Dashboards lag by tens of seconds, which looks like a mild delay.

4

The queue is full

New spans are dropped. Nothing visible happens in your application. Graphs look patchy, and traces appear with missing steps.

5

Timeout or recovery

Either the stuck export finishes, or the 30-second timeout cancels it. The courier takes the next batch of old data.

6

The long tail

The backlog drains while new data keeps coming. If the system runs near its limit, gaps persist for minutes after the root cause is gone.

Why you often do not notice

Telemetry code is deliberately designed to never harm your application. When the queue is full, the SDK does not throw an error at your business logic. It quietly discards the span, usually with only an occasional internal warning. That is the right choice for protecting users, but it means the first sign of trouble is often a human saying “the graph looks weird.” This is why the monitoring chapter later in this guide insists that you also watch the health of the pipeline itself.

i
The Synchronous Exception

A simple span processor exists that exports each span the moment it ends, on the same thread that did the work. It is meant for local debugging. In production it turns a slow Collector directly into slow user requests, because your code waits for the network before it can continue. That is head-of-line blocking at its worst, since the line is your real customers. Use the batching processor for real systems.

8The Collector Side: Receivers, Queues and Fan-out

The Collector has its own queues and workers, and its own ways of blocking.

The OpenTelemetry Collector is a standalone program. Inside it, a pipeline is built from three kinds of parts. Receivers accept incoming data. Processors change or filter it. Exporters send it onward. Many pipelines can run side by side in one Collector, usually one per signal type.

flowchart LR
R1["OTLP Receiver"] --> M["Memory Limiter"]
M --> B["Batch Processor"]
B --> F["Fan-out to exporters"]
F --> Q1["Queue for Backend A"]
Q1 --> C1["Worker threads A"]
C1 --> D1["Backend A"]
F --> Q2["Queue for Backend B"]
Q2 --> C2["Worker threads B"]
C2 --> D2["Backend B"]
    
FIG 4. A Collector pipeline with two destinations, each with its own queue and workers

The exporter’s built-in helper

Most Collector exporters share a common helper that adds three features. A timeout limits how long a single send may take. A sending queue holds requests while workers are busy. And a retry mechanism re-sends failed requests after a pause. The sending queue has a number of consumers, which are worker threads that pull requests from the queue and send them. Typical defaults, at the time of writing, are a few seconds of timeout, around ten consumers, and a queue of roughly a thousand requests. Values change between versions, so confirm them in the documentation for your release.

Everyday Analogy

Think of an airport with ten check-in counters and one long line. Passengers wait in the single line (the queue) and step up to whichever counter opens. One passenger with a complicated visa problem ties up one counter, but nine remain open. That is why several consumers matter: they turn a one-lane road into a ten-lane road. Trouble comes when all ten counters meet complicated passengers at once.

Fan-out: when one pipeline feeds two places

If you configure a pipeline with two exporters, such as one sending to your main backend and one to cheap archive storage, the Collector hands the data to both. When the exporters have sending queues, the handoff is nearly instant because each just drops the request into its own queue. When a queue is switched off, the call goes straight to the network, and the Collector walks through the exporters one after another. Now a slow archive store makes the main backend wait, and the receiver waits too, because it cannot answer the application until all exporters have replied.

!
Duplicate Data Trap

If one exporter fails and the Collector reports an error to the sender, the sender may retry the whole request. The healthy exporter then receives the same data a second time. That produces duplicate spans and inflated counts. Giving each exporter its own queue, and treating them as separate lanes, reduces this risk.

The memory limiter and the batch processor

The memory limiter is a safety valve. It watches how much memory the Collector uses, and when usage gets close to a limit it starts refusing new data instead of running out of memory and crashing. It is normally placed first in the pipeline. The batch processor groups many small incoming requests into bigger outgoing ones, commonly waiting a fraction of a second or until a few thousand items are collected. Because it hands data off quickly, it decouples receiving from sending. Some newer Collector versions are moving this batching into the exporter’s own queue, so watch release notes if you plan upgrades.

Backpressure: the pain travels backward

When the queue in front of a slow backend is full, the exporter refuses the next request. That refusal travels backward through the pipeline to the receiver, which tells the application’s SDK “I am busy, try later.” This signal is called backpressure. It is healthy and useful, because it stops the Collector from blowing up its memory. But it also means a slow backend at the far end of the chain can ripple all the way back to thousands of SDKs, each with its own courier now waiting.

9Retries: When Helping Makes It Worse

Trying again sounds harmless. In a crowded system it can turn a small problem into a large one.

Networks fail in small ways all the time. A packet is lost, a server restarts, a connection resets. The standard answer is to retry: send the same request again after a short pause. OTLP defines which failures are worth retrying, and well-built exporters retry with exponential backoff, meaning the pause gets longer after each failure, such as one second, then two, then four, and so on.

Which failures are retried

The OTLP specification separates failures into two groups. Temporary ones may succeed on a second try. Permanent ones never will, so retrying them wastes effort and blocks workers.

Kind of failureExamplesRetry?
Temporary, over gRPCUnavailable, deadline exceeded, aborted, cancelled, data lossYes, with backoff
Temporary, over HTTPStatus 429, 502, 503, 504Yes, with backoff
Overload signalgRPC resource exhausted, normally only when the server also says when to come backOnly with that hint
PermanentBad request, malformed data, not authorizedNo, drop and count it

When the server includes a hint about how long to wait, such as a standard retry delay in the response, a good client follows that hint instead of guessing. Spreading retries out in time with a little randomness, called jitter, matters just as much, as we will see next.

How retries create head-of-line blocking

In the Collector, a consumer that is retrying a request stays busy with it. It keeps waiting and re-sending, with growing pauses, until the request succeeds, is judged permanently failed, or hits the maximum retry time, which by default can be several minutes. Meanwhile it takes no new work. If a backend is flaky and every consumer gets stuck retrying, all counters at the airport are occupied by passengers who cannot be served. The queue behind them grows until it is full, and then backpressure starts flowing toward the applications.

Everyday Analogy

Imagine a power cut in a big city. When the power returns, thousands of people hit redial on the same phone number at the same instant, and the phone exchange collapses under the sudden flood. If everybody instead waits a random few seconds before redialing, the calls spread out and most go through. Retrying without jitter makes every client behave like the whole city redialing together, and engineers call that a retry storm or thundering herd.

The retry budget idea

Retries multiply load. If a service normally receives 1,000 requests per second and every failed request is retried three times, a failure that hits everyone can briefly demand up to 4,000 requests per second from a service that is already struggling. Mature teams limit total retry time, cap the number of retries, and prefer to drop old low-value data rather than keep workers busy with it. The goal is to remember that a retry is borrowed capacity, and the loan must be repaid by someone.

T
Practical Rule

Set the total retry time to roughly how long the data stays useful, not how long you wish to preserve it. A trace that is ten minutes old may still be good for a postmortem, but a metric used for a live alarm is useless after one minute.

10The Network Layer: Connections, Streams and Load Balancers

Even if your queues are perfect, the road underneath can create its own blocking.

OTLP can travel over two main transports. OTLP over gRPC uses HTTP/2, a modern protocol that can carry many conversations, called streams, at once over one connection. OTLP over HTTP sends ordinary web requests, using HTTP/1.1 or HTTP/2. The choice affects how blocking appears.

LayerHow blocking can happenRelief
HTTP/1.1One request at a time per connection. A slow request holds the whole connection.Several connections, or move to HTTP/2
HTTP/2 (used by gRPC)Many streams share one connection, so a slow stream does not block the others at this level.Built in, limited by the maximum streams allowed
TCP underneathOne lost packet makes all later bytes on that connection wait for a resend, whichever stream they belong to.Healthy networks, shorter paths, nearby Collectors
Load balancerLong-lived connections stay glued to one Collector, even if it is the slow one.gRPC-aware balancing, connection age limits
Everyday Analogy

HTTP/1.1 is a single-lane country road, where one slow tractor stops everyone. HTTP/2 is a multi-lane highway, so a slow truck in one lane lets cars pass in the others. TCP is the road surface itself. If a pothole damages the whole highway at one spot, every lane must slow down until the repair is made. A multi-lane highway helps a lot, but it cannot fix a broken road.

The sticky connection problem

gRPC connections are designed to live a long time. A typical network load balancer that works at the connection level picks one Collector when the connection opens and then sends everything on it to that same Collector for hours. If your fleet has fifty applications, and a handful happen to land on one Collector replica, that replica becomes busy while others sit idle. Worse, if that replica is the slow one, all those applications experience head-of-line blocking while the other replicas are healthy. The fixes are to use a balancer that understands requests rather than just connections, and to set a maximum connection age on the Collector so connections are periodically closed and re-opened, letting traffic spread out again.

Size matters

Bigger batches are more efficient, but they have two costs. They take longer to serialize, send and process, so each trip holds the courier longer. And receivers set a maximum message size. The Collector’s gRPC receiver commonly allows around four megabytes by default. A batch larger than the limit is refused, and because refusing is permanent for that exact batch, retrying it would never help. Keep batch size modest enough that a normal trip finishes in tens or hundreds of milliseconds. Turning on compression such as gzip reduces bytes on the wire at the price of some processor time, which is usually a good trade on busy networks.

i
Distance Matters

A trip that takes 100 milliseconds to a Collector in the same data center may take 250 milliseconds or more across regions. From Chapter 6, that alone cuts the maximum shipping speed by more than half. Sending to a nearby local Collector first, and letting that Collector make the long journey, is a very common design for this reason.

11Poison Pills, Noisy Neighbors and Security

Not all blocking comes from slow networks. Sometimes the data itself, or the people sending it, are the cause.

The poison pill

A poison pill is one piece of data that the system cannot handle, which causes repeated failure. Imagine one batch that contains a malformed value the backend rejects every time. If the system treats the rejection as temporary, it retries forever, holding a worker each time. The same happens with a batch that is simply too big for the receiver. Good pipelines classify errors correctly, drop permanent failures quickly, and count them so someone can investigate.

Everyday Analogy

At airport security, one traveler’s bag sets off the alarm, and the officer must hand-check every item. The lane stops while thirty people wait behind, each with a harmless carry-on. A good airport pulls that traveler to a side table so the line keeps moving. A good pipeline does the same with data that keeps failing.

Heavy items that slow the line

Some metrics batches are heavy because of cardinality, a word that means the number of different combinations of labels. If a metric is labeled with a user identifier, and you have ten million users, you may create ten million separate series. A batch that contains them takes far longer to process than a batch of ordinary metrics, and everything queued behind it waits. Keeping labels to a small set of values is not only a storage-cost concern, it is a pipeline-health concern.

Valuable and worthless data in one line

Health checks and heartbeat endpoints can create thousands of nearly identical spans every minute. In a shared queue they stand in line with the rare error traces that you actually need during an incident. When the line is full, both are dropped equally. A filtering step that removes or samples the low-value spans early gives the valuable ones room to survive.

Noisy neighbors in shared Collectors

When many teams or customers share one Collector tier, a single bug in one service, such as a loop that emits a log line every microsecond, can flood the shared queue. The other teams, who did nothing wrong, now see delays and drops. This is the noisy neighbor problem. Per-team pipelines, separate queues, and rate limits turn a shared single lane into separate lanes, so one team’s flood stays in its own lane.

The security angle

A telemetry endpoint is a door into your infrastructure that accepts data from many sources. An attacker, or just a misconfigured client, can push huge volumes or oversized messages to make your queues fill, which is a form of denial of service that uses head-of-line blocking as its weapon. Basic hygiene helps: encrypt connections with TLS, require authentication for anything that is reachable beyond a trusted network, set message size and rate limits, and keep the memory limiter on. Do not expose a raw Collector receiver directly to the public internet.

!
Do Not Forget

Blocking can be accidental or hostile. The same defenses, namely separation, limits and early refusal, protect you from both.

12Seeing the Problem: Metrics, Logs and Alerts

You cannot fix what you cannot see. Here is what to watch, and what each signal tells you.

The pipeline carries your monitoring data, so it needs monitoring of its own. This is sometimes called monitoring the monitor. Three sources help: the SDK’s own diagnostic output, the Collector’s internal metrics, and the backend’s ingestion statistics.

What to watchWhereWhat a bad value means
Queue size compared with capacityCollector internal metrics, and newer SDK self-metricsClimbing toward full means shipping is slower than arriving. This is the earliest warning.
Failed sendsCollector exporter failure countersThe destination is rejecting or timing out. Check the reason before raising limits.
Failed enqueuesCollector exporter enqueue failure countersThe queue was full and data was lost at this exact point.
Refused items at the receiverCollector receiver refusal countersBackpressure is reaching your applications.
Refused by memory limiterCollector processor refusal countersThe Collector is protecting itself from memory exhaustion.
Dropped spans in the SDKSDK diagnostic logs and processor metricsThe SDK queue overflowed in the application.
Export durationSDK exporter metrics, and traces of the CollectorRising time per trip lowers the speed limit from Chapter 6.
Data freshness at the backendBackend dashboardsGrowing lag between event time and arrival time reveals hidden blocking.

The Collector exposes its own metrics, often on a local port in a format that Prometheus can read, and the names usually start with a common prefix and mention the exporter, receiver or processor involved. Exact names and suffixes vary by version, so inspect your own endpoint. The newer OpenTelemetry semantic conventions also define metrics for the SDK itself, such as queue size, queue capacity and processed counts, though some are still marked experimental and may not yet be present in every language.

Useful alarm ideas

  • Queue use above about seventy percent for several minutes in a row.
  • Any sustained rate of failed enqueues, since it means data is being lost right now.
  • Receiver refusals above zero for more than a short burst.
  • A sudden drop in the number of spans reaching the backend while your application traffic stays flat. This catches silent loss.
T
Do Not Share the Same Road

If the telemetry about your telemetry travels through the same congested pipe, the alert arrives late or never. Send pipeline health metrics through a separate, lightweight path, or scrape them directly, and add a “dead-man’s switch” alert that fires when expected data stops arriving at all.

13Design Patterns That Prevent Head-of-Line Blocking

Every cure removes one of the two ingredients: strict ordering, or a single shared path.

Recall from Chapter 3 that blocking needs an ordered queue and a single shared path. Each pattern below breaks one of those two conditions. Taken together they form a layered defense, much like the compartments in a ship’s hull, where one flooded compartment cannot sink the whole vessel. This idea is known as a bulkhead.

flowchart LR
A["Application Services"] --> AG["Agent Collector on each host"]
AG --> GW["Gateway Collector Tier"]
GW --> P1["Critical Pipeline for errors and slow traces"]
GW --> P2["Bulk Pipeline for normal traces"]
GW --> P3["Metrics Pipeline"]
GW --> P4["Logs Pipeline"]
P1 --> B1["Primary Trace Backend"]
P2 --> B2["Low Cost Storage"]
P3 --> B3["Metrics Backend"]
P4 --> B4["Log Backend"]
    
FIG 5. A two-tier design with separate lanes, each with its own queue, so one slow backend cannot stall the others
Pattern 1

Agent Then Gateway

Applications send to a Collector on the same host or node. That hop takes milliseconds, so the SDK courier is never stuck. The agent then forwards to a bigger gateway tier that has many workers and large queues.

Pattern 2

Separate Lanes Per Signal

Traces, metrics and logs already use separate queues. Keep them in separate pipelines and exporters in the Collector as well, so a log flood cannot delay a metric alarm.

Pattern 3

Priority Routing

Use routing and filtering to send errors and slow traces down a protected pipeline, and bulk or routine data down a cheaper one that is allowed to drop first.

Pattern 4

One Queue Per Destination

Give each backend its own sending queue and workers. A slow archive cannot starve the main store. This is the bulkhead idea in its simplest form.

Pattern 5

More Workers

Raise the number of consumers so that a few stuck requests do not occupy every counter. Make sure the backend can actually handle the extra parallel load.

Pattern 6

Sampling

Send less data in the first place. Head sampling decides at the start of a trace, and tail sampling in a gateway keeps the interesting traces, such as errors and slow ones, after seeing the whole story.

Pattern 7

Nested Timeouts

Make inner timeouts shorter than outer ones. The Collector should give up on a backend before the SDK gives up on the Collector, so the courier gets a clear answer rather than silence.

Pattern 8

Fail Fast and Shed Load

Use the memory limiter, queue limits and outlier detection in your balancer to refuse or reroute early. A quick “not now” is kinder than a slow “maybe.”

Tail sampling needs special care

To decide whether a trace is interesting, tail sampling must see all spans of that trace in the same place. A special exporter can route spans by trace identifier, so every span of one trace lands on the same gateway replica. This creates a new risk, since one slow replica now holds up just the traces assigned to it. The exporter gives each destination its own queue, which keeps the damage limited to that slice. Tools such as Honeycomb’s Refinery are built around the same idea of sampling after seeing whole traces.

T
Production Example

Grafana Alloy and the AWS Distro for OpenTelemetry are both packaged distributions built from the Collector idea. Teams use them as the agent on each host, then forward to a central gateway or a managed backend, precisely so that the fast local hop absorbs bursts while the slow long-distance hop runs in the background.

14Trade-offs: Bigger Queues, Disk Buffers and More Tiers

Every protection has a price. Here is an honest look at the three most popular ones.

There is no free fix. Each approach trades one risk for another, and a good architect states the trade openly.

Option A: Make the queue bigger

Advantages

  • Buys time during short slowdowns of a few seconds.
  • Very simple to change.
  • Fewer drops during brief bursts.

Disadvantages

  • Uses more memory inside your application, which may crash it.
  • Delays data further, so dashboards show an older picture.
  • Does nothing if the shipping speed stays below the arrival rate for long.

Option B: Add a disk-backed queue in the Collector

Advantages

  • Data survives a Collector restart.
  • Holds far more than memory allows.
  • Useful when the backend has planned maintenance.

Disadvantages

  • Adds disk writes, so it is slower than memory.
  • Needs sensible disk size and cleanup planning.
  • Can hide a long outage and create a huge stale backlog afterwards.

Option C: Add an agent tier and a gateway tier

Advantages

  • Fast local hop keeps the SDK courier moving.
  • Central place for sampling, filtering and credentials.
  • Backends see fewer, larger connections.

Disadvantages

  • More programs to run, upgrade and watch.
  • More hops means more places for a queue to fill.
  • Extra network and compute cost.
Everyday Analogy

A bigger queue is like a bigger waiting room at a clinic. It is comfortable for a short rush, but if doctors see fewer patients per hour than arrive, the room only delays the day when it overflows. The only lasting cures are more doctors, shorter visits, or fewer patients coming at once.

The honest conclusion

A larger or persistent queue handles bursts. Parallelism, separation and sampling handle sustained overload. If your system is permanently shipping slower than it produces, no buffer will save it, and you must reduce the data or raise the capacity. Choose the tool that matches the kind of problem you actually have.

15Anti-Patterns and Common Mistakes

Four habits that look sensible on a whiteboard and fail in production.

ANTI-PATTERN 01Avoid
What Teams Do

They leave the simple, immediate span processor switched on in a live service because it worked fine on a laptop and shows spans instantly.

Why It Hurts

Each finished span is exported on the thread doing the work. When the Collector slows down, user requests slow down with it. The monitoring system becomes a cause of outages.

Do Instead

Use the batching processor in every real environment, and keep the immediate one for debugging on a developer machine.

ANTI-PATTERN 02Avoid
What Teams Do

After losing data once, they set the queue to a huge size, and the retry time to hours, to guarantee that nothing is ever dropped.

Why It Hurts

Memory grows until the application or Collector is killed, which loses everything at once. Workers stay busy with ancient data while fresh data waits.

Do Instead

Size queues for the longest slowdown you plan to ride out, usually seconds to a few minutes, then accept controlled loss beyond that and alert on it.

ANTI-PATTERN 03Avoid
What Teams Do

They put every signal, every team and every environment through one Collector pipeline with one exporter and one queue, because it is easier to configure.

Why It Hurts

One noisy log source or one slow backend now blocks everything. The blast radius is the whole company.

Do Instead

Separate pipelines per signal, and separate queues per destination and per important tenant. Cheap to configure, expensive to skip.

ANTI-PATTERN 04Avoid
What Teams Do

They set the SDK export timeout to a long value such as thirty seconds, and the Collector timeout to a short value, without ever checking how they interact.

Why It Hurts

When the Collector stalls, the SDK courier is stuck for the full thirty seconds, and the 2,048-span queue overflows in a second or two.

Do Instead

Keep timeouts short, nest them so inner ones expire first, and measure real export durations before choosing numbers.

Other frequent mistakes

  • Never reading the Collector’s own metrics until a post-incident review.
  • Raising the number of workers without checking whether the backend can accept more parallel writes, which only moves the jam.
  • Running the Collector with no memory limiter and no resource limits in its container.
  • Forgetting that development and production defaults may differ.
  • Treating dropped spans as acceptable and invisible, rather than counting them.

16A Step-by-Step Tuning Walkthrough

A practical order of work that fits most teams, from measuring to protecting.

Resist the urge to change numbers at random. Follow a sequence, change one thing at a time, and watch the metrics from Chapter 12 after every change.

1

Measure the arrival rate

Find the number of spans, metric points and log records per second at normal and peak times, per service and in total.

2

Measure real export time

Record the typical and slowest time for one export. Compute the speed limit with the formula from Chapter 6 and compare it with your peak arrival rate. You want a comfortable gap.

3

Shorten the SDK hop

Point applications at a nearby agent Collector. This usually brings the trip time down from hundreds of milliseconds to a handful.

4

Set nested timeouts

Choose timeouts so that the Collector’s wait on a backend is shorter than the SDK’s wait on the Collector. Keep both short enough that a stuck trip is cancelled quickly.

5

Size queues and workers

Choose a queue that holds a few minutes at peak at most, and enough workers that two or three stuck requests do not stop everything.

6

Separate lanes and filter junk

Split pipelines by signal and by priority, remove health-check spans, and apply sampling to routine traces.

7

Rehearse a failure

In a test environment, slow down or block a backend on purpose. Watch what fills first and whether alerts fire. Fix the weakest link and repeat.

The knobs at a glance

KnobWhereTurn it to help withRisk when overdone
Maximum queue sizeSDK and CollectorShort burstsMemory pressure, stale data
Maximum batch sizeSDK and CollectorEfficiency, or oversize rejections when loweredLong trips when too large
Export timeoutSDK and CollectorFreeing stuck couriers soonerCancelling slow but healthy trips
Number of consumersCollectorTolerating a few stuck requestsOverloading the backend
Retry limitsCollectorReleasing workers from hopeless retriesDropping data that could have succeeded
Memory limitCollectorPreventing crashesRefusing data too eagerly if set too low
Connection ageCollector gRPC serverSpreading load across replicasMore reconnect overhead
CompressionExportersSaving bandwidthExtra processor use
i
Where To Look For Exact Names

Setting names and default values differ between languages and Collector versions. Use the configuration reference of the exact version you deploy, and write down the values you chose and why, so the next engineer can tell a deliberate choice from an accident.

17Real-World Scenarios

Four illustrative situations, written as stories, showing how blocking appears and how teams respond.

The scenarios below are illustrative composites built from patterns that are widely discussed in the observability community. The numbers are examples chosen for clarity, not measurements from any named company.

Scenario 1: The big sale day at a large retailer

Imagine a retailer on the scale of Amazon on a major sale day. Traffic triples. Each checkout service now creates 3,000 spans per second, and the cross-region Collector takes about one second per trip because the receiver is busy. From Chapter 6 the speed limit is only 512 spans per second, so queues overflow within seconds. The team fixes it in two steps: run an agent on each node so the SDK trip drops to a few milliseconds, and use tail sampling at the gateway to keep all error traces and a small share of normal ones.

Scenario 2: The slow vendor backend

A streaming company, in the spirit of Netflix, sends traces to two places: an in-house store and a third-party vendor. The vendor has a slow afternoon. Because both exporters sat in one pipeline with no queue on the vendor side, the slowness reached the in-house path too. After giving each destination its own queue, a vendor slowdown now fills only the vendor’s queue, and the in-house store keeps getting fresh data.

Scenario 3: The runaway log line

A ride-hailing platform, similar in scale to Uber, ships a release where a retry loop logs an error thousands of times per second. The shared gateway queue fills with log records, and the same gateway is also carrying metrics used for paging alerts. Alerts go quiet because the metrics are stuck behind the logs. The fix is structural: logs, metrics and traces get separate pipelines, with metrics given the most protected lane, plus a rate limit per service.

Scenario 4: The pinned connection

A software company runs six Collector replicas behind a basic network load balancer. After a rolling update, three replicas end up holding most of the long-lived connections, and one of them is on a slower machine. Applications attached to that replica show gaps in their traces while others look fine. Setting a maximum connection age and moving to a balancer that understands gRPC requests spreads the load evenly after the next restart.

Most telemetry outages are not caused by one dramatic failure. They are caused by a small slowdown that nobody had a plan for.

18Frequently Asked Questions

Short answers to the questions people ask most often.

Q1Does head-of-line blocking mean OpenTelemetry is badly designed?
No. It is a natural result of ordered queues and single workers, which are sensible choices for keeping cost and memory low. OpenTelemetry gives you the tools to manage it, and this guide is about using them.
Q2Will a slow export make my application slower?
Not when you use the batching processor. Spans are placed in memory and the application moves on, with the cost being dropped telemetry rather than slow requests. It can slow the application if you use a synchronous processor that exports on the request thread.
Q3Why does the SDK drop the newest spans instead of the oldest?
Dropping on arrival when the queue is full is the simplest and fastest rule, and it protects the application from any extra work. The trade-off is that after an outage you receive older data and miss the newest.
Q4Can I run several exports at the same time from one SDK?
The specification asks that one exporter instance does not run overlapping exports, so the usual answer is no. The practical solution is to make the SDK hop very fast by sending to a local agent, and let the agent use many workers for the slower journey.
Q5Is HTTP/2 enough to avoid blocking?
It removes blocking between streams at the HTTP layer but not at the TCP layer below, and it does not remove blocking from queues, retries, large batches or connection pinning.
Q6Should I make my queues as large as possible?
No. Large queues cost memory, increase staleness and only help short bursts. Size them for the slowdown you intend to ride out, then alert when they approach full.
Q7What is the difference between backpressure and dropping?
Backpressure tells the sender to slow down or try again, which keeps data but moves the waiting upstream. Dropping discards data to keep the system healthy. Healthy pipelines use both, in a planned order.
Q8How do I know whether my pipeline is losing data right now?
Watch failed enqueue counters and receiver refusal counters in the Collector, dropped-span diagnostics in the SDK, and compare the number of spans reaching the backend with the number your applications produce.
Q9Do metrics and logs have the same problem as traces?
Yes, in slightly different forms. Logs use a batching processor with the same queue behavior. Metrics use a periodic reader, so a slow export delays or skips later readings. Separate lanes for each signal protect all three.
Q10Is a disk-backed queue the best fix?
It is a good tool for surviving restarts and planned maintenance, but it does not increase shipping speed. If arrivals stay above the speed limit, the disk simply fills slowly.
Q11Where should I start if I can only do one thing?
Put a Collector close to your applications, with a memory limiter and a sending queue, and turn on its internal metrics. That one change shortens the SDK hop, gives you a buffer, and lets you see what happens.

19Summary and Key Takeaways

Key Takeaways

  • Head-of-line blocking happens when an ordered queue and a single shared path let one slow item hold up everything behind it.
  • In the SDK, the batching processor sends one batch at a time, so shipping speed equals batch size divided by export time, and slow exports collapse throughput quickly.
  • A full SDK queue drops the newest spans, and recovery can last far longer than the failure when arrivals sit close to the speed limit.
  • In the Collector, retries hold workers, queues fill, and backpressure travels back to the applications, so a slow backend can reach every SDK.
  • Transport and data also block: sticky connections, oversized batches, poison pills, heavy metric batches and mixed-value queues all create their own jams.
  • Defend with bulkheads: agent and gateway tiers, separate lanes per signal and priority, one queue per destination, nested short timeouts, sampling and a memory limiter.
  • Monitor the monitor: watch queue use, failed enqueues and refusals, and send those health signals through a separate path so alerts survive the very jam they warn about.