Skip to content

Redundant clones in span export path #3372

@bryantbiggs

Description

@bryantbiggs

The span export pipeline has three unnecessary clone/allocation points that compound on every export cycle. The log pipeline already avoids most of these via the borrowed LogBatch pattern, but spans still take the expensive ownership path.

1. batch.split_off(0) allocates a new Vec every export cycle

In opentelemetry-sdk/src/trace/span_processor.rs, export_batch_sync calls batch.split_off(0) to pass owned data to the exporter. This allocates a fresh Vec on every export. There's already a TODO comment acknowledging this:

// TODO: Compared to Logs, this requires new allocation for vec for
// every export. See if this can be optimized by
// *not* requiring ownership in the exporter.

The log side avoids this entirely because LogExporter::export() takes a borrowed LogBatch<'_>.

2. Deep clone of the entire batch per retry attempt

In opentelemetry-otlp/src/exporter/tonic/trace.rs, the export wraps the batch in Arc<Vec<SpanData>> and then on each retry iteration does:

group_spans_by_resource_and_scope((*batch_clone).clone(), &self.resource)

That (*batch_clone).clone() deep-clones every SpanData in the batch on every attempt, including the first one. If the data were serialized to bytes once before retrying, retries would just resend the same bytes.

3. Each SpanData is cloned during protobuf conversion

In opentelemetry-proto/src/transform/trace.rs, group_spans_by_resource_and_scope iterates over &SpanData references but then clones each one to convert it:

.map(|span_data| span_data.clone().into())

The From<SpanData> for Span impl already consumes the SpanData by value, so this clone is forced by the function signature taking Vec<SpanData> while also needing references for grouping. Taking ownership and draining the vec would avoid this.

Suggested approach

  • Change SpanExporter::export() to accept borrowed data (like LogExporter does with LogBatch), removing the need for split_off(0) in the batch processor. This is the biggest API change and would fix point 1.
  • Make group_spans_by_resource_and_scope consume the Vec instead of borrowing it, so it can move SpanData into proto Spans without cloning. Fixes point 3.
  • Serialize the protobuf request once before the retry loop, so retries resend bytes instead of re-converting from SpanData. Fixes point 2.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions