Skip to content

perf(stats): cut the dashboard tick's page misses by 43% - #276

Merged
henry40408 merged 1 commit into
mainfrom
perf/dashboard-tick-pages
Sep 13, 2026
Merged

henry40408 merged 1 commit into
mainfrom
perf/dashboard-tick-pages

Conversation

@henry40408

Copy link
Copy Markdown
Owner

Summary

While a dashboard is open, the server builds its stats snapshot every 10 seconds. On a 370k-row database each tick read 8 304 pages cold. It now reads 4 755 (−43%).

  • One scan for the summary. count_queries_multi_since (totals, blocks) and cache_stats_multi_since (cache hits, latency) each walked the same 30 days of idx_query_logs_ts_metrics. summary_multi_since answers both in one statement by putting the allowed-only filter inside each CASE instead of the WHERE. The two old functions had no other callers and are removed.
  • A covering index for top upstreams. upstream and response_ms were in no index, so the query looked up the table row for every forwarded query in the last 24h. Migration 14 adds idx_query_logs_ts_upstream ON (timestamp, upstream, response_ms) WHERE upstream IS NOT NULL, and the query names it with INDEXED BY.

Why this index shape

Measured with page misses on the same database. Four candidates were compared: timestamp-first vs upstream-first, full vs partial. The "pages written" column counts WAL frames for one committed 500-row batch, averaged over three batches.

index read (24h) size pages written / batch
none (baseline) 1608 53
(upstream, timestamp, response_ms) 328 9.1 MiB 71
(timestamp, upstream, response_ms) 317 9.1 MiB 58
(upstream, …) WHERE upstream IS NOT NULL 220 5.7 MiB 65
(timestamp, …) WHERE upstream IS NOT NULL 211 5.7 MiB 56

Partial, because 56% of rows (blocked or cached) have no upstream. Timestamp-first, because the logger appends at the newest end; an upstream-first index would split every batch across one insertion point per upstream.

One tick, before and after

Each statement runs on a fresh connection, as the read pool's 2 MiB caches effectively leave them. The new statements return exactly the same results as the old ones (diffed).

before count_1m 3
before count_multi 2152
before cache_multi 2152
before domains 1831
before clients 558
before up_old 1608
after  count_1m 3
after  summary 2152
after  domains 1831
after  clients 558
after  up_new 211
TICK before 8304 after 4755

Tests

  • stats_page_miss_test: the_dashboard_summary_is_one_metrics_scan and the_top_upstreams_never_read_the_log_table both failed before the change (225 pages vs 111 for one scan; 1470 of 1892 pages) and pass after. The shared seed now forwards half its rows so the second test can measure anything.
  • stats_db_test: summary_multi_since gives each window's figures and leaves blocked rows out of the latency average. An empty window reports zeroes.
  • db::tests::migration_v14_adds_the_upstream_index: a version-13 database gains the index, and top upstreams still answers correctly through it.

E2E was not run locally; the dashboard's rendering is unchanged and CI runs the suite.

🤖 Generated with Claude Code

The dashboard pays for its readings every 10 seconds. Two things made
that tick expensive:

- Totals and blocks came from one statement, and cache hits and latency
  from a second, but both walked the same 30 days of
  idx_query_logs_ts_metrics. summary_multi_since answers both from one
  scan by putting the allowed-only filter inside each CASE instead of the
  WHERE.
- top_upstreams_since reads upstream and response_ms, which no index
  held, so it looked up the table row for every forwarded query. Migration
  14 adds idx_query_logs_ts_upstream, a partial index on (timestamp,
  upstream, response_ms) WHERE upstream IS NOT NULL. It puts timestamp
  first so each logger batch is written in one place.

On a 370k-row database a tick drops from 8 304 to 4 755 page misses. The
index is 5.7 MiB, and a 500-row insert batch writes 56 pages instead of 53.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.75281% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.27%. Comparing base (55c68ea) to head (bba141b).

Files with missing lines Patch % Lines
src/db.rs 97.22% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #276      +/-   ##
==========================================
+ Coverage   91.26%   91.27%   +0.01%     
==========================================
  Files          31       31              
  Lines       11329    11342      +13     
==========================================
+ Hits        10339    10352      +13     
  Misses        990      990              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@henry40408
henry40408 merged commit c82c657 into main Sep 13, 2026
7 checks passed
@henry40408
henry40408 deleted the perf/dashboard-tick-pages branch September 13, 2026 03:47
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