Skip to content

[SDK] BatchSpanProcessor drain the queue in a tight loop instead of waiting for the next full batch #4449

Description

@yswdqz

Describe your environment

  • Platform: macOS (Apple Silicon)
  • opentelemetry-cpp version: 1.27.0 (installed via Homebrew)
  • Compiler: AppleClang 16.0.0
  • Downstream collector: local otlp_receiver listening on localhost:2026 (same machine, negligible network latency)
  • BatchSpanProcessor configuration:
    • max_queue_size = 8192
    • max_export_batch_size = 2048
    • schedule_delay_millis = 5000

Steps to reproduce

  1. Start a local OTLP gRPC receiver that exposes per-export counters.
  2. Configure BatchSpanProcessor with OtlpGrpcExporter pointing at the local receiver.
  3. Produce spans at a constant rate (10k / 30k / 50k spans/s) for 60 seconds.
  4. Observe the number of export requests received and the average number of spans per request.

What is the expected behavior?

With max_export_batch_size = 2048, a steady stream of spans should result in exports that are close to 2048 spans each. This minimizes gRPC request count and CPU overhead.

This is consistent with the BSP semantics in Java and Go:

  • Export when a full batch is available.
  • If a full batch is never reached, export whatever is buffered when schedule_delay expires.

What is the actual behavior?

BatchSpanProcessor::Export() in opentelemetry-cpp is effectively a tight do { ... } while(true) loop:

void BatchSpanProcessor::Export()
{
  do {
    num_records_to_export =
        buffer_.size() >= max_export_batch_size_ ? max_export_batch_size_ : buffer_.size();
    // ... consume & export ...
  } while (true);
}

After each export finishes, the worker immediately checks the buffer again. As long as the buffer is non-empty, it exports another batch. It does not wait for the next 2048-span batch to accumulate.

As a result, even with a local fast collector, the average batch size is only around 1300–1400 spans instead of 2048. The extra export requests cause more serialization, more lock/condition-variable wakeups, and more gRPC calls, leading to higher CPU usage than a strict-batch implementation.


Performance data

The following data was collected with 2048 batch size, 0 ms added export delay, and no explicit ForceFlush.

(2026.8.19 edit, The previous tests had noise; the CPU core differences were actually not significant.)

sps mode export requests avg spans/request
10,000 official BSP 416 1,442.3
10,000 strict-batch BSP 293 2,047.8
30,000 official BSP 1,309 1,375.1
30,000 strict-batch BSP 879 2,047.8
50,000 official BSP 2,269 1,322.2
50,000 strict-batch BSP 1,465 2,047.8

Notes:

  • The official BSP only reaches about 65%–70% of the configured batch size, and issues roughly 35%–55% more export requests than strict-batch.

Questions / Discussion

  1. What is the rationale for the current tight-loop drain behavior? Is it primarily to reduce export latency, or is there another reason?
  2. Has the project considered an optional mode where the worker exports exactly one batch per wakeup and then waits again for either max_export_batch_size or schedule_delay?
  3. For CPU-sensitive deployments that can tolerate the 5-second maximum delay, the current behavior does not appear optimal. Would the community be open to a PR that adds a stricter batching strategy?

Additional context

  • This behavior occurs on the normal export path and does not require ForceFlush.
  • When exporter latency is increased (simulating a slow remote collector), the official BSP's average batch size does approach 2048, but only because the queue starts backing up and dropping spans. The strict-batch implementation continues to export 2048-span batches stably under the same conditions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtriage/acceptedIndicates an issue or PR is ready to be actively worked on.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions