Commit 33380a2
authored
DSM overhead optimizations (#8450)
# DSM Per-Message Overhead Optimizations
## Summary of changes
- **Edge-tag array caching**: Introduced `EdgeTagCache<TKey>` and
`BacklogTagCache<TKey>` — process-wide, per-type `ConcurrentDictionary`
caches that intern edge-tag arrays and backlog-tag strings so they are
only allocated once per unique key (topic/group/cluster combination).
- **Node-hash caching**: Added a `NodeHashCacheEntry`/`NodeHashSnapshot`
mechanism inside `DataStreamsManager` that memoizes the expensive
`CalculateNodeHash` result per `(edgeTags[], nodeHashBase)` pair. Reads
are lock-free via a volatile field; writes acquire a per-entry lock only
on cache miss or base change.
- **Zero-allocation context encode/decode (net core 3.1+)**: Added
`PathwayContextEncoder.EncodeInto` and a `Span<byte>`-based `Decode`
overload; `DataStreamsContextPropagator` uses `stackalloc` buffers on
.NET Core 3.1+ to avoid intermediate `byte[]` heap allocations on every
produce/consume.
- **Reference-equality dictionary comparers**: `DataStreamsAggregator`
and `DataStreamsManager._nodeHashCache` now use reference-equality
comparers backed by `RuntimeHelpers.GetHashCode`, which is safe because
all keys are interned by the caches above.
- **Drain-signal instead of sleep**: Replaced the 10 ms `Thread.Sleep`
polling loop in `DataStreamsWriter` with a `ManualResetEventSlim` that
wakes immediately when the queue reaches 1 000 items or after a 500 ms
timeout, eliminating unnecessary context switches.
- **Integration-specific cache-key structs**: Added `readonly struct`
cache keys (`ConsumeEdgeTagCacheKey`, `ProduceEdgeTagCacheKey`,
`CommitBacklogTagCacheKey`, `ProduceBacklogTagCacheKey`) for Kafka;
equivalent structs for AWS SQS/SNS/Kinesis, Azure Service Bus, IBM MQ,
and RabbitMQ.
- **Minor hot-path fix (Kafka)**: The
`Remove(TemporaryBase64PathwayContext)` header scan is now skipped when
`KafkaCreateConsumerScopeEnabled=true` (the default), avoiding an O(n)
scan on every message.
- **`LastConsumePathway` guard removed**: Dropped the redundant `!=
null` guard on the produce path that required an `AsyncLocal` read
before the actual `AsyncLocal` read.
## Reason for change
DSM instrumentation runs on the hot path of every instrumented message.
Profiling revealed that the dominant allocations were:
1. A new `string[]` edge-tag array on every produce/consume call.
2. A `CalculateNodeHash` call (hashing over all edge tags) on every
checkpoint.
3. Intermediate `byte[]` arrays for pathway context Base64
encoding/decoding.
4. Unnecessary CPU spin from a fixed 10 ms sleep between drain cycles.
These optimizations target p99 and throughput benchmarks for Kafka, SQS,
SNS, RabbitMQ, IBM MQ, Azure Service Bus, and Kinesis instrumentation.
## Implementation details
### Caching strategy
`EdgeTagCache<TKey>` and `BacklogTagCache<TKey>` use the
static-generic-class pattern (`static class Foo<T>` with a static field)
to give each integration its own dictionary instance without any runtime
dispatch. The key type is a `readonly struct` implementing
`IEquatable<TKey>`, which prevents boxing in `ConcurrentDictionary`
lookups.
The caches are bounded at `MaxEdgeTagCacheSize = 1000` entries. Once
that limit is reached, new keys are computed on the fly (no caching) to
prevent unbounded memory growth from high-cardinality identifiers.
### Node-hash caching
`_nodeHashCache` is keyed by `string[]` **identity** (not value
equality) because the arrays themselves are interned by
`EdgeTagCache<TKey>`. Each entry holds a volatile `NodeHashSnapshot`
(`nodeHashBase` + `NodeHash`). On every checkpoint:
1. Look up the array reference — O(1) identity hash.
2. Read the volatile snapshot — lock-free.
3. If the base matches, return immediately.
4. Otherwise, acquire the per-entry lock, double-check, compute, and
publish a new snapshot.
### Zero-allocation encode/decode
`PathwayContextEncoder.EncodeInto(PathwayContext, Span<byte>)` writes
directly into a caller-supplied buffer. `DataStreamsContextPropagator`
stackallocs `MaxEncodedSize` (26 bytes) and `MaxBase64EncodedSize` (36
bytes) on the stack and uses `Base64.EncodeToUtf8`/`DecodeFromUtf8`
in-place. The only unavoidable allocation is the final `ToArray()`
passed to `headers.Add`, because Kafka takes ownership of the byte
array.
This path is guarded by `#if NETCOREAPP3_1_OR_GREATER`; .NET Framework
falls back to the original heap-allocating path.
### Drain signal
`DataStreamsWriter` previously slept 10 ms unconditionally between drain
iterations, burning CPU and adding ~10 ms latency per batch even under
load. The new `ManualResetEventSlim` is signalled immediately when
either queue exceeds `DrainThreshold` (1 000 items), capping worst-case
latency at `DrainTimeoutMs` (500 ms) while eliminating idle wakeups.
## Test coverage
- `DataStreamsManagerTests`: new unit tests verify that
`GetOrCreateEdgeTags` and `GetOrCreateBacklogTags` return the **same
array/string reference** on repeated calls with the same key, and
distinct references for different keys. Tests cover Kafka
produce/consume, RabbitMQ produce/consume, and generic key types.
- `PathwayContextEncoderTests`: existing encode/decode round-trip tests
pass against the new `Span<byte>` overloads.
- All existing DSM tests continue to pass.
## Other details
- The `MaxEdgeTagCacheSize` constant is `internal` to allow unit tests
to verify the overflow/bypass behavior.
- No public API surface changes; all new types are `internal`.
- `.NET Framework` code paths are unchanged — all `Span`-based
optimizations are gated behind `#if NETCOREAPP3_1_OR_GREATER`.1 parent ac5ec3d commit 33380a2
34 files changed
Lines changed: 694 additions & 70 deletions
File tree
- tracer
- src/Datadog.Trace
- ClrProfiler/AutoInstrumentation
- AWS
- Kinesis
- SNS
- SQS
- Azure/ServiceBus
- IbmMq
- Kafka
- RabbitMQ
- DataStreamsMonitoring
- Aggregation
- Utils
- test/Datadog.Trace.Tests/DataStreamsMonitoring
Lines changed: 5 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
61 | | - | |
| 61 | + | |
62 | 62 | | |
63 | 63 | | |
64 | | - | |
| 64 | + | |
65 | 65 | | |
66 | 66 | | |
67 | | - | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
68 | 70 | | |
69 | 71 | | |
70 | 72 | | |
| |||
tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AWS/Kinesis/GetRecordsAsyncIntegration.cs
Lines changed: 3 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
64 | | - | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
65 | 67 | | |
66 | 68 | | |
67 | 69 | | |
| |||
Lines changed: 3 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
81 | | - | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
82 | 84 | | |
83 | 85 | | |
84 | 86 | | |
| |||
Lines changed: 16 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
Lines changed: 6 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | | - | |
| 37 | + | |
38 | 38 | | |
39 | 39 | | |
40 | | - | |
41 | | - | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
42 | 45 | | |
43 | 46 | | |
44 | 47 | | |
| |||
Lines changed: 14 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
Lines changed: 12 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
69 | | - | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
70 | 72 | | |
71 | 73 | | |
72 | 74 | | |
| |||
81 | 83 | | |
82 | 84 | | |
83 | 85 | | |
84 | | - | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
85 | 92 | | |
86 | 93 | | |
87 | 94 | | |
88 | 95 | | |
89 | 96 | | |
90 | 97 | | |
91 | | - | |
92 | 98 | | |
93 | 99 | | |
94 | 100 | | |
| |||
148 | 154 | | |
149 | 155 | | |
150 | 156 | | |
151 | | - | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
152 | 160 | | |
153 | 161 | | |
154 | 162 | | |
| |||
Lines changed: 16 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
Lines changed: 7 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
37 | 40 | | |
38 | 41 | | |
39 | 42 | | |
| |||
81 | 84 | | |
82 | 85 | | |
83 | 86 | | |
84 | | - | |
85 | 87 | | |
86 | 88 | | |
87 | | - | |
88 | | - | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
89 | 93 | | |
90 | 94 | | |
91 | 95 | | |
| |||
Lines changed: 14 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
0 commit comments