Skip to content

Make TagMap a facade over an id store and a name store (dense store - part 6) - #13007

Draft
dougqh wants to merge 10 commits into
dougqh/tagmap-store-classesfrom
dougqh/tagmap-store-facade
Draft

dougqh wants to merge 10 commits into
dougqh/tagmap-store-classesfrom
dougqh/tagmap-store-facade

Conversation

@dougqh

@dougqh dougqh commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

What Does This Do

TagMap keeps known tags in an IdStore (by id) and custom tags in a HashStore (by name), instead of one bucket array.

  • A name is classified once, at the boundary, and routed to its store. Reads by id go straight to the id store.
  • Read-through works per store. Known and custom tags never share a key, so precedence never crosses stores.
  • The name store is created lazily, on the first custom write. Until then, a map borrows its read-through parent's store read-only.
  • stream() and spliterator() yield stable Entrys, and getEntryReader gives a momentary reader for typed reads.
  • TagMap.Internals stays for now, writing straight to the id store. It goes away in the next PR, when the span owns its stores.
  • Store access is restricted to core. The forbidden-API filter blocks instrumentation from using the stores and TagMap's new view methods.

Motivation

This is the second of three PRs that replace the span's single TagMap with two stores split by tag kind. TagMap had picked up name canonicalization, known-versus-custom classification and direction awareness. Moving storage into the stores leaves TagMap a thin facade, and lets the span use the same stores directly in the next PR.

Additional Notes

  • No behavior change. The TagMap suites pass as they are, including the Internals tests, which now exercise the shim. Entry objects are still shared across copies.
  • TagStoresTest covers the stores through TagMap, and IdStoreContractTest gains the view cases.
  • Tests: internal-api and dd-trace-core pass. The only failures are the two TracerConnectionReliabilityTest cases, which need Docker.

Where this is going

This PR is one step in replacing the span's single TagMap with storage that knows each tag's kind. The goal is to cut what every span allocates on the application thread, without changing behavior.

The end state

Later, not in this stack: cheaper read-through to parent spans, trace-level tags in a per-trace holder (TraceSegment), and primitive tag values without boxing.

The stack. This replaces #12918, split into pieces that can each be reviewed, and measured, on their own.

PR Step Allocation per 7-span trace (flag off unless noted)
#12999 A null tag value removes the tag in TagMap. This is the stack's only behavior change, kept separate on purpose. unchanged
#12992 Bucket known tags by slot in TagMap, and add TraceShapeBenchmark −96 B vs master
#13001 TagStore for the eager post-processors unchanged
#13002 Extract TagMap's bucket mechanics into HashSupport (a pure move) unchanged
#13006 Add the id and name tag stores, not yet used unchanged
#13007 Make TagMap a facade over the two stores unchanged
#13003 Have the span own its tag stores directly +104 B (+1.3%)
#13004 Dense id store behind trace.experimental.dense.tags.enabled (off by default) flag on: −30.6%
#12926 Turn the dense store on by default, add sizing hints and bulk prototype seeding flag on: about 3% less again

Every PR after #12999 is behavior-preserving.

The pieces' allocation figures come from a deterministic per-thread probe (ThreadMXBean allocated bytes) that builds the same 7-span trace as TraceShapeBenchmark. The #12992 and #12926 figures come from TraceShapeBenchmark itself.

🤖 Generated with Claude Code

@datadog-datadog-prod-us1-2

This comment has been minimized.

@dougqh dougqh changed the title Make TagMap a facade over an id store and a name store Make TagMap a facade over an id store and a name store (dense store - part 6) Oct 23, 2026
@dd-octo-sts

dd-octo-sts Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 13.98 s 13.91 s [-0.3%; +1.3%] (no difference)
startup:insecure-bank:tracing:Agent 12.97 s 12.97 s [-0.9%; +1.0%] (no difference)
startup:petclinic:appsec:Agent 17.23 s 17.18 s [-0.6%; +1.2%] (no difference)
startup:petclinic:iast:Agent 17.22 s 17.27 s [-1.1%; +0.6%] (no difference)
startup:petclinic:profiling:Agent 16.87 s 17.01 s [-1.9%; +0.3%] (no difference)
startup:petclinic:sca:Agent 17.21 s 17.22 s [-0.9%; +0.8%] (no difference)
startup:petclinic:tracing:Agent 16.32 s 16.20 s [-0.1%; +1.6%] (no difference)

Commit: faa11220 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

@bric3
bric3 added this pull request to stack #13019 October 23, 2026 16:02
@dougqh
dougqh force-pushed the dougqh/tagmap-store-facade branch from 75ec723 to 77b8ac8 Compare October 23, 2026 16:57
TagMap keeps known tags in an IdStore (by id) and custom tags in a
HashStore (by name), instead of one bucket array. A name is classified
once, at the boundary, and routed to its store; known and custom tags
never share a key, so read-through and precedence work per store and
never cross between them. The name store is created on the first custom
write; until then a map borrows its read-through parent's store
read-only.

Behavior is unchanged: the TagMap suites pass as they are, and Entry
objects are still shared across copies. The span still holds a single
TagMap; it starts owning the stores directly in the next change.
TagMap.Internals stays for now, writing straight to the id store, and
goes away with that change.

Reads by id go straight to the id store. stream() and spliterator()
yield stable Entries, and getEntryReader gives a momentary reader for
typed reads. TagStoresTest covers the stores through TagMap, and
IdStoreContractTest gains the view cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dougqh
dougqh force-pushed the dougqh/tagmap-store-facade branch from 77b8ac8 to 8a02c1d Compare October 26, 2026 15:04
dougqh and others added 8 commits October 26, 2026 12:59
TagMap's id loads went through getEntry, which a store without an entry
per tag answers by building one. Read the value from the id store
directly, as the name loads already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…acade

# Conflicts:
#	internal-api/src/main/java/datadog/trace/api/TagMap.java

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: core Tracer core tag: ai generated Largely based on code generated by an AI or LLM tag: no release notes Changes to exclude from release notes type: refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant