Skip to content

perf(server): keep sqlite query statistics fresh - #218

Merged
tusharbhardwaj-bk merged 1 commit into
expbkmainfrom
t3code/perf-3-sqlite-optimize
Sep 26, 2026
Merged

tusharbhardwaj-bk merged 1 commit into
expbkmainfrom
t3code/perf-3-sqlite-optimize

Conversation

@tusharbhardwaj-bk

@tusharbhardwaj-bk tusharbhardwaj-bk commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The prod bkt3 database (7.7 GB) has never been analysed: there is no sqlite_stat1. The planner therefore chooses among overlapping indexes without statistics. projection_thread_activities has 4 indexes on thread_id prefixes and projection_threads has 7.

Fix

New fork layer persistence/sqliteOptimize.expbkt3.ts. Every 6 h it runs PRAGMA analysis_limit = 400; PRAGMA optimize = 0x10002, SQLite's recommended recipe for long-lived connections.

  • It only analyses tables whose statistics are missing or stale.
  • It samples at most ~400 rows per index.
  • It logs sqlite.optimize.finished with durationMs.
  • It is fail-soft: errors are logged, never thrown.

It does not run at startup. The coordinator's rule was: if the timed run on the prod copy exceeds 2 s, run it only on the schedule. The first-ever run took 2,768 ms, and node:sqlite is synchronous, so even a parked fiber would block the event loop. The first run therefore lands 6 h after a deploy. That is a one-time ~2.8 s pause; after that, runs cost ~0 ms.

Wiring and the off-switch:

  • One line inside the existing marked Layer.mergeAll(VcsLayerLive, SessionArchiveLayerLive, SessionArchiveSweeperLayerLive, …) group in server.ts, plus a marked import.
  • It is server-only. The CLI's use of persistence/Layers/Sqlite.ts does not pick it up.
  • T3_SQLITE_OPTIMIZE=0 turns it off, in case fresh statistics ever flip a query plan the wrong way.

Evidence

Measured on the VACUUM INTO copy of prod (SQLite 3.53.0, 954k activity rows):

run time result
first optimize=0x10002 (no stats) 2,768 ms sqlite_stat1 created, 94 rows
second run 0 ms nothing stale

EXPLAIN QUERY PLAN for 7 hot queries is identical before and after the statistics exist:

  • shell thread list;
  • thread activities page;
  • thread replay stats;
  • events after a sequence (the new event feed);
  • pending approvals by (thread_id, kind);
  • PR link lookup;
  • due execution intents.

The statistics therefore do not flip today's plans, and they give the planner real numbers for future ones.

Tests: sqliteOptimize.expbkt3.test.ts, 3 tests.

  • env parsing;
  • a table without statistics gets sqlite_stat1 rows;
  • the scheduled layer does not run before 6 h and does run at 6 h (TestClock).

vp run typecheck in apps/server is clean, lint is clean, and the fork-marker check passes.

Model/harness: Claude Opus 5.5 via Claude Code in T3 Code.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Prod has never been analysed (no sqlite_stat1), so the planner chooses among
overlapping indexes without statistics. Every 6 h the server now runs
PRAGMA analysis_limit=400; PRAGMA optimize=0x10002.

It never runs at startup: the first-ever run took 2.8 s on a copy of the
prod database, and node:sqlite blocks the event loop while it runs. Later
runs take ~0 ms. T3_SQLITE_OPTIMIZE=0 turns it off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M labels Sep 26, 2026
@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire — 13.8 KiB — 15.1 KiB ✅
Codex Thread snapshot wire — 7.3 KiB — 7.8 KiB ✅
Codex Live turn WebSocket wire — 6.5 KiB — 7.8 KiB ✅
Codex Live turn WebSocket decoded — 57.1 KiB — 66.4 KiB ✅
Codex Live turn messages — 10 — 21 ✅
Claude Total thread wire — 13.8 KiB — 15.1 KiB ✅
Claude Thread snapshot wire — 7.3 KiB — 7.8 KiB ✅
Claude Live turn WebSocket wire — 6.5 KiB — 7.8 KiB ✅
Claude Live turn WebSocket decoded — 57.9 KiB — 66.4 KiB ✅
Claude Live turn messages — 9 — 21 ✅

Baseline: unavailable · PR result: 0a3f524 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 114.9 KiB
  • Claude decoded thread snapshot: 115.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@tusharbhardwaj-bk
tusharbhardwaj-bk merged commit 58b720b into expbkmain Sep 26, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants