An OpenTelemetry Collector often receives many small groups of spans, logs or metric data points. Sending every group as a separate request creates unnecessary network calls. Waiting for one very large request uses more memory and delays the data.
The OTel batch processor sits between those two extremes. It collects spans, metric data points or log records and sends them downstream when the batch reaches a size trigger or a timeout expires.
The basic configuration is small. The part that causes confusion is what the size settings actually mean. send_batch_size is a trigger, not a hard limit. If the destination needs a strict maximum number of items per request, configure send_batch_max_size as well.
What is the OTel batch processor?
The OpenTelemetry Collector batch processor groups telemetry before passing it to the next component in a pipeline. This reduces the number of export requests and can improve compression.
small incoming requests
| | | | |
v v v v v
batch processor
[ accumulated data ]
|
| size reached or timeout expired
v
larger request
|
v
exporterThe processor works with all three OpenTelemetry signals. For traces it counts spans, for metrics it counts data points, and for logs it counts log records. The configured number is not a byte size.
The processor keeps pending data in memory. During a normal Collector shutdown, it sends the remaining batch before stopping. It does not save that data to disk or retry a failed export. Exporter queues and retry settings handle those jobs.
The batch processor is available in the Core, Contrib and Kubernetes Collector distributions. Its current stability level is beta for traces, metrics and logs. The official batch processor documentation contains the full configuration reference.
OTel batch processor configuration
The following Collector configuration accepts OTLP over gRPC and HTTP. The same batch processor is used by the traces, metrics and logs pipelines, and the debug exporter makes the example easy to run without a remote backend.
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 20
batch:
timeout: 1s
send_batch_size: 1000
send_batch_max_size: 2000
exporters:
debug:
verbosity: basic
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [debug]
metrics:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [debug]
logs:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [debug]The debug exporter prints a short telemetry summary in the Collector logs. It is useful for checking that data reaches the Collector before connecting a real backend.
The service.pipelines section connects the components defined above it. For example, the traces pipeline receives data through otlp, runs memory_limiter and batch in that order, then sends the result to debug. A component is not active until a pipeline includes it.
This configuration sends a batch when either of these conditions is met:
- the pending batch reaches at least 1,000 items
- one second passes before it reaches that trigger
No emitted batch can contain more than 2,000 items. The exact meaning of an item depends on the pipeline: a span, a metric data point or a log record.

How timeout, send_batch_size and send_batch_max_size work
These settings control different parts of the same batching cycle.
| Setting | Default | What it controls |
|---|---|---|
timeout | 200ms | Sends the pending batch when the timer expires |
send_batch_size | 8192 | Triggers a send after the pending item count reaches this value |
send_batch_max_size | 0 | Limits the number of items in one outgoing batch; 0 means no upper limit |
timeout
timeout prevents low-volume telemetry from waiting indefinitely for a size trigger. If only 400 spans arrive during the one-second timeout in the example, the processor sends those 400 spans.
A longer timeout gives a low-volume pipeline more time to build a larger batch. That can reduce request overhead, but it also adds export delay and keeps data in memory longer. A shorter timeout reduces that delay at the cost of sending smaller requests more often.
Setting timeout: 0s removes the wait. Each incoming group continues to the next component immediately, although send_batch_max_size can still split a large group. In this mode, send_batch_size is ignored.
send_batch_size
send_batch_size tells the processor when to send. It does not define the exact outgoing batch size.
Suppose send_batch_size is 1000 and the pending batch currently contains 900 spans. The next incoming request contains 300 spans. The trigger has now been crossed, so the processor sends data downstream. Without send_batch_max_size, that send can contain more than 1,000 spans.
This distinction matters when an exporter or backend rejects large requests. Lowering send_batch_size can make batches smaller in normal traffic, but it does not enforce a maximum.
send_batch_max_size
send_batch_max_size sets the item-count limit for a batch passed to the next component. Larger batches are split before they continue through the pipeline.
The value must be greater than or equal to send_batch_size. This configuration is invalid:
processors:
batch:
send_batch_size: 2000
send_batch_max_size: 1000The Collector rejects it because the maximum is below the trigger.
This setting counts items, not bytes. In a traces pipeline, send_batch_max_size: 1000 means no outgoing batch contains more than 1,000 spans. If each span is 1 KiB, the batch is roughly 1 MiB. If each span is 50 KiB, the same 1,000-span batch is roughly 50 MiB. The item limit therefore cannot guarantee an exact request size in bytes.
Where to place the batch processor
Put memory_limiter near the beginning of the pipeline and batch near the end:
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, k8sattributes, tail_sampling, batch]
exporters: [otlp_http/backend]Processors run from left to right. The memory limiter should see pressure before more processing consumes memory. Enrichment, filtering and sampling should happen before batching so removed data does not occupy the final outgoing batches.
otlp_http/backend is an example component name. otlp_http is the exporter type and backend is a name chosen for this configuration. The exporter and its endpoint must also be defined under the top-level exporters section.
This order is especially important with OpenTelemetry tail sampling. The tail sampling processor first groups spans by trace ID and decides which traces to keep. The batch processor then prepares the retained spans for export.
The name batch can appear in the traces, metrics and logs pipelines. The Collector still maintains a separate pending batch for each signal.
Export batches to a backend
Replace the debug exporter with the exporter for your observability backend when the pipeline is ready. The batch processor works the same way regardless of the destination. This example sends traces to Parseable over OTLP HTTP:
exporters:
otlp_http/parseable:
endpoint: ${env:PARSEABLE_URL}
encoding: json
headers:
X-API-Key: ${env:PARSEABLE_API_KEY}
X-P-Stream: ${env:PARSEABLE_TRACE_STREAM}
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp_http/parseable]The exporter sends trace data to the /v1/traces path under PARSEABLE_URL. Logs and metrics use their own pipelines and datasets. The Parseable OpenTelemetry documentation covers all three signals.
Size the batch processor
There is no useful batch size that fits every Collector. Start with the destination's request limits and the amount of traffic handled by one Collector instance.

