You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add support for using zstd compression for OTLP when targeting net11.0.
Benchmarks
Note
These benchmark results were generated with Claude Code.
Environment: BenchmarkDotNet 0.16.0-preview.2, .NET 11.0.0-rc.2, Windows 11, Intel Core i7-13700H, pinned to P-cores (--affinity 4095).
The payloads are real, uncompressed OTLP/HTTP request bodies captured from the exporter:
Traces with HTTP server and database client spans
Structured logs with scopes
HTTP server histograms
The baseline is gzip CompressionLevel.Fastest, which is what the exporter uses today.
Compressed size (bytes)
Payload
Raw
gzip Fastest
gzip Optimal
zstd L3
zstd L3 vs gzip Fastest
traces-1
1,362
1,092
952
972
-11.0%
traces-128
54,317
8,358
7,179
7,145
-14.5%
traces-512
214,349
34,675
28,978
22,393
-35.4%
logs-512
210,769
46,068
39,853
38,797
-15.8%
logs-2048
819,740
177,510
152,819
147,115
-17.1%
metrics-20
8,490
1,844
1,604
1,567
-15.0%
metrics-200
65,964
8,716
6,440
6,169
-29.2%
Compression time
Mean time per compression, with the ratio to gzip Fastest in parentheses. An identical control copy of the baseline measured 0.90-1.07, so only differences larger than about 10% are meaningful.
Payload
gzip Fastest
zstd L3 (cached encoder)
zstd L3 (new encoder)
zstd L1 (ratio)
traces-1
11.2 us
9.8 us (0.89)
13.4 us (1.21)
0.81
traces-128
40.3 us
40.5 us (1.01)
63.6 us (1.59)
0.89
traces-512
235 us
152 us (0.65)
464 us (1.98)
0.59
traces-2048
913 us
690 us (0.76)
1489 us (1.63)
0.76
logs-512
318 us
313 us (0.99)
672 us (2.13)
0.77
metrics-200
64.1 us
62.2 us (0.98)
88.3 us (1.38)
0.94
gzip Optimal took 2.7x-6.1x as long as gzip Fastest.
Decompression time
Receiver-side time, as a ratio to gzip decompression (lower is better for zstd).
Payload
Ratio
traces-1
1.04
traces-128
0.71
traces-512
0.74
traces-2048
0.69
logs-512
0.83
metrics-200
0.85
Allocations
Managed allocations per compression: 88-120 B for zstd (cached encoder), against 232 B for gzip.
zstd at level 3 produces 11-36% smaller payloads than the current gzip setting, and is also smaller than gzip Optimal.
Its CPU cost is the same as gzip's or lower (35% lower at the default batch size of 512 spans), but only when the encoder is reused. Creating a new encoder per export costs 1.2x-2.1x gzip's CPU.
The cost is native memory held between exports: 1.5-2.4 MiB per exporter at typical batch sizes, against gzip's ~330 KiB per export, which is freed afterwards.
Pledging the payload size is what keeps that from being ~9.7 MiB.
Merge requirement checklist
CONTRIBUTING guidelines followed (license requirements, successful build/tests, etc.)
Tests added/updated
CHANGELOG.md file(s) updated for non-trivial changes
❌ Patch coverage is 91.33858% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.09%. Comparing base (1e88560) to head (14697a4). ⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.
Adds optional Zstandard compression to the OTLP exporter on supported .NET 11+ runtimes, extending the existing HTTP/protobuf and gRPC compression paths.
Changes:
Adds the public Zstd option, environment-variable parsing, and configuration documentation.
Introduces reusable Zstandard encoders, platform checks, and gzip fallback when receivers reject compressed requests.
Adds compression, fallback, and encoder-reuse tests, plus Zstandard benchmark coverage.
- Only fall back to gzip for gRPC UNIMPLEMENTED when grpc-accept-encoding
does not list zstd.
- Only fall back to gzip for HTTP 415; 400 is no longer treated as a
compression rejection.
- Dispose of a Zstandard encoder returned after shutdown instead of
caching it.
Assisted-by: Claude Code <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Repeated grpc-accept-encoding fields can bypass gzip fallback and trigger continued zstd retries, while permanent fallback unnecessarily retains the unused zstd compressor.
Both permanent gzip-fallback paths leave the zstd encoder cached for the remaining lifetime of the exporter, even though no subsequent request will use it. After a real zstd request has been serialized, this retains its native compression workspace unnecessarily. Please shut down the zstd compressor when permanently switching to gzip; its existing shutdown logic also safely releases encoders returned by overlapping requests.
This adds a public compression option and changes export/fallback behavior, but the component's CHANGELOG.md has no entry for zstd. Please add an entry at the end of its ## Unreleased section with this PR's link, as required by REVIEW.md:11-25, so users can discover the new option and its platform limitations.
grpc-accept-encoding may be sent as more than one header field, which
GetHeaderValue rejects by throwing. That turned an UNIMPLEMENTED status
into a retryable Unavailable one and skipped the gzip fallback. Join the
values of the header instead.
Assisted-by: Claude Code <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Permanent downgrade to GZip leaves the unused native Zstandard encoder cached until client shutdown, unnecessarily retaining a potentially multi-megabyte workspace per long-lived exporter.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Release the Zstandard encoder when permanently downgrading to GZip
Permanently switching to GZip leaves the native Zstandard encoder cached until the entire client shuts down, even though no subsequent request can reuse it. This affects both successful fallback and the three-failure downgrade, retaining a potentially multi-megabyte workspace per long-lived exporter unnecessarily. Please release the compressor when committing either permanent downgrade; its Shutdown implementation already handles encoders still in use by overlapping exports.
Once the exporter permanently switches to GZip, nothing can reuse the
cached Zstandard encoder, so shut the compressor down instead of
retaining its native memory until the client shuts down.
Assisted-by: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
keep-openPrevents issues and pull requests being closed as staleneeds-spec-changeIssues which require the OpenTelemetry Specification to clarify or define behaviorperfPerformance relatedpkg:OpenTelemetry.Exporter.OpenTelemetryProtocolIssues related to OpenTelemetry.Exporter.OpenTelemetryProtocol NuGet packagespec-complianceIssues related to compliance with the OpenTelemetry Specification
2 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Based on open-telemetry/opentelemetry-specification#5321.
Changes
Add support for using zstd compression for OTLP when targeting
net11.0.Benchmarks
Note
These benchmark results were generated with Claude Code.
Environment: BenchmarkDotNet 0.16.0-preview.2, .NET 11.0.0-rc.2, Windows 11, Intel Core i7-13700H, pinned to P-cores (
--affinity 4095).The payloads are real, uncompressed OTLP/HTTP request bodies captured from the exporter:
The baseline is gzip
CompressionLevel.Fastest, which is what the exporter uses today.Compressed size (bytes)
Compression time
Mean time per compression, with the ratio to gzip Fastest in parentheses. An identical control copy of the baseline measured 0.90-1.07, so only differences larger than about 10% are meaningful.
gzip Optimal took 2.7x-6.1x as long as gzip Fastest.
Decompression time
Receiver-side time, as a ratio to gzip decompression (lower is better for zstd).
Allocations
Managed allocations per compression: 88-120 B for zstd (cached encoder), against 232 B for gzip.
Native memory per live compressor
Measured as private bytes.
Summary
Merge requirement checklist
CHANGELOG.mdfile(s) updated for non-trivial changesFootnotes
Yes, I can answer maintainer questions about the content of this PR without using AI. ↩