SimpleSpanProcessor or BatchSpanProcessor: trading request-thread latency for telemetry loss under concurrent load
0 reputation · 23 Jun 2021, 22:58 UTC
0 reputation · 23 Jun 2021, 22:58 UTC
Under concurrent request load, OpenTelemetry SDK span processors force a documented trade-off between request-path latency and telemetry completeness. SimpleSpanProcessor exports each span synchronously on the thread that ends it, keeping telemetry latency low but adding export overhead directly to concurrent request handling. BatchSpanProcessor queues spans for a background thread, decoupling telemetry from request latency, but introduces a bounded queue that silently drops spans when full.
The goal is to minimize end-to-end request latency while preserving complete telemetry under sustained concurrency. The uncertainty is that the SDK offers no adaptive middle ground: operators must choose between blocking request threads with SimpleSpanProcessor or accepting telemetry loss with BatchSpanProcessor under saturation. Both processors preserve context propagation semantics, so the latency divergence stems from export scheduling rather than trace correlation.
Default queue sizes, batch limits, and drop policies vary by language SDK, so the exact saturation point differs across implementations. Concurrent latency may also originate from exporters, network I/O, or downstream collector capacity rather than the processor alone.
Neither processor is universally better. SimpleSpanProcessor adds export time directly to the request thread, so latency rises with exporter and network cost. BatchSpanProcessor keeps the request path fast until its bounded queue fills, then silently drops spans. Under sustained concurrency, the dominant cost depends on whether your exporter can drain the queue faster than spans arrive; if not, queue saturation and telemetry loss dominate.
Confirmed: SimpleSpanProcessor exports synchronously on the thread that ends the span. BatchSpanProcessor queues spans and exports on a background thread. Both preserve trace context; the difference is scheduling.
Environment-dependent: Whether synchronous export adds measurable request latency depends on export duration (serialization, network, collector response time). Whether BatchSpanProcessor drops spans depends on queue size, batch size, export throughput, and arrival rate.
Defaults differ across SDKs. The following values reflect common defaults reported in SDK documentation as of recent releases; verify them against your exact SDK version before relying on them.
| SDK | Queue size | Batch size | Schedule delay | Drop policy |
|---|---|---|---|---|
| Java | 2048 | 512 | 5000 ms | Drop oldest |
| Go | 8192 | 512 | 5 s | Drop newest |
| .NET | 1000 | 500 | 5000 ms | Drop oldest |
| Python | 2048 | 512 | 5000 ms | Drop oldest |
These defaults mean that a short burst of high concurrency can exhaust a .NET queue quickly, while Go's larger queue absorbs more before dropping. The drop policy also differs: Go discards the newest span on overflow, preserving older context, whereas Java, .NET, and Python discard the oldest.
There is no documented adaptive processor that automatically switches between blocking and dropping based on load. The SDK requires an explicit choice or a tuned compromise.
Which language SDK and version are you using? The defaults, the availability of blocking-on-overflow, and the metric names differ enough that the recommendation changes materially.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.