Skip to content

Collect, visualize, expose, and alert on table/index sizes, growth, usage, and locking (#1103)#1114

Merged
erikdarlingdata merged 5 commits into
devfrom
feature/1103-object-size-index-usage-locking
Jun 12, 2026
Merged

Collect, visualize, expose, and alert on table/index sizes, growth, usage, and locking (#1103)#1114
erikdarlingdata merged 5 commits into
devfrom
feature/1103-object-size-index-usage-locking

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Closes #1103.

Adds time-series collection + visualization + MCP + alerting for per-table / per-index size, growth, index usage, and locking/contention, across both Dashboard and Lite. All from stock DMVs (sys.dm_db_partition_stats, sys.dm_db_index_usage_stats, sys.dm_db_index_operational_stats), verified stable 2016 → 2025 / Azure SQL DB / MI.

What's included (6 commits, by layer)

  • Collection — Dashboard collector install/55_collect_index_object_stats.sql (cross-DB cursor + Azure EngineEdition=5 branch) → collect.index_object_stats (02/06, NC index), scheduled daily / 90-day (04/42); dynamic retention picks it up. Lite RemoteCollectorService.IndexObjectStats.cs → DuckDB schema v29 + archival in both ArchivableTables arrays + schedule.
  • Read layerGetObjectSizeGrowthAsync (per-table size + 7d/30d growth + daily rate), GetIndexUsageAsync (Unused / Write-only / Active classification), GetIndexLockingAsync (top contended) in both apps.
  • UI — three FinOps sub-tabs in both apps: Object Sizes & Growth, Index Usage, Locking & Contention.
  • MCPget_table_index_sizes, get_index_usage, get_object_locking in both apps.
  • Alerting — daily data is cumulative, so detection is delta-based (two most recent snapshots, not stddev-baseline): ANOMALY_OBJECT_GROWTH (table grew >100 MB + 20% day-over-day) and ANOMALY_OBJECT_CONTENTION (index gained ≥60s new row-lock wait, reset-guarded). Flows through the existing anomaly→AnalysisNotificationService path; FactScorer + FactAdvice extended.

Verification done

  • Live on SQL 2016 (oldest supported): collector returns 148 rows / 6 DBs / SUCCESS log; all three read queries return correct data; growth detector fires correctly against a synthetic prior snapshot (Posts 2278→6835 MB, 200%) and cleans up.
  • Builds: Dashboard + Lite clean.
  • Tests: Lite 434 / Dashboard 482, 0 failures. New IndexObjectStatsTests (5) cover the Lite DuckDB read dialect + the anomaly detector (incl. single-snapshot no-false-positive). MCP schema-compat tests pass (3/3 each).
  • Self-review: code-reviewer (correctness) and security-reviewer both clean; T-SQL style findings applied.

⚠️ Not yet validated live (needs a reviewer with the apps running)

  • The WPF UI (both apps) was build-verified only — not visually run. The new tabs mirror the existing Storage Growth grid pattern.
  • Alerting end-to-end (fact → notification → email/tray) was unit-tested but never fired against a live server; thresholds are constants (100 MB / 20% / 60s), not yet user-configurable.
  • Lite collection→DuckDB end-to-end and the Azure SQL DB EngineEdition=5 branch were not run against a live instance (only sql2016 was available).

Open items for follow-up (flagged, not silently dropped)

  • Make alert thresholds user-configurable (Settings UI + IAlertSettings).
  • Optional: bind topN as a DuckDB parameter in the Lite read layer (currently a safe int interpolation) for defense-in-depth.

🤖 Generated with Claude Code

erikdarlingdata and others added 5 commits June 11, 2026 22:18
Collection layer for both apps. A daily collector captures per-table and
per-index size, growth, index usage, and locking/contention from
sys.dm_db_partition_stats, sys.dm_db_index_usage_stats, and
sys.dm_db_index_operational_stats (stable 2016+, Azure SQL DB, and MI).

Dashboard: collect.index_object_stats table (02 + 06 ensure block + NC index),
collector proc (55), schedule row + master dispatch (04/42). Dynamic retention
(43) picks the table up automatically by collection_time.

Lite: RemoteCollectorService.IndexObjectStats.cs (per-DB cross-database
sp_executesql on-prem; per-database connect on Azure), DuckDB schema v29 +
archival registration in BOTH ArchivableTables arrays, schedule default.

Cumulative usage/locking counters carry sqlserver_start_time as the reset
boundary so deltas are computed safely in the read layer. Verified live on
SQL 2016: 148-149 rows across 6 databases, unused/write-only index signal works.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Read layer (both apps): GetObjectSizeGrowthAsync (per-table size + 7d/30d
growth + daily rate, indexes rolled up), GetIndexUsageAsync (seeks/scans/
lookups/updates + Unused/Write-only/Active classification), GetIndexLockingAsync
(row/page lock waits, escalations, latch waits, top contended). Dashboard reads
collect.index_object_stats; Lite reads v_index_object_stats. Dashboard growth
queries validated live on SQL 2016.

UI: three FinOps sub-tabs in both apps - "Object Sizes & Growth", "Index Usage",
"Locking & Contention" - mirroring the existing Storage Growth grid pattern
(sortable columns, column filters on identity columns, refresh, count indicators).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three read tools in both apps exposed to the AI surface: get_table_index_sizes
(largest tables + growth), get_index_usage (with Unused/Write-only/Active
classification), get_object_locking (lock/latch waits + escalations, top
contended). Registered in McpHostService, documented in McpInstructions under a
new "Storage & Index Tools" section. McpSchemaCompatTests pass (3/3 each app).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Daily object/index data is cumulative, so detection is delta-based (two most
recent snapshots) rather than stddev-baseline. New detector method in both
AnomalyDetector (Lite/DuckDB) and SqlServerAnomalyDetector (Dashboard/SQL):
  - ANOMALY_OBJECT_GROWTH: biggest day-over-day table grower over 100 MB + 20%.
  - ANOMALY_OBJECT_CONTENTION: index with largest new row-lock wait time (>=60s),
    guarded against counter resets (cur.ms >= prv.ms).
Joins on stable object_id/index_id; emits one fact each (worst offender) to avoid
notification spam. FactScorer scores both (ratio-based); FactAdvice supplies
headline/investigation/remediation. Findings flow through the existing
AnalysisNotificationService path (severity gate + cooldown) — no email/settings
plumbing needed. Growth detection validated live on SQL 2016 with a synthetic
prior snapshot (Posts 2278->6835 MB, 200%, detected + cleaned up).

Thresholds are currently constants (not yet user-configurable).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…style (#1103)

Tests (Lite.Tests/IndexObjectStatsTests.cs): seed two daily snapshots and exercise
the DuckDB read layer (date_diff growth math, GREATEST last-access, unused/write-only
classification, contention rows) and the delta-based anomaly detector (growth +
contention fire; single-snapshot yields no false positives). Bump DuckDbSchemaTests
table count 30->31 for the new index_object_stats table.

Style (per T-SQL review): reformat the embedded queries in
SqlServerAnomalyDetector.DetectObjectStatsAnomalies and two scalar subqueries in
DatabaseService.FinOps.IndexObjects to house style (multi-line CTEs, one column per
line, JOIN/ON layout with correlation order). No behavior change.

Full suites green: Lite 434, Dashboard 482. Correctness + security self-review clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@erikdarlingdata erikdarlingdata merged commit ee7dc57 into dev Jun 12, 2026
6 checks passed
@erikdarlingdata erikdarlingdata deleted the feature/1103-object-size-index-usage-locking branch June 12, 2026 03:19
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