Skip to content

[OTLP] Add support for zstd compression - #8056

Draft
martincostello wants to merge 9 commits into
open-telemetry:mainfrom
martincostello:OTEP-5321
Draft

martincostello wants to merge 9 commits into
open-telemetry:mainfrom
martincostello:OTEP-5321

Conversation

@martincostello

Copy link
Copy Markdown
Member

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:

  • 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.

Native memory per live compressor

Measured as private bytes.

Compressor Native memory Lifetime
gzip Fastest / Optimal ~330 KiB Freed after each export
zstd L3, size pledged 54 KiB (1 span), 1.5 MiB (512 spans), 2.4 MiB (2048 spans) Kept by the cached encoder between exports
zstd L3, size not pledged 9.7 MiB, whatever the payload size Kept by the cached encoder between exports
zstd L3, payload >= 8 MiB ~9.5 MiB Kept by the cached encoder between exports

Summary

  • 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
  • Changes in public API reviewed (if applicable)
  • AI usage disclosure:
    • No AI used
    • AI-assisted
    • Bulk AI-generated
  • I have the experience and knowledge necessary to understand, review, and validate all content in this PR1

Footnotes

  1. Yes, I can answer maintainer questions about the content of this PR without using AI. ↩

Add support for using zstd compression for OTLP when targeting `net11.0`.

Based on open-telemetry/opentelemetry-specification#5321.
@github-actions github-actions Bot added pkg:OpenTelemetry.Exporter.OpenTelemetryProtocol Issues related to OpenTelemetry.Exporter.OpenTelemetryProtocol NuGet package perf Performance related labels Oct 28, 2026
@opentelemetry-pr-dashboard

Copy link
Copy Markdown

Pull request dashboard status

Waiting on the author · refreshed 2026-10-28 14:27 UTC

Move out of draft to request review.

Status above doesn't look right?
  • Just replied or pushed? Anything around or after the refresh time above may not be picked up yet — give it a few minutes.
  • Anything look wrong? Report it with what you expected; it helps us improve the dashboard.

@martincostello martincostello added the keep-open Prevents issues and pull requests being closed as stale label Oct 28, 2026
@codecov

codecov Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ 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.

Files with missing lines Patch % Lines
...mentation/ExportClient/Grpc/GrpcProtocolHelpers.cs 50.00% 3 Missing and 1 partial ⚠️
...ol/Implementation/ExportClient/OtlpExportClient.cs 88.88% 0 Missing and 3 partials ⚠️
...ocol/Implementation/ExportClient/ZstdCompressor.cs 91.66% 1 Missing and 2 partials ⚠️
...col/Implementation/ExportClient/ZstdHttpContent.cs 90.90% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #8056      +/-   ##
==========================================
- Coverage   93.09%   93.09%   -0.01%     
==========================================
  Files         397      401       +4     
  Lines       23335    23694     +359     
  Branches     4359     4431      +72     
==========================================
+ Hits        21724    22057     +333     
- Misses       1103     1113      +10     
- Partials      508      524      +16     
Flag Coverage Δ
unittests-Project-Experimental 93.15% <91.33%> (-0.06%) ⬇️
unittests-Project-Stable 93.19% <91.33%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...mentation/ExportClient/ExportClientGrpcResponse.cs 100.00% <100.00%> (ø)
...mplementation/ExportClient/OtlpGrpcExportClient.cs 92.35% <100.00%> (+0.92%) ⬆️
...mplementation/ExportClient/OtlpHttpExportClient.cs 100.00% <100.00%> (ø)
...tation/OpenTelemetryProtocolExporterEventSource.cs 98.24% <100.00%> (+0.08%) ⬆️
...orter.OpenTelemetryProtocol/OtlpExporterOptions.cs 97.90% <100.00%> (+0.02%) ⬆️
...col/Implementation/ExportClient/ZstdHttpContent.cs 90.90% <90.90%> (ø)
...ol/Implementation/ExportClient/OtlpExportClient.cs 94.28% <88.88%> (-1.97%) ⬇️
...ocol/Implementation/ExportClient/ZstdCompressor.cs 91.66% <91.66%> (ø)
...mentation/ExportClient/Grpc/GrpcProtocolHelpers.cs 53.06% <50.00%> (-0.60%) ⬇️

... and 10 files with indirect coverage changes

Add more test coverage for zstd support.
Place a limit on how often a HTTP 400 will cause a change in the compression codec.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Compression fallback incorrectly retries HTTP 400 responses and gRPC errors despite advertised zstd support, while encoder returns after shutdown can retain native memory.

3 open issues
What changed in this PR

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.
File Description
test/​OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/​OtlpHttpExportClientTests.cs Tests HTTP compression, fallback, frame compatibility, and encoder lifecycle.
test/​OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/​OtlpGrpcExportClientTests.cs Tests Zstandard gRPC framing and fallback.
test/​OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/​OtlpExporterOptionsTests.cs Tests case-insensitive zstd configuration parsing.
test/​Benchmarks/​Exporter/​OtlpExporterCompressionBenchmarks.cs Adds Zstandard to compression benchmarks.
src/​OpenTelemetry.Exporter.OpenTelemetryProtocol/​README.md Documents configuration, platform restrictions, and fallback.
src/​OpenTelemetry.Exporter.OpenTelemetryProtocol/​OtlpExporterOptions.cs Recognizes the zstd configuration value.
src/​OpenTelemetry.Exporter.OpenTelemetryProtocol/​OtlpExportCompression.cs Adds the public compression option and remarks.
src/​OpenTelemetry.Exporter.OpenTelemetryProtocol/​Implementation/​OpenTelemetryProtocolExporterEventSource.cs Adds unsupported-platform and receiver-rejection warnings.
src/​OpenTelemetry.Exporter.OpenTelemetryProtocol/​Implementation/​ExportClient/​ZstdHttpContent.cs Streams Zstandard-compressed HTTP content.
src/​OpenTelemetry.Exporter.OpenTelemetryProtocol/​Implementation/​ExportClient/​ZstdCompressor.cs Implements encoder reuse and bounded compression windows.
src/​OpenTelemetry.Exporter.OpenTelemetryProtocol/​Implementation/​ExportClient/​OtlpHttpExportClient.cs Selects HTTP compression and detects rejection responses.
src/​OpenTelemetry.Exporter.OpenTelemetryProtocol/​Implementation/​ExportClient/​OtlpGrpcExportClient.cs Adds Zstandard framing and gRPC rejection detection.
src/​OpenTelemetry.Exporter.OpenTelemetryProtocol/​Implementation/​ExportClient/​OtlpExportClient.cs Centralizes fallback state and compressor shutdown.
src/​OpenTelemetry.Exporter.OpenTelemetryProtocol/​.publicApi/​Stable/​PublicAPI.Unshipped.txt Registers the new public enum value.

Review effort: Balanced

- 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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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.

1 open issue
2 previously missed issues

In code that hasn't changed since last review

Medium severity Shut down the zstd encoder after permanently switching to gzip

src/​OpenTelemetry.Exporter.OpenTelemetryProtocol/​Implementation/​ExportClient/​OtlpExportClient.cs:154

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.

Low severity Document zstd in the Unreleased changelog

src/​OpenTelemetry.Exporter.OpenTelemetryProtocol/​OtlpExportCompression.cs:39

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.

3 resolved since last review

Review effort: Balanced

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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium severity Release the Zstandard encoder when permanently downgrading to GZip

src/​OpenTelemetry.Exporter.OpenTelemetryProtocol/​Implementation/​ExportClient/​OtlpExportClient.cs:154

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.

1 resolved since last review

Review effort: Balanced

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>
Update expected event IDs.
@martincostello martincostello added needs-spec-change Issues which require the OpenTelemetry Specification to clarify or define behavior spec-compliance Issues related to compliance with the OpenTelemetry Specification labels Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

keep-open Prevents issues and pull requests being closed as stale needs-spec-change Issues which require the OpenTelemetry Specification to clarify or define behavior perf Performance related pkg:OpenTelemetry.Exporter.OpenTelemetryProtocol Issues related to OpenTelemetry.Exporter.OpenTelemetryProtocol NuGet package spec-compliance Issues related to compliance with the OpenTelemetry Specification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants