Deprecating the Collector Batch Processor
Batching in the OpenTelemetry Collector is a process that ensures requests are the right size for the exporter, large enough to improve compression and amortize round-trip latency, but not so large as to exceed limits. Batching combines a number of concerns that are critical to performance, reliability, and cost in running Collectors.
Since OpenTelemetry began in 2019, its Collector has included a batch
processor component, responsible for combining and splitting requests before
sending them to downstream consumers, monitoring inbound and outbound timeouts,
and holding back new requests when the pipeline is full.
TL;DR
We are deprecating the batch processor. Batching is now supported directly in
exporters, called exporter-level batching, and a new queue_batch processor
replaces the original processor component. Integrated queue and batch behavior
brings numerous improvements described below.
Exporter-level batching becomes enabled by default in most exporters in release
v0.171.0, planned for February 2027. However, waiting for the new default is
not enough: if your configuration includes the batch processor, you need to
remove it from your pipelines before the processor itself is removed. You can
opt in to exporter-level batching today by modifying your configuration, as
shown next.
Before:
receivers:
host_metrics:
scrapers:
cpu: {}
processors:
batch:
exporters:
otlp:
...
sending_queue:
enabled: true
# batch not enabled
service:
pipelines:
metrics:
receivers: [host_metrics]
processors: [batch]
exporters: [otlp]
After:
receivers:
host_metrics:
scrapers:
cpu: {}
exporters:
otlp:
...
sending_queue:
enabled: true
batch:
enabled: true
service:
pipelines:
metrics:
receivers: [host_metrics]
exporters: [otlp]
Removal of the batch processor from the core repository and distributions is
planned for release v0.177.0 (May 2027). In most cases, users will prefer
exporter-level batching, with batching after the queue. Don’t worry, batching in
a processor is still possible using the replacement queue_batch processor.
Why replace the batch processor?
Collector processors consume and produce an in-memory representation of
OpenTelemetry data. Consequently, the batch processor is configured in terms
of item counts, which are the number of individual log records, metric data
points, and trace spans. Its default settings are:
processors:
batch: # deprecated processor
timeout: 200ms # maximum delay
send_batch_size: 8192 # minimum item count
# send_batch_max_size: # unlimited maximum item count
In the basic configuration, the batch processor does not impose a maximum item
count. When both limits are set, the batching logic would split and/or combine
requests. For example:
processors:
batch: # deprecated processor
timeout: 200ms # maximum delay
send_batch_size: 1000 # minimum item count
send_batch_max_size: 5000 # maximum item count
By 2021, its basic behavior and default settings had effectively stabilized. By
then, batch processor users had identified three major limitations of the
design, wanting:
- Control over size limits, to set limits in terms of bytes
- Control over concurrency, to set limits on concurrent request count limit
- Control over back-pressure and error propagation.
The batch processor includes a small in-memory queue. Its producer side makes
a blocking write to a Golang channel, then it returns success to the caller like
this:
batch.pending <- request
return nil
This is standard behavior. The problem is that batch processor’s built-in
queue has no configurable support for concurrency, back-pressure, or error
propagation, not to mention a storage extension for making the queue persistent.
Integrated exporter-level queue and batch behavior
Exporter-level batching is generally available for the exporters that elect to
use common sending_queue behavior. Queuing and batching behavior are
integrated:
- Requests first try to enter the queue. The queue has limited capacity. When the queue is full, it can fail immediately or it can block the caller. Usually, the queue returns success to the caller on admission. However, there is also an option to wait for a response.
- A number of consumers concurrently pull requests from the queue. If batching, undersize and oversize requests are combined and split before being handled by consumers, whereas right-sized requests pass straight through the batcher.
Requests exiting the combined queue and batch logic then pass to the exporter’s configured timeout and retry configuration. Here is the default configuration:
exporters:
<exporter>:
sending_queue: # exporter queue_batch behavior
queue_size: 1000 # size limit
sizer: requests # size is number of requests
block_on_overflow: false # no backpressure
wait_for_result: false # no error propagation
num_consumers: 10 # concurrency limit
batch:
enabled: false # batching disabled by default
sizer: items # measure in items of telemetry
min_size: 8192 # minimum number of items
flush_timeout: 200ms # maximum delay
In detail:
num_consumers: determines concurrent exportsqueue_sizeandsizer: determine a limit on pending datablock_on_overflow: false means fail fast; true means wait until the deadlinewait_for_result: false means do not wait for acknowledgement; true means wait for success or failurestorage: optional storage extension for a durable queue.
By default, the queue does not block and does not wait for a result, meaning it will return success or failure to the caller immediately.
Because exporter-level batching was introduced after queueing behavior
stabilized, batching remains disabled by default. After enabling exporter-level
batching, the default for sizer, min_size, and flush_timeout match the
original batch processor.
For most exporters today, you can migrate from the default batch processor
settings by removing it from the pipeline and enabling exporter-level batching.
This enables batching with the default settings shown above:
exporters:
<exporter>:
sending_queue:
batch:
enabled: true
In detail:
enabled: Enable exporter-level batchingsizer: How to measure batch size: items, bytesmin_size: Minimum size of batch before sendingmax_size: Maximum size of batch (optional)flush_timeout: Maximum delay before sending.partition: Details below.
Queue capacity after migration
Moving batching into sending_queue also moves it to the other side of the
queue. In a typical batch processor pipeline, the exporter sending_queue
appears after the batch processor. With an existing batch processor in the
pipeline, the default-size queue holds up to 1000 already-batched requests, so
be careful translating existing configuration.
With exporter-level batching, batching happens after the queue. Now, the queue
holds individual requests arriving from the pipeline, typically much smaller
requests. The same queue_size: 1000, measured in requests may be a lot less
data. Removing batch from the pipeline may cause the queue to hold less data,
which can lead to overflow.
When moving to sending_queue batching, consider raising queue_size to hold
an appropriate number of unbatched requests. For an in-memory queue, meaning
without a storage extension, ideally you want the queue to hold at least as
much data as will keep num_consumers busy. Depending on whether callers will
block, additional queue space should be allocated to survive intermittent export
failures.
When queue_size is measured in items or bytes, request size matters less.
For example, the following queue stores up to 100,000 items. With a minimum size
of 5,000 items per batch, 10 consumers can send 50,000 or more items
concurrently. The queue allows an additional 50,000 items to enter and await
ending before blocking or failing requests.
exporters:
<exporter>:
sending_queue:
sizer: items # size the queue in items, not requests
queue_size: 100_000 # fits around 50-100 of SDK-size batches
num_consumers: 10 # 10 workers will export batches
batch:
enabled: true
sizer: items # export size in items
min_size: 5_000 # minimum size export
An items- or bytes-sized queue makes sending_queue memory costs explicit,
where a request-sized queue can hold large or small requests, varying in memory.
Using bytes helps when the backend limit is also in bytes. Use max_size to
every request under the limit:
exporters:
<exporter>:
sending_queue:
sizer: bytes # size the queue in bytes
queue_size: 100_000_000 # 100 MB buffered
batch:
enabled: true
min_size: 1_000_000 # 1 MB batches
max_size: 10_000_000 # stay under the backend's message limit
Both items and bytes sizers cost more CPU than requests, and bytes is the
most expensive because it computes the encoded size. Watch
otelcol_exporter_queue_size and compare it against
otelcol_exporter_queue_capacity after the switch.
More features
Similar to the batch processor, the sending_queue mechanism supports
partitioning by metadata, where distinct combinations of
selected headers form independent batches.
exporters:
<exporter>:
sending_queue:
<...>
batch:
enabled: true
<...>
partition:
enabled: true
metadata_keys: # list of request headers
- x-auth-token
cache_size: 100 # up to 100 simultaneous batches
idle_timeout: 100s # removed from memory afer 100s
The Collector’s storage extension is available for all sending_queue
configurations, supporting configuration of persistent queues, for
example:
exporters:
<exporter>:
sending_queue:
<...>
storage: file_storage/persistent
extensions:
file_storage/persistent: # file-based storage
directory: <...> # directory path
<...>
Queue/Batch processor
In most cases, configuring sending_queue in the exporter is the best option
for users, but there are still a few cases where batching in a processor makes
sense, for example:
- Prior to an aggregation processor that improves with large batches
- Multiple exporters sharing a processor in scenarios where the processor can save memory.
For such cases, we’ve introduced a new queue_batch
processor as a batch processor replacement. It uses the
same configuration structure as sending_queue, and its default item threshold
and flush timeout match the original batch processor:
processors:
queue_batch: # processor queue and batch
num_consumers: 1 # single concurrent export
sizer: requests # limit in requests
queue_size: 10 # limit 10 in queue
block_on_overflow: true # block when queue is full
wait_for_result: false # return success on queue entry
batch:
enabled: true # batching enabled
sizer: items # measure in items
min_size: 8192
flush_timeout: 200ms
With integrated queue and batch behavior in a processor, users are now able to apply batching, back-pressure, and persistent storage or error propagation consistently whether in a processor or an exporter.
Batching by default in 2027
The sending_queue configuration has been stable since around v0.126.0, and
the replacement is completely ready. One of the remaining tasks is to enable
batching by default in most exporters.
We are entering phase 2 of the Collector batching migration plan.
The batch processor is now officially deprecated.
In the upcoming Collector release, the batch processor will begin to issue a
deprecation warning. Users who do nothing will experience a planned change of
default. Batching will be enabled by default in release v0.171.0 (February
2027); the batch processor will be removed from the core repository and
distributions, planned for release v0.177.0 (May 2027).
Until then, several immediate steps are available to users:
- Configure exporter
sending_queueinstead of thebatchprocessor. Most exporters already support this feature. By configuring your own settings, the change of default has no effect. - Use the
queue_batchprocessor instead of thebatchprocessor.
Before we reach the point of batching being enabled by default, several mitigation steps are ready to use today.
- The
pkg.exporterhelper.queueBatchEnabledfeature flag exists to allow testing with batching enabled by default. - The Collector pipeline setup tries to detect when a pipeline has both a
batchprocessor and asending_queuewith batching enabled. This will issue a warning, and in this case thebatchprocessor can be simply removed.
For component developers
The Collector mdatagen command supports declaring the
sending_queue configuration in the component’s metadata.yaml, declaring
whether sending_queue takes the default behavior, has specific overrides, or
is entirely omitted.
Thanks
There have been many contributors to the queueing and batching codebase, from the earliest issues to the finishing touches.
The queue_batch lead authors are
Bogdan Drutu and
Dmitry Anoshin, with partitioning support by
Sindy Li.
Thanks to so many more contributors, including Adii, Aditya Amol Kuchekar, Alan Lee, Alex Boten, Andrew Gizas, Andrew Wilkins, Andrzej Stencel, Anitha Vadla, Antoine Toulme, Chethan, Damien Mathieu, Evan Bradley, Gergely Madarasz, Israel Blancas, Jade Guiton, Jan Korona, Joshua MacDonald, Matthieu Morel, Mike Terhar, Pablo Baeyens, Paulo Dias, Ravishankar Gnanaprakasam, Roger Coll, Semih Serhat Karakaya, simpleqt, Tyler Helmuth, Vengal Rao, Vibhanshu Singh, Vihas Makwana, Vinicius Azevedo, Vishal Raj, Yang Song, and Yiquan Zhou.