Problem Statement
The current Exporter/Client contract does not guarantee that the telemetry passed to Client.UploadTraces is fully consumed when the method returns. A client is allowed to retain the request or process it asynchronously.
This prevents exporters from safely reusing the memory backing the protobuf request after UploadTraces returns.
This is particularly relevant for arena-backed protobuf serialization. An exporter could significantly reduce allocations by reusing an arena between exports, but resetting or reusing the arena while a client still references the request can corrupt data.
PR #8425 demonstrated this issue: the arena-based implementation provided substantial allocation and CPU improvements, but reusing the arena was incompatible with the existing Client ownership contract.
The problem is therefore not specific to arenas. It is the absence of an explicit API contract that tells an exporter when ownership of the serialized request can be safely reclaimed.
Proposed Solution
Introduce a SyncExporter together with a SyncClient interface that explicitly provides a synchronous upload operation.
SyncClient extends the existing Client interface and adds UploadTracesSync:
type SyncClient interface {
Client
UploadTracesSync(ctx context.Context, td ptrace.Traces) error
}
The new exporter would be constructed with a SyncClient:
func NewSync(
ctx context.Context,
client SyncClient,
opts ...Option,
) (*SyncExporter, error)
SyncExporter would use UploadTracesSync exclusively.
The contract of UploadTracesSync is that the client MUST fully consume the supplied telemetry before returning and MUST NOT retain, reference, mutate, or asynchronously process it after the method returns.
This provides the exporter with an explicit lifetime boundary:
SyncExporter
|
| create arena-backed request
v
UploadTracesSync
|
| fully consume request
v
UploadTracesSync returns
|
| request can no longer be accessed
v
arena can be reset/reused
This allows SyncExporter to use arena-backed protobuf serialization and safely reuse the arena across exports.
The arena remains an implementation detail of the exporter. No arena-backed object should escape the synchronous export operation.
NewSync should also use the existing Option mechanism to allow configuration of serialization and arena behavior. Potential options include:
- initial arena/batch size;
- expected attributes per span;
- expected bytes per span or attribute;
- arena growth thresholds;
- maximum arena size eligible for reuse;
- other workload-specific pre-allocation and serialization parameters.
The exact options can be determined during implementation. The goal is to allow workloads with predictable telemetry characteristics to tune pre-allocation without exposing arena ownership or lifecycle details.
The existing upstream HTTP and gRPC clients should be able to implement SyncClient by adding UploadTracesSync. They can reuse their existing transport and serialization logic while providing the stronger ownership guarantee required by SyncExporter.
This does not change the existing Client contract. A client that only implements Client continues to have the existing semantics and can be used by the existing exporter. A client must explicitly implement UploadTracesSync to be used with SyncExporter.
Arena pooling should also have a bounded retention policy. Large arenas should not be retained indefinitely merely because they were used for a large export.
Alternatives
Reuse the existing UploadTraces method
SyncExporter could continue using Client.UploadTraces and rely on documentation stating that a particular client behaves synchronously.
This is not sufficient because the existing Client contract permits clients to retain the request after UploadTraces returns. The exporter therefore has no API-level guarantee that the memory can be reused safely.
Detect synchronous clients through type assertions
The exporter could detect whether a client implements some synchronous behavior and select the arena-reuse path dynamically.
This makes the safety of memory reuse depend on implementation details and would make the ownership contract less explicit. A dedicated UploadTracesSync operation makes the stronger guarantee part of the API instead.
Allocate a new arena for every export
The exporter can avoid unsafe reuse by allocating a fresh arena for every export and allowing it to be reclaimed after the client is finished with the request.
This is compatible with the existing Client contract and can still reduce some allocations, but it cannot safely reuse the arena when the client processes requests asynchronously. The proposed synchronous API provides the stronger contract needed for full arena reuse.
Prior Art
The proposal follows a common API design pattern of providing a stronger operation with an explicit lifetime/ownership contract rather than changing the semantics of an existing general-purpose interface.
Within OpenTelemetry Go, the existing Client interface already separates the exporter from the underlying transport. SyncClient extends this model rather than introducing a separate transport abstraction.
The existing upstream OTLP HTTP and gRPC clients are also prior art for the implementation: both already perform the relevant request processing through their transport clients and should be able to expose the synchronous operation without requiring separate HTTP or gRPC client implementations.
PR #8425 is the immediate prior art within OpenTelemetry Go. It demonstrated both the performance opportunity from arena-backed serialization and the correctness problem caused by reusing arena memory under the existing Client contract.
Additional Context
The main distinction introduced by this proposal is an explicit ownership boundary:
Client.UploadTraces retains its existing semantics and may be asynchronous.
SyncClient.UploadTracesSync guarantees that the supplied telemetry is no longer accessed after the method returns.
SyncExporter can therefore safely reclaim and reuse memory immediately after UploadTracesSync returns.
The proposal intentionally keeps the synchronous behavior opt-in. Existing exporters and clients do not need to change their semantics.
The expected implementation path for upstream OTLP clients is straightforward: add UploadTracesSync to the existing HTTP and gRPC clients, reusing their current transport implementation while guaranteeing that the supplied protobuf request is consumed before the method returns.
The arena implementation should be kept internal and should avoid unbounded memory retention. Configuration through NewSync options can allow tuning of arena sizing and pre-allocation for workloads where telemetry shape is known in advance.
The goal is to provide a well-defined API contract that enables the allocation and CPU optimizations explored in PR #8425 without weakening the correctness guarantees of the existing exporter API.
Problem Statement
The current
Exporter/Clientcontract does not guarantee that the telemetry passed toClient.UploadTracesis fully consumed when the method returns. A client is allowed to retain the request or process it asynchronously.This prevents exporters from safely reusing the memory backing the protobuf request after
UploadTracesreturns.This is particularly relevant for arena-backed protobuf serialization. An exporter could significantly reduce allocations by reusing an arena between exports, but resetting or reusing the arena while a client still references the request can corrupt data.
PR #8425 demonstrated this issue: the arena-based implementation provided substantial allocation and CPU improvements, but reusing the arena was incompatible with the existing
Clientownership contract.The problem is therefore not specific to arenas. It is the absence of an explicit API contract that tells an exporter when ownership of the serialized request can be safely reclaimed.
Proposed Solution
Introduce a
SyncExportertogether with aSyncClientinterface that explicitly provides a synchronous upload operation.SyncClientextends the existingClientinterface and addsUploadTracesSync:The new exporter would be constructed with a
SyncClient:SyncExporterwould useUploadTracesSyncexclusively.The contract of
UploadTracesSyncis that the client MUST fully consume the supplied telemetry before returning and MUST NOT retain, reference, mutate, or asynchronously process it after the method returns.This provides the exporter with an explicit lifetime boundary:
This allows
SyncExporterto use arena-backed protobuf serialization and safely reuse the arena across exports.The arena remains an implementation detail of the exporter. No arena-backed object should escape the synchronous export operation.
NewSyncshould also use the existingOptionmechanism to allow configuration of serialization and arena behavior. Potential options include:The exact options can be determined during implementation. The goal is to allow workloads with predictable telemetry characteristics to tune pre-allocation without exposing arena ownership or lifecycle details.
The existing upstream HTTP and gRPC clients should be able to implement
SyncClientby addingUploadTracesSync. They can reuse their existing transport and serialization logic while providing the stronger ownership guarantee required bySyncExporter.This does not change the existing
Clientcontract. A client that only implementsClientcontinues to have the existing semantics and can be used by the existing exporter. A client must explicitly implementUploadTracesSyncto be used withSyncExporter.Arena pooling should also have a bounded retention policy. Large arenas should not be retained indefinitely merely because they were used for a large export.
Alternatives
Reuse the existing
UploadTracesmethodSyncExportercould continue usingClient.UploadTracesand rely on documentation stating that a particular client behaves synchronously.This is not sufficient because the existing
Clientcontract permits clients to retain the request afterUploadTracesreturns. The exporter therefore has no API-level guarantee that the memory can be reused safely.Detect synchronous clients through type assertions
The exporter could detect whether a client implements some synchronous behavior and select the arena-reuse path dynamically.
This makes the safety of memory reuse depend on implementation details and would make the ownership contract less explicit. A dedicated
UploadTracesSyncoperation makes the stronger guarantee part of the API instead.Allocate a new arena for every export
The exporter can avoid unsafe reuse by allocating a fresh arena for every export and allowing it to be reclaimed after the client is finished with the request.
This is compatible with the existing
Clientcontract and can still reduce some allocations, but it cannot safely reuse the arena when the client processes requests asynchronously. The proposed synchronous API provides the stronger contract needed for full arena reuse.Prior Art
The proposal follows a common API design pattern of providing a stronger operation with an explicit lifetime/ownership contract rather than changing the semantics of an existing general-purpose interface.
Within OpenTelemetry Go, the existing
Clientinterface already separates the exporter from the underlying transport.SyncClientextends this model rather than introducing a separate transport abstraction.The existing upstream OTLP HTTP and gRPC clients are also prior art for the implementation: both already perform the relevant request processing through their transport clients and should be able to expose the synchronous operation without requiring separate HTTP or gRPC client implementations.
PR #8425 is the immediate prior art within OpenTelemetry Go. It demonstrated both the performance opportunity from arena-backed serialization and the correctness problem caused by reusing arena memory under the existing
Clientcontract.Additional Context
The main distinction introduced by this proposal is an explicit ownership boundary:
Client.UploadTracesretains its existing semantics and may be asynchronous.SyncClient.UploadTracesSyncguarantees that the supplied telemetry is no longer accessed after the method returns.SyncExportercan therefore safely reclaim and reuse memory immediately afterUploadTracesSyncreturns.The proposal intentionally keeps the synchronous behavior opt-in. Existing exporters and clients do not need to change their semantics.
The expected implementation path for upstream OTLP clients is straightforward: add
UploadTracesSyncto the existing HTTP and gRPC clients, reusing their current transport implementation while guaranteeing that the supplied protobuf request is consumed before the method returns.The arena implementation should be kept internal and should avoid unbounded memory retention. Configuration through
NewSyncoptions can allow tuning of arena sizing and pre-allocation for workloads where telemetry shape is known in advance.The goal is to provide a well-defined API contract that enables the allocation and CPU optimizations explored in PR #8425 without weakening the correctness guarantees of the existing exporter API.