Skip to content

Add typed blocker-side columns to blocking_BlockedProcessReport#996

Merged
erikdarlingdata merged 1 commit into
devfrom
feature/sql-blocked-process-typed-columns
May 26, 2026
Merged

Add typed blocker-side columns to blocking_BlockedProcessReport#996
erikdarlingdata merged 1 commit into
devfrom
feature/sql-blocked-process-typed-columns

Conversation

@erikdarlingdata
Copy link
Copy Markdown
Owner

Summary

Schema-only PR (1 of 2) for the SQL-side blocked-process pair extraction work. Moves blocked-process-report XML parsing from analysis time to collection time so the Dashboard analysis path can read structured columns instead of re-parsing blocked_process_report_xml on every BLOCKING_CHAIN fact (up to 5000 XElement.Parse calls per cycle).

Adds five typed columns to collect.blocking_BlockedProcessReport:

  • blocking_spid integer
  • blocking_last_tran_started datetime2(7)
  • blocking_status nvarchar(10)
  • blocked_sql_text nvarchar(max)
  • blocking_sql_text nvarchar(max)

Names match the equivalent Lite DuckDB columns so the Dashboard and Lite analysis SQL can read identically-named columns.

What's in this PR

  • install/02_create_tables.sql — new columns added inside the existing IF OBJECT_ID(...) IS NULL table-creation guard (fresh-install path).
  • upgrades/2.11.0-to-2.12.0/01_extend_blocked_process_report_columns.sql — new upgrade script. Five idempotent IF NOT EXISTS / ALTER TABLE ADD blocks plus a one-pass backfill of existing activity='blocked' rows.
  • upgrades/2.11.0-to-2.12.0/upgrade.txt — manifest.
  • install/23_process_blocked_process_xml.sqlcollect.process_blocked_process_xml now populates the new typed columns at insert time from the XQuery (//blocked-process-report/blocking-process/process/@…)[1] shape, scoped to the freshly-parsed window. Runs before the is_processed=1 mark so a crash rolls everything back and the raw XML rows stay unmarked for retry.

Wire-format notes

The stored XML is <event>-rooted with the report nested two levels deep at /event/data[@name="blocked_process"]/value/blocked-process-report. Both insertion paths (the raw collector in install/22 and sp_HumanEventsBlockViewer's named-XE path) preserve this wrap. All XQuery in this PR uses the descendant axis //blocked-process-report/... to sidestep the two-level wrap; this was empirically validated against sql2022.PerformanceMonitor (a leading-slash root path returns NULL on every row, descendant axis returns the expected attributes).

Defense-in-depth

If sp_HumanEventsBlockViewer writes rows but the XQuery extraction populates zero typed columns, a TYPED_COLUMNS_EMPTY row is written to config.collection_log so any future wire-format regression surfaces in telemetry instead of silently producing garbage chains in the analysis path.

What's NOT in this PR

C# read-path simplification ships in a follow-up PR — deletion of BlockedProcessXmlParser.cs and switching SqlServerFactCollector.CollectBlockingChainFactsAsync / SqlServerDrillDownCollector.CollectReconstructedBlockingChains to read the new typed columns directly. That PR is gated on this one being on dev.

Test plan

  • Upgrade script applied cleanly on sql2022.PerformanceMonitor (5 ALTERs + 126-row backfill)
  • collect.process_blocked_process_xml ALTER applied cleanly
  • Verification gate (§10 step 8, tightened) returns 0:
    sql SELECT COUNT_BIG(*) FROM collect.blocking_BlockedProcessReport WHERE activity='blocked' AND blocked_process_report_xml IS NOT NULL AND blocking_spid IS NULL AND blocked_process_report_xml.exist(N'//blocked-process-report/blocking-process/process[@spid][1]') = 1;
  • Spot-checked populated rows have plausible blocker-side values (spid, status, last_tran_started, SQL text)
  • tsql-style-enforcer agent — pass (one minor alias-qualification fix applied)
  • upgrade-path-validator agent — pass
  • Live-server validation by maintainer before merge
  • Repeat upgrade script on a server that hasn't yet seen any blocked-process rows (should ALTER cleanly and backfill UPDATE 0 rows)
  • Verify upgrade script is re-runnable (re-running on already-upgraded server should ALTER 0 columns and UPDATE 0 rows)

🤖 Generated with Claude Code

Move blocked-process-report XML parsing from analysis time to collection
time on the SQL side. Five new columns on
collect.blocking_BlockedProcessReport (blocking_spid,
blocking_last_tran_started, blocking_status, blocked_sql_text,
blocking_sql_text) are populated at insert time inside
collect.process_blocked_process_xml via XQuery against the stored
<event>-rooted XML using the descendant axis.

A 2.11.0 -> 2.12.0 upgrade script adds the columns idempotently and
backfills existing activity='blocked' rows in a single pass. Idempotency
on backfill and live UPDATE is via WHERE blocking_spid IS NULL.

Defense-in-depth: if sp_HumanEventsBlockViewer writes rows but the
XQuery extraction populates zero typed columns, a TYPED_COLUMNS_EMPTY
row is written to config.collection_log so any future wire-format
regression is visible without waiting for the analysis path to surface
garbage chains.

This is the schema-only PR. The Dashboard C# read-path simplification
(deleting BlockedProcessXmlParser.cs and switching
SqlServerFactCollector / SqlServerDrillDownCollector to read the new
columns directly) ships in a follow-up PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@erikdarlingdata erikdarlingdata merged commit 23a3f7b into dev May 26, 2026
6 checks passed
@erikdarlingdata erikdarlingdata deleted the feature/sql-blocked-process-typed-columns branch May 26, 2026 21:43
MisterZeus pushed a commit to MisterZeus/PerformanceMonitor that referenced this pull request Jun 5, 2026
MisterZeus pushed a commit to MisterZeus/PerformanceMonitor that referenced this pull request Jun 5, 2026
The Dashboard fact collector and drill-down collector now read the
typed blocker-side columns added in erikdarlingdata#996 (blocking_spid,
blocking_last_tran_started, blocking_status, blocked_sql_text,
blocking_sql_text) directly from collect.blocking_BlockedProcessReport
instead of re-parsing blocked_process_report_xml on every analysis
cycle — up to 5000 XElement.Parse calls per BLOCKING_CHAIN fact
collection are eliminated.

BlockedProcessXmlParser is deleted; no callers remain. Lite is
unchanged — it has no SQL-side staging table and continues to parse
once at collect time inside RemoteCollectorService.BlockedProcessReport.

Both SELECTs add `AND blocking_spid IS NOT NULL` so rows whose source
XML carried an empty <blocking-process><process/></blocking-process>
(system tasks, torn-down sessions) are filtered out — they can't
contribute to a reconstructed chain. The old parser silently produced
BlockingSpid = 0 for those rows, which the reconstructor's self-edge
check then rejected anyway.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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