Start by checking roughly how large one span, log record or metric data point is. Then decide how large you want each outgoing request to be:
items per batch = target request size / average item sizeFor example, if one span is about 2 KiB and you want each request to be around 2 MiB:
2 MiB / 2 KiB = about 1,024 spansYou can use 1024 as the initial send_batch_size. It will not produce an exact 2 MiB request every time because some spans contain more attributes or events than others. Compression can also make the final network request smaller. Test with real telemetry and adjust the value from there.
Tune the three settings together:
- Choose
send_batch_sizeas the normal send trigger. - Set
send_batch_max_sizewhen the next component needs a maximum number of items per batch. - Set
timeoutfrom the maximum batching delay the pipeline can accept.
Check the calculation separately for logs, metrics and traces. A single set of values may be convenient, but the signals often have different traffic rates and item sizes. Named processor instances let each pipeline use its own settings:
processors:
batch/traces:
timeout: 1s
send_batch_size: 1000
send_batch_max_size: 2000
batch/logs:
timeout: 500ms
send_batch_size: 5000
send_batch_max_size: 10000
service:
pipelines:
traces:
processors: [memory_limiter, batch/traces]
logs:
processors: [memory_limiter, batch/logs]Use those numbers only as a configuration example. Measure the real workload before treating them as production values.
How batching affects memory and latency
The processor holds a pending batch in memory. Increasing send_batch_size or timeout can therefore increase both memory use and the time telemetry waits before export.
High traffic usually reaches the size trigger quickly, so timeout has little effect. Low traffic waits for the timer. This means two Collector instances with the same configuration can produce different average batch sizes when their traffic rates differ.
Large batches may improve compression and reduce export calls, but bigger is not automatically better. Building and sending a large batch can cause a short increase in memory use. If a destination rejects requests above a certain size, retrying the same oversized request will not fix it.
Keep the memory limiter processor before batch. Do not set the Collector memory limit based on batch data alone because receivers, other processors and exporter queues also use memory. Test with spans, logs and metrics that are close to the size of your real data.
Batch by client metadata
Most pipelines need one batcher per signal. A multi-tenant pipeline may need to keep data from different tenants in separate batches. The Collector can do this using client metadata, such as a tenant_id sent with the request.
receivers:
otlp:
protocols:
grpc:
include_metadata: true
processors:
batch/by-tenant:
send_batch_size: 1000
metadata_keys: [tenant_id]
metadata_cardinality_limit: 100With this configuration, requests containing tenant_id: team-a go into one batch and requests containing tenant_id: team-b go into another. Each unique tenant value creates another batcher with its own pending data, so a large number of tenant values can use more memory.
metadata_cardinality_limit limits how many unique metadata combinations the processor accepts during the life of the Collector process. The default is 1,000. Validate tenant metadata with an authentication extension instead of accepting arbitrary values from untrusted clients.
Do not use this option to group data by ordinary resource or span attributes. It reads client metadata attached to the Collector request.
Monitor batch processor metrics
The Collector exposes internal metrics that show whether a batch was sent because it reached send_batch_size or because the timeout expired. They also show how many items and bytes each sent batch contained.
| Metric | What it shows |
|---|---|
otelcol_processor_batch_batch_send_size | Number of spans, data points or log records in each sent batch |
otelcol_processor_batch_batch_send_size_bytes | Number of bytes in each sent batch |
otelcol_processor_batch_batch_size_trigger_send | Number of sends triggered by send_batch_size |
otelcol_processor_batch_timeout_trigger_send | Number of sends triggered by timeout |
otelcol_processor_batch_metadata_cardinality | Number of distinct metadata combinations currently being batched |
The byte-size metric is available only when the Collector's internal telemetry level is set to detailed. The current metric definitions are listed in the processor's internal telemetry documentation.
If timeout-triggered sends dominate and batches stay small, that Collector does not receive enough traffic to reach the configured size within the timeout. That may be acceptable for a low-volume service. Raise the timeout only if the extra delay is acceptable.
If most sends are size-triggered and the outgoing payloads remain within backend limits, the configuration is doing its job. Also watch total Collector memory, exporter failures, queue size and end-to-end telemetry delay. Batch metrics alone cannot show whether the destination is healthy.
Batch processor and exporter queues are different
The batch processor combines small groups into larger requests. If the backend becomes slow, an exporter sending queue holds completed batches until the exporter catches up. Retry settings tell the exporter when to try a failed request again.
batch processor -> exporter queue -> export attempt -> backendThey solve different problems. The batch processor improves how data is grouped. The queue handles data waiting to be exported. An in-memory queue is lost when the Collector process stops, while a persistent queue stores pending data on disk so it can continue after a restart.
OTel batch processor tuning checklist
Before using a tuned configuration in production, confirm the following:
send_batch_sizeis treated as a trigger, not a maximum.send_batch_max_sizeis at least as large assend_batch_size.- The expected payload stays within the backend's request limit.
timeoutmatches the export delay the application can tolerate.memory_limiterruns beforebatch.- Sampling and filtering run before
batch. - Metadata cardinality is bounded when metadata batching is enabled.
- Processor metrics, exporter failures and Collector memory are monitored together.
Start with the defaults when they already work for the pipeline. Change one setting at a time and compare batch size, request rate, memory use and export delay using traffic similar to the real workload.

