Skip to content

perf(state): merge FTS5 segments + add handoff_state index to curb write-lock contention - #54752

Merged
kshitijk4poor merged 3 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/state-db-perf
Jul 1, 2026
Merged

perf(state): merge FTS5 segments + add handoff_state index to curb write-lock contention#54752
kshitijk4poor merged 3 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/state-db-perf

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Summary

Salvages two verified, complementary state.db performance fixes into one
reviewable change. On long-lived databases shared by the gateway + cron
processes, write-lock hold times grew until competing writers hit
database is locked.

Two orthogonal fixes, together:

  1. FTS5 segment merge (hermes_state.py) — original work by @isair (fix(state): periodically merge FTS5 segments to curb write-lock contention #50124)
    • The messages triggers append one FTS5 segment per insert; the existing
      optimize_fts() helper was never called, so segments accumulated
      unbounded (~34k trigram segments / 27k messages observed), lengthening
      every MATCH scan and every insert's automerge cost — and the WAL
      write-lock hold.
    • Wires optimize_fts() into _execute_write on a coarse 1000-write cadence
      beside the existing checkpoint; _try_optimize_fts is best-effort /
      never-raises, off-lock. optimize is a no-op once merged, so steady-state
      cost is negligible.
  2. handoff_state index (hermes_state.py) — original work by @kenyonxu (fix(gateway): add handoff_state index to prevent heartbeat blocking #43504)
    • Adds idx_sessions_handoff_state ON sessions(handoff_state, started_at) so
      the gateway heartbeat's handoff query stops doing a full table scan.

Credit

Cherry-picked preserving original authorship:

One trivial conflict resolved: the DEFERRED_INDEX_SQL block gained two indexes
on main since #43504 was authored; all three index definitions are kept.

Verification

Note

Deliberately NOT bundled here: #43701 (trigram index redesign, schema migration
v15→v16) changes trigram search behavior (drops tool_name/tool_calls from
the index) and interacts with this FTS path — it deserves its own review.

isair and others added 2 commits June 29, 2026 14:20
…ntion

The message triggers append one FTS5 segment per insert into both the
porter and trigram indexes. Nothing ever called the existing
optimize_fts() maintenance helper, so on a long-lived state.db these
segments accumulate without bound (observed: ~34k trigram segments for
~27k messages). Every MATCH then has to scan all segments, and every
insert pays a growing automerge cost that lengthens the WAL write-lock
hold time. Because the gateway and cron agents are separate processes
sharing one state.db, those longer holds exhaust the 1s-timeout x 15-retry
budget in _execute_write and surface as repeated:

    Session DB creation failed (will retry next turn): database is locked
    Session DB append_message failed: database is locked

Wire optimize_fts() into the write path on a coarse cadence
(_OPTIMIZE_EVERY_N_WRITES = 1000), alongside the existing every-50-writes
checkpoint. 'optimize' is effectively free once the index is already
merged, so steady-state cost is negligible; only the first merge of a
neglected index is expensive. The call is best-effort and never fails the
surrounding write.

Tests: cadence fires on the write path; a failing optimize never breaks
the write.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 583647b)
The index references the handoff_state column which is added by
_reconcile_columns() on legacy databases. Placing it in SCHEMA_SQL
causes 'no such column' errors during schema migration tests because
SCHEMA_SQL runs before reconciliation.

Move to DEFERRED_INDEX_SQL which runs after _reconcile_columns() —
matching the existing pattern used by idx_messages_session_active.

Refs: NousResearch#43504, NousResearch#40695
(cherry picked from commit 40ecd61)

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: LGTM

Performance improvement: periodically merges fragmented FTS5 segments (every 1000 writes) to prevent tens-of-thousands of segments from lengthening write-lock hold time and starving competing writers. Adds idx_sessions_handoff_state index.

Looks Good

  • Best-effort optimize never fails writes
  • Coarse cadence amortizes merge cost far below checkpoint cadence
  • optimize is a no-op once merged — idle DB pays almost nothing
  • Index uses CREATE INDEX IF NOT EXISTS (safe for existing DBs)
  • Tests cover cadence triggering and failure resilience

Reviewed by Hermes Agent

@alt-glitch alt-glitch added type/perf Performance improvement or optimization comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jun 29, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: salvages two open PRs into one reviewable change — #50124 (FTS5 segment-merge cadence, @isair) and #43504 (handoff_state index, @kenyonxu). All three are open; this is the combined superset. Flagging for a maintainer to pick the canonical PR (this salvage vs. the two originals).

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: LGTM

Good performance fix to merge FTS5 segments and add a handoff_state index. The implementation is clean with proper error handling and comprehensive tests. The periodic merge cadence is well-documented and the failure handling is robust.

Looks Good

  • Clean implementation with proper error handling
  • Good test coverage (2 tests)
  • Well-documented merge cadence and failure handling
  • Adds useful index for handoff_state queries
  • Follows existing patterns in the codebase

Reviewed by Hermes Agent

@kshitijk4poor
kshitijk4poor merged commit 843a3be into NousResearch:main Jul 1, 2026
29 of 30 checks passed
@kshitijk4poor
kshitijk4poor deleted the salvage/state-db-perf branch August 5, 2026 07:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/perf Performance improvement or optimization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Discord gateway heartbeat can be blocked by synchronous handoff SQLite polling

5 participants