Skip to content

Index persistence is never scheduled after boot: everything indexed at runtime is lost unless the process exits cleanly #1180

Description

@sbynode-ux

What I see on 0.9.28

indexPersistence.scheduleSave() has exactly two call sites, both in the bootstrap:

  • after rebuildIndex() when the loaded index was empty
  • after the legacy memory backfill into BM25

Nothing schedules a save when an observation is compressed and added to the index at runtime, and there is no periodic flush. The only other save is the one in the shutdown handler.

So between boot and a clean shutdown, the persisted snapshot never advances. On this install, 30 minutes and several hundred observations after start, no shard under mem:index:bm25:* had been rewritten, while mem:obs:* and mem:memories were being written continuously. Search was fine — the in-memory index has everything — which is exactly why this is easy to miss.

Why it matters

Any exit that does not run the shutdown handler loses the whole runtime delta:

  • SIGKILL (systemd TimeoutStopSec expiring is a realistic one — viewerServer.close() can wait on keep-alive connections)
  • OOM kill
  • host reset / power loss
  • container stop that outruns the grace period

After that, boot restores the last snapshot, which is not empty, so rebuildIndex() does not run — the gap silently survives every subsequent restart. The user-visible symptom is "search stopped finding recent work", with a healthy-looking server.

Repro

  1. Start with a non-empty persisted index.
  2. Generate observations for a few minutes.
  3. ls -lt the mem:index:bm25:* shards — mtimes are still from boot.
  4. kill -9 the server, restart: the runtime observations are missing from BM25/vector search but present in KV.

Suggested fix

Whichever of these fits your design:

  • call scheduleSave() where the index is mutated at runtime (the debounce already coalesces bursts), and/or
  • a periodic flush, e.g. setInterval(() => indexPersistence.scheduleSave(), 300_000).unref() next to the existing bootstrap wiring

I am running the periodic-flush version locally (5 min, .unref()ed) and it is one line. Happy to send it as a PR — say which shape you prefer, or if both.

Related: #1179 (no way to rebuild the index without a restart) — the two together are why a stale index can persist indefinitely once it happens.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions