perf(state): add messages(session_id, id) index for window/ordering queries - #76237
perf(state): add messages(session_id, id) index for window/ordering queries#76237spfcraze wants to merge 1 commit into
Conversation
|
Thanks for the focused state-store performance improvement. The premise remains present on current Problems
Suggested changes
This is an automated hermes-sweeper review. |
…ueries Every ORDER BY id query on the messages table sorted or scanned the whole session: get_messages_around's window seek, latest_message_row_id (LIMIT 1), and get_messages' full-load ordering all paid O(session history) per call — hot mid-turn via session_search and reactions. messages.id is an original column (INTEGER PRIMARY KEY AUTOINCREMENT), so the index lives in SCHEMA_SQL next to idx_messages_session — no legacy-column migration hazard (the kanban lesson from NousResearch#28776 does not apply). Measured (real schema, one 20k-message session, median of 30): get_messages_around 7.08 -> 0.22 ms (32x), latest_message_row_id 3.37 -> 0.011 ms (307x), get_messages full load 111.6 -> 98.6 ms (1.13x — remaining cost is row deserialization, not the sort). Window results byte-identical at probe points across the session. Tests: VM-step pin (get_messages_around bounded work, calibrated ~12 vs ~855 handler calls, threshold 300 — fails without the index) and window parity with/without the index. No EXPLAIN/plan text (behavior contracts, AGENTS.md).
|
Both schema excerpts updated in ec0c8d3 (amended, force-pushed) — Re-verified after the amend: sabotage (base 2 fail / head 142 pass), zero branch-only failures vs baseline in tests/test_hermes_state.py. |
1a0616b to
ec0c8d3
Compare
What does this PR do?
Every ORDER BY id query on the messages table sorted or scanned the whole session's history: get_messages_around's window seek, latest_message_row_id (LIMIT 1), and get_messages' full-load ordering all paid O(session history) per call — hot mid-turn via session_search and reactions. Add idx_messages_session_id ON messages(session_id, id) to SCHEMA_SQL. messages.id is an original column (INTEGER PRIMARY KEY AUTOINCREMENT), so there is no legacy-column migration hazard (the kanban lesson from #28776 does not apply); CREATE INDEX IF NOT EXISTS keeps existing DBs idempotent.
Related Issue
No direct issue — discovered via code review and reproduced live (see below).
Related PRs reviewed during the duplicate check (none covers this change):
Changes Made
fix/messages-session-id-index— 2 file(s) changed vs base:hermes_state_common.pytests/test_hermes_state.pyhermes_state_common.py: one line in SCHEMA_SQL (idx_messages_session_id), placed next to idx_messages_session. tests/test_hermes_state.py: +2 tests — a VM-step pin (get_messages_around bounded work via SQLite progress handler, calibrated ~12 indexed vs ~855 unindexed handler calls, threshold 300; fails without the index) and window parity with/without the index at probes across the session. No EXPLAIN/plan-text assertions (behavior contracts over snapshots, AGENTS.md).
How to Test
Measured on the real schema (one 20k-message session, median of 30 runs, repo venv): get_messages_around(window=20) 7.08 -> 0.22 ms (32x); latest_message_row_id 3.37 -> 0.011 ms (307x); get_messages full load 111.6 -> 98.6 ms (1.13x — the remaining cost is row deserialization, not the sort). Window results byte-identical at probe points across the session. Insert cost: one extra B-tree entry per message insert, minor next to the 2-3 FTS trigger writes every insert already pays.
Validation completed (recorded by prp):
tests/test_hermes_state.py.tests/test_hermes_state.py: branch 142 passed / 0 failed vs baseline 140 passed / 0 failed — zero branch-only failures.Logs
Sabotage verification output: