Skip to content

MCP budget: shrink get_query_heatmap's default cell cap and text preview (#4198) - #4259

Merged
erikdarlingdata merged 7 commits into
devfrom
fix/4198-heatmap-default
Sep 25, 2026
Merged

erikdarlingdata merged 7 commits into
devfrom
fix/4198-heatmap-default

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Part of #4198.

Why

get_query_heatmap (Darling and Lite) measured 144,757 bytes / 1,763 ms at default arguments on a busy
production server. It was the largest #4198 offender after the Query Store regressions tool. The shared
budget (McpResponseBudget.DefaultBytes) is 32 KB.

A heatmap payload is CELLS (time bins times magnitude buckets) plus TEXT. I measured which one dominates
first. At 500 cells (the old default), the five fixed per-cell fields alone run to roughly 75 KB before a
single byte of query text. That is more than double the budget. Shrinking the text preview alone did not
fix this. The cell cap came down regardless of text width.

What changes

Both products, identically:

  • Default cell cap: 500 to 100. 100 cells covers 14-plus fully-populated 5-minute bins with all seven
    magnitude buckets. That is most of a business day on a server whose queries land in two or three
    buckets. Raising bucket_minutes still covers more of the window in the same cell count.
  • Default top_query_text preview: 120 to 80 characters. This alone barely moves the total, since the fixed
    per-cell structure dominates, as measured above.
  • New top_query_text_truncated field on every cell: true whenever the stored statement is longer than the
    preview shown, at either preview length.
  • New full_text argument (default false): returns each cell's top query at full length instead of the
    80-character preview. The full length is bounded at 32,000 characters. No real T-SQL statement reaches
    that. An explicit ask still gets what it asks for, per the MCP read tools have no default response-size budget: at default arguments 12 tools return >50 KB and 3 return >100 KB for one server, more than an agent client's per-result cap #4198 ruling, even past the budget.
  • New full_text field echoed at the top level so a caller who never inspects a cell still learns a second
    call gets more.
  • The SQL-level LEFT(query_text, 120) becomes a bound parameter on both SKUs (Postgres date_bin/DuckDB
    time_bucket queries). It fetches preview-length-plus-one so the extra character is the truncation
    signal. This is the same over-fetch-by-one idiom the cell cap already used. No second round trip, no
    computed LENGTH() column.
  • McpPayloadContractCensusTests (Darling): top_query_text_truncated is a genuinely new kind of cut this
    census had no class for. The full text is already in the store. A caller opts back in with full_text
    rather than re-paging. That is the opposite direction from a source-side cut where nothing is left to
    fetch. Added a new ReadSidePreviewCutKeys roster. The existing classes do not fit this shape.
  • McpToolsListBudget pins (both SKUs): added the full_text parameter line (94 bytes) and raised each
    product's TotalCeilingBytes by the measured +147-byte growth.
  • Not on A budget pin over every MCP read tool (part of #4198) #4224's ExemptOffenders roster, so McpReadToolBudgetLiveTests was left untouched, as directed.
  • Query heatmap resolves and sorts query text for every query-stats row in the window, then keeps it for ~1 in 125: 613 ms vs 266 ms with the text fetched for the winners only (WPF viewer and get_query_heatmap) #4233 (the WPF/web heatmap panel fetching full text for every row before keeping 1,431 client-side) is a
    separate, unapproved issue. Out of scope here, not touched.

Measured bytes

New live/DuckDB tests seed 60 fully-populated 5-minute bins: 420 cells, well past both the old and new cap.
Each cell has a realistic 227-character two-table-join statement and an 18-character hash
(CONVERT(varchar(64), …, 1)'s real width). This reproduces the reported shape rather than a minimal one.

Darling (Postgres, my rig) Lite (DuckDB)
Before (reported, production) 144,757 bytes same tool/shape
After, default (100-cell cap, truncated at cap to 98 whole bins) 27,610 bytes asserted < 32,768, same assertions
After, full_text=true 42,603 bytes (explicit ask, over budget by design) asserted > default

Default stays under the 32,768-byte budget with about 5,150 bytes (15.7%) of headroom.

I proved the regression test fails on the pre-fix code. I reverted the two source files, rebuilt, and
confirmed the new test does not compile against the old 7-parameter signature. Reapplied, and it passes.

Test plan

  • Darling.Tests full suite once, on a fresh UTC rig (port 55976): 13,844 total, 4 failed at first pass.
    One failure (McpPayloadContractCensusTests) was mine: the new field needed classifying, fixed and
    re-verified in isolation (68/68 pass). The other three - CaptureDownChunkOrderTests,
    ServerListAndSummaryPlanShapeTests, TrendPayloadBudgetLiveTests - fail in isolation on this rig. They
    touch no file this PR changes (TimescaleDB chunk-pruning plan shape and a trend tool's stream read).
    Dev's latest completed Build run (36103277984) was green. They look environmental: a dozen MCP read tools have no default response-size budget: at default arguments 12 tools return >50 KB and 3 return >100 KB for one server, more than an agent client's per-result cap #4198 lanes
    ran a full Postgres rig with 74 background workers on this machine tonight. I did not re-run the full
    suite a third time after the census fix. The deadline did not allow it. Flagging for the coordinator to
    recheck on a quieter machine.
  • Lite.Tests full suite once: 5,338 total, 0 failed.
  • New DarlingQueryHeatmapBudgetLiveTests and QueryHeatmapBudgetTests (Lite) both green, plus the
    existing DarlingQueryHeatmapTests (36/36) and McpToolsListBudgetTests (both SKUs) green.
  • I did not touch or run McpReadToolBudgetLiveTests (not on the A budget pin over every MCP read tool (part of #4198) #4224 roster for this tool).
  • Installer.Tests not run (out of scope per lane rules).

CHANGELOG entry

SECTION: Fixed
ENTRY:

Generated with Claude Code

https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ

erikdarlingdata and others added 2 commits September 25, 2026 02:46
Part of #4198. A default call on a busy server measured 144,757 bytes -
4.4x the shared 32 KB budget - because 500 cells alone, before any query
text, already ran past it. Cuts the default cell cap to 100 and the
per-cell query text preview to 80 characters, adds a per-cell
top_query_text_truncated flag and a full_text opt-in for the whole
statement, on both Darling and Lite.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
Part of #4198. top_query_text_truncated is a read-side preview cut (the
full text is already in the store; full_text opts back into it), not a
page cut or a source-side one, so it needed its own roster class rather
than joining an existing one that would have misdescribed it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
@erikdarlingdata erikdarlingdata changed the title DO NOT MERGE (MCP budget): shrink get_query_heatmap's default cell cap and text preview under budget (#4198) (MCP budget): shrink get_query_heatmap's default cell cap and text preview under budget (#4198) Sep 25, 2026
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 25, 2026 07:44
@erikdarlingdata erikdarlingdata changed the title (MCP budget): shrink get_query_heatmap's default cell cap and text preview under budget (#4198) MCP budget: shrink get_query_heatmap's default cell cap and text preview (#4198) Sep 25, 2026
erikdarlingdata and others added 5 commits September 25, 2026 03:47
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
Both heatmap (#4198, +147 bytes) and audit_config (#4192/#4195/#4193/#4217,
+82 bytes) independently bumped TotalCeilingBytes from the same base.
Merged value: 171_637 + 147 + 82 = 171_866.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
All deltas: audit_config +82, deadlock_detail +364, plan_corrections +137,
heatmap +147 (Darling); deadlock_detail +253, plan_corrections +137,
heatmap +147 (Lite). Census uses FieldPreviewCutKeys (dev's rename).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
ReadSidePreviewCutKeys (branch's own class, not in Concat chain) held
top_query_text_truncated for get_query_heatmap. Merged into FieldPreviewCutKeys
and removed the unused ReadSidePreviewCutKeys declaration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant