Commit a2abe6b
Add incremental query metrics primitives to the base package (DataDog#24791)
* Copy the Postgres incremental query metrics primitives into base
MySQL needs DeltaDetector and ObfuscationLookup too. Copy them verbatim
first so the rest of the stack reads as a diff against the reviewed
Postgres code; git diff --no-index against the originals prints nothing.
Tests are lifted from postgres/tests/test_statements_v2.py with their
bodies unchanged. Postgres keeps its own copies until a follow-up PR.
* Generalize ObfuscationLookup over the statement key type
The cache never interprets its key, so PgssKey was incidental; MySQL
identifies a statement by a digest string. Make the class generic over K
and rename queryid_map_size to key_map_size.
The docstring no longer claims a rejection is permanent. That holds for
a MySQL digest, not in general, so the caller decides.
* Generalize DeltaDetector over the row key
Take a key callable instead of reading queryid, dbid and userid off each
row. PgssKey is gone and DeltaResult now exposes changed_keys and
vanished_keys.
The two DeltaResult field docstrings were also attached to the wrong
fields.
* Stop mutating the caller's rows when collapsing duplicates
The first row of each duplicate group was stored by reference and then
summed into, so the caller's snapshot came back rewritten: given rows
with calls=8 and calls=7, the first was left holding 15. Copy instead.
* Add a maxsize property that trims when the cache shrinks
Callers resize the cache from a server setting on every collection, and
with no public setter Postgres assigns _maxsize directly. That skips
trimming, so lowering the bound evicts nothing until a later populate.
* Return obfuscation failures from populate
A statement the obfuscator rejects was skipped silently and stayed a
miss, so every collection in which it changed fetched and re-attempted
it. Hand the failed keys back so callers can negative-cache them.
* Extract a module-level obfuscate_statement
Obfuscation was only reachable through the cache, which assumes a key
determines its text. MySQL's prepared_statements_instances is keyed on a
reusable address, so its rows must be obfuscated afresh each cycle.
* Add resolve_obfuscations to own the cache miss path
Postgres and MySQL wrap the cache in the same sequence, whose ordering
constraints fail quietly when broken. TextDisposition also makes the
integration say whether a rejected text is permanent (DDIGNORE, EXPLAIN)
or transient (<insufficient privilege>), which a chain of continue
statements blurs.
Emits no telemetry: counts come back in ResolveStats for the caller to
report under its own metric names.
* Add changelog entry
Co-authored-by: Cursor <cursoragent@cursor.com>
* Drive cache retention from the live keys, not the vanished ones
resolve_obfuscations bound the delta key and the cache key to one type
variable, but MySQL keys counters on (schema, digest) and caches on the
digest alone. Projecting vanished_keys down to a digest drops entries
another schema is still running, and since eviction precedes the lookup
the digest is re-fetched in the same cycle. Against a live MySQL 8.4 with
one statement shared by two schemas, the obvious projection re-fetched
the shared digest on 4 of 8 collections; the live set makes that 1.
So evict() becomes retain(live_keys) and a caller can only misstate what
is present, which shows up in the hit rate, rather than silently discard
an entry that is still needed. Sweeping the whole cache costs 0.55 ms at
maxsize=10000, and the count comes back as ResolveStats.dropped.
Retention no longer depends on the delta, so DeltaResult.vanished_keys
goes, along with a set difference the detector computed twice.
The docstrings claimed eviction stopped a returning key being served a
result cached against its previous incarnation. Neither source can do
that: a queryid and a digest are both derived from the normalized
statement, so a key cannot name two texts. Retention reclaims memory, and
saying so is what makes it clear it has to run on quiet collections too.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Group the primitives into a query_metrics package and rename for clarity
The two modules were named for their mechanisms rather than their subject, which
left the vocabulary inconsistent with the rest of DBM and made the classification
enum read as an instruction to the base library rather than a description of what
the integration saw.
- db/query_metrics/ now holds the set, split into stats, obfuscation, cache and
resolver. The package re-exports the public names lazily, mirroring db/__init__.py,
so the file layout is not part of the interface.
- DeltaDetector.compute() becomes QueryStats.diff(snapshot), returning a Delta.
"Query stats" is what every source calls itself (pg_stat_statements,
dm_exec_query_stats, $queryStats), and it keeps stats (what the database exposes)
distinct from metrics (what we emit). derivative_rows becomes rows, and
metric_columns becomes counter_columns to match.
- TextDisposition becomes TextKind, with STATEMENT/EXCLUDED/UNAVAILABLE replacing
CACHE/IGNORE/SKIP. Integrations now report what a text turned out to be and the
resolver owns the caching policy those kinds imply, rather than each integration
re-deriving whether a rejection is permanent. The kinds split on whether the text
is the statement's own and, if not, whether that can change; text that is
permanently unavailable would need a kind of its own.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Keep derivative_rows as the name of the delta rows
rows is an actual pg_stat_statements counter column, so Delta.rows produced
expressions like delta.rows[0]['rows'] at the places worth reading carefully.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Add DBM as a codeowner for the query_metrics package
* Reclaim obfuscation results whose last key is gone
retain() dropped the key mappings but left the results they named, and the
results are what hold the obfuscated text, so nothing that costs memory was
reclaimed. Because only an overflow evicts a result, the stranded ones
accumulated until the result tier sat at maxsize however small the live set
was, and then displaced results of statements still live but not recently run.
With four keys per signature and two live signatures, a 50-entry cache filled
from cycle 48 and evicted a statement present in every snapshot.
Keys also leave by LRU trimming, so the sweep runs on every retain rather than
only when a stale key was dropped. At maxsize=10000 with nothing shared it
costs 0.66 ms, against 0.40 ms for the stale key set it follows.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>1 parent 4e724a0 commit a2abe6b
13 files changed
Lines changed: 1295 additions & 0 deletions
File tree
- .github
- datadog_checks_base
- changelog.d
- datadog_checks/base/utils/db/query_metrics
- tests/base/utils/db/query_metrics
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
| 157 | + | |
| 158 | + | |
157 | 159 | | |
158 | 160 | | |
159 | 161 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
Lines changed: 6 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
Lines changed: 19 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 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
Lines changed: 247 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 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
Lines changed: 51 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 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
0 commit comments