Skip to content

Commit d6e7088

Browse files
dougqhclaude
andcommitted
Add a two-tier presence bloom to the dense tag store
Replace the dense store's linear knownIndexOf scan gate with a two-tier presence filter driven by the tag-id's declaration coordinate: a per-map knownGroupMask (1L << group-decl) checked first, then a knownFieldMask (field-decl & 63). Either bit clear proves the tag definitely absent, so an append is O(1) and a read-through parent whose groups are disjoint from ours never pays a scan. The scan stays authoritative (superset semantics); the masks are never cleared on removal (a stale-set bit only costs a scan, whereas clearing could drop a bit still shared via collision -> false negative). Splits the tag-id encoding into group-decl (6 bits) + field-decl (10 bits) in KnownTagCodec (dropping nameHash from known ids) so the coordinate feeds the filter, and carries an interim hand-maintained coordinate KnownTags table (the tag-registry generator replaces it with a byte-equal generated one in a follow-up). Read-through stays chain-aware: parentDenseVisible prunes each ancestor level's scan with that level's own two-tier filter. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d758efe commit d6e7088

6 files changed

Lines changed: 654 additions & 366 deletions

File tree

internal-api/src/main/java/datadog/trace/api/KnownTagCodec.java

Lines changed: 80 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,27 @@ public static boolean isActive() {
2222
}
2323

2424
/*
25-
* tagId bit layout: [63 intercepted] [62-48 globalSerial (15 bits)] [47-32 fieldPos]
26-
* [31-0 nameHash]. Bit 63 (the sign bit) marks a tag the tag interceptor must see, so the check
27-
* is a single {@code tagId < 0}. globalSerial is globally unique per known tag; fieldPos is its
28-
* slot in the global positional layout (TagMap.knownEntries index); nameHash is
29-
* TagMap.Entry#_hash(name) and is layout-independent. Unknown (string-only) tags have the upper
30-
* 32 bits zero. NOTE: TagMap.Entry decodes nameHash inline as (int) tagId on its hot path, so the
31-
* low-32 encoding here must stay in sync with that.
25+
* tagId bit layout: [63 intercepted] [62-48 globalSerial (15 bits)] [47-42 group-decl (6 bits)]
26+
* [41-32 field-decl (10 bits)] [31-0 reserved, zero]. Bit 63 (the sign bit) marks a tag the tag
27+
* interceptor must see, so the check is a single {@code tagId < 0}. globalSerial is globally
28+
* unique per known tag. The middle 16 bits carry the tag's DECLARATION coordinate: group-decl is
29+
* the declaration group (the trace-level tier / each span type / each mixin is a group),
30+
* field-decl is the tag's ordinal within that group (restarts per group). Together they drive the dense
31+
* store's two-tier presence fast path (per-map group mask, then field bloom) — see {@link
32+
* TagMap}. The low 32 bits are unused for known ids (the whole id is fully determined by
33+
* serial + coordinate, so the generator can emit a literal); they are free for a wider
34+
* group-decl/field-decl split later. Unknown (string-only) custom tags are NOT known ids — they
35+
* key off {@code TagMap.Entry#_hash(name)} in their own bucket path and never enter here.
3236
*/
3337
public static int globalSerial(long tagId) {
3438
return (int) ((tagId >>> 48) & 0x7FFF);
3539
}
3640

3741
/**
38-
* Flag bit (the sign bit) marking a tag the tag interceptor must process — reserved/"virtual"
39-
* tags AND intercepted-but-stored tags (e.g. http.method, which the interceptor side-effects and
40-
* also stores). Encoded in the id so {@code DDSpanContext.setTag(long)} can route with a single
41-
* sign test ({@link #isIntercepted}) instead of resolving the name. Non-intercepted tags (peer.*,
42+
* Flag bit (the sign bit) marking a tag the tag interceptor must process — reserved tags AND
43+
* intercepted-but-stored tags (e.g. http.method, which the interceptor side-effects and also
44+
* stores). Encoded in the id so {@code DDSpanContext.setTag(long)} can route with a single sign
45+
* test ({@link #isIntercepted}) instead of resolving the name. Non-intercepted tags (peer.*,
4246
* base.service, …) leave it clear and take the fast store path. Must agree with the interceptor's
4347
* name-based {@code needsIntercept} for every assigned id.
4448
*/
@@ -54,25 +58,54 @@ public static long intercepted(long tagId) {
5458
return tagId | INTERCEPTED;
5559
}
5660

57-
public static int fieldPos(long tagId) {
58-
return (int) ((tagId >>> 32) & 0xFFFF);
61+
// The middle 16 bits [47-32] hold the declaration coordinate, split so the two-tier presence
62+
// fast path can read each half cheaply: group-decl (high 6 bits, [47-42]) selects the tag's
63+
// declaration group (<=64 groups -> one long group mask, where a clear bit proves the whole
64+
// group absent and enables a bulk fast insert); field-decl (low 10 bits, [41-32]) is the tag's
65+
// ordinal within that group (<=1024 tags/group), which the field bloom hashes (field-decl & 63)
66+
// once a group is present. See TagMap's dense-store fast path.
67+
static final int GROUP_DECL_SHIFT = 42;
68+
static final int GROUP_DECL_MASK = 0x3F; // 6 bits
69+
static final int FIELD_DECL_SHIFT = 32;
70+
static final int FIELD_DECL_MASK = 0x3FF; // 10 bits
71+
72+
/**
73+
* The tag's declaration group: the trace-level tier, a span type, or a mixin. Assigned by the tag
74+
* registry; drives the two-tier presence fast path's group mask.
75+
*/
76+
public static int groupDecl(long tagId) {
77+
return (int) ((tagId >>> GROUP_DECL_SHIFT) & GROUP_DECL_MASK);
5978
}
6079

61-
public static int nameHash(long tagId) {
62-
return (int) tagId;
80+
/** The tag's ordinal within its declaration group. */
81+
public static int fieldDecl(long tagId) {
82+
return (int) ((tagId >>> FIELD_DECL_SHIFT) & FIELD_DECL_MASK);
6383
}
6484

6585
/**
66-
* globalSerial partition. {@code [1, FIRST_STORED_SERIAL)} is reserved for "virtual" tags that
67-
* are specially handled (redirected to span fields or processed by the tag interceptor) and are
68-
* NOT stored in the TagMap — these are hand-assigned in tracer core. {@code [FIRST_STORED_SERIAL,
69-
* ..]} is for generated convention tags that ARE stored (slotted/bucketed). {@code globalSerial
70-
* == 0} means unknown / string-only. Both core and the code generator must agree on this
71-
* boundary.
86+
* globalSerial partition. {@code [1, FIRST_STORED_SERIAL)} is the RESERVED tier and {@code
87+
* [FIRST_STORED_SERIAL, ..]} is the STORED tier; {@code globalSerial == 0} means unknown /
88+
* string-only. Both core and the code generator must agree on this boundary.
89+
*
90+
* <p><b>Reserved</b> is the shared mechanism: the tracer reserves the key and handles it itself
91+
* instead of putting it in the TagMap. It says nothing about whether a value exists — that splits
92+
* into two kinds (the {@code kind:} in the overlay):
93+
*
94+
* <ul>
95+
* <li><b>structural</b> — the value <i>does</i> exist, it just lives in a first-class
96+
* span/trace field (service, resource.name, error, span.type, origin), not the tag map.
97+
* <li><b>directive</b> — there is <i>no</i> stored value; the key is a command that triggers
98+
* trace behavior (sampling.priority, manual.keep, measured).
99+
* </ul>
100+
*
101+
* "virtual" over-claims non-existence (wrong for structural) and "built-in" over-claims existence
102+
* (wrong for directive), so the tier is named for the mechanism they share: reserved. These are
103+
* hand-assigned in the overlay. <b>Stored</b> tags are the generated convention tags that ARE put
104+
* in the map (slotted/bucketed).
72105
*/
73106
public static final int FIRST_STORED_SERIAL = 256;
74107

75-
/** True if the tagId names a reserved "virtual"/specially-handled tag (not stored in the map). */
108+
/** True if the tagId names a reserved (structural/directive) tag — handled, not stored. */
76109
public static boolean isReserved(long tagId) {
77110
int globalSerial = globalSerial(tagId);
78111
return globalSerial > 0 && globalSerial < FIRST_STORED_SERIAL;
@@ -84,48 +117,53 @@ public static boolean isStored(long tagId) {
84117
}
85118

86119
/**
87-
* Sentinel {@code fieldPos} meaning "no positional slot". It is the maximum value the 16-bit
88-
* fieldPos field can hold, so it always compares {@code >= slotCount()} and routes to the hash
120+
* Sentinel {@code field-decl} meaning "no positional slot". It is the maximum value the 10-bit
121+
* field-decl field can hold, so it always compares {@code >= slotCount()} and routes to the hash
89122
* buckets rather than the fast positional array. Two kinds of tagId use it:
90123
*
91124
* <ul>
92-
* <li>Reserved/virtual tags ({@code globalSerial < FIRST_STORED_SERIAL}) — not stored at all;
93-
* the sentinel just guarantees an incidental store never lands in a slot.
125+
* <li>Reserved tags ({@code globalSerial < FIRST_STORED_SERIAL}) — not stored at all; the
126+
* sentinel just guarantees an incidental store never lands in a slot.
94127
* <li>Unslotted stored tags ({@code globalSerial >= FIRST_STORED_SERIAL}) — "low-priority" tags
95128
* that get a stable id (and so {@code keyOf}/{@code nameOf} unification with their string
96-
* form) but are deliberately not given a slot, so they live in the buckets and don't widen
97-
* {@code knownEntries[]} for every span. {@code getEntry(long)} for these resolves the name
98-
* and rehashes — the cost of not owning a slot.
129+
* form) but are deliberately not given a slot, so they live in the buckets. {@code
130+
* getEntry(long)} for these resolves the name and rehashes — the cost of not owning a slot.
99131
* </ul>
100132
*/
101-
public static final int NO_SLOT = 0xFFFF;
133+
public static final int NO_SLOT = FIELD_DECL_MASK; // field-decl all-ones sentinel
102134

103135
/**
104136
* True if the tagId names a stored tag that deliberately has no positional slot (bucket-only).
105137
*/
106138
public static boolean isUnslotted(long tagId) {
107-
return isStored(tagId) && fieldPos(tagId) == NO_SLOT;
139+
return isStored(tagId) && fieldDecl(tagId) == NO_SLOT;
108140
}
109141

110142
/**
111-
* Builds a tagId from its parts: {@code globalSerial} (globally unique per known tag), {@code
112-
* fieldPos} (the tag's slot within its span type's positional table), and the tag {@code name}
113-
* (whose hash is computed via the same function the runtime uses, so the low 32 bits match {@link
114-
* TagMap.Entry#hash()}). Inverse of {@link #globalSerial}/{@link #fieldPos}/{@link #nameHash}.
143+
* Builds a tagId from all three parts: {@code globalSerial} (globally unique per known tag),
144+
* {@code groupDecl} (its declaration group), and {@code fieldDecl} (its ordinal within that
145+
* group). The low 32 bits are zero, so the id is fully determined by these parts — the generator
146+
* emits it as a literal. Inverse of {@link #globalSerial}/{@link #groupDecl}/{@link #fieldDecl}.
115147
* Intended for the code generator and tests.
116148
*/
117-
public static long tagId(int globalSerial, int fieldPos, String name) {
118-
long nameHash = TagMap.Entry._hash(name) & 0xFFFFFFFFL;
119-
return ((long) globalSerial << 48) | ((long) (fieldPos & 0xFFFF) << 32) | nameHash;
149+
public static long tagId(int globalSerial, int groupDecl, int fieldDecl) {
150+
return ((long) globalSerial << 48)
151+
| ((long) (groupDecl & GROUP_DECL_MASK) << GROUP_DECL_SHIFT)
152+
| ((long) (fieldDecl & FIELD_DECL_MASK) << FIELD_DECL_SHIFT);
153+
}
154+
155+
/** Builds a tagId in group 0 — shorthand for {@link #tagId(int, int, int)} with group-decl 0. */
156+
public static long tagId(int globalSerial, int fieldDecl) {
157+
return tagId(globalSerial, 0, fieldDecl);
120158
}
121159

122160
/**
123-
* Builds a tagId with no positional slot ({@code fieldPos == }{@link #NO_SLOT}). Use for reserved
124-
* "virtual" tags and for "low-priority" stored tags that get a stable id but are intentionally
161+
* Builds a tagId with no positional slot ({@code field-decl == }{@link #NO_SLOT}). Use for
162+
* reserved tags and for "low-priority" stored tags that get a stable id but are intentionally
125163
* kept out of the fast slot array (they route to the hash buckets). See {@link #NO_SLOT}.
126164
*/
127-
public static long tagId(int globalSerial, String name) {
128-
return tagId(globalSerial, NO_SLOT, name);
165+
public static long tagId(int globalSerial) {
166+
return tagId(globalSerial, NO_SLOT);
129167
}
130168

131169
// Number of positional slots in the global layout = (max stored fieldPos) + 1, declared by the

0 commit comments

Comments
 (0)