Skip to content

perf(retrieval): cache the search weighting maps per index generation (v6.0.0) - #200

Merged
CryptoJones merged 1 commit into
mainfrom
perf/searchindex-weighting-cache
Aug 2, 2026
Merged

perf(retrieval): cache the search weighting maps per index generation (v6.0.0)#200
CryptoJones merged 1 commit into
mainfrom
perf/searchindex-weighting-cache

Conversation

@CryptoJones

Copy link
Copy Markdown
Owner

Closes #186.

What

_owners, _weight_generated, and _weight_superseded each rebuilt their maps from a full scan of chunks/notes on every query. Three full-table Python scans per search, so latency grew with vault size instead of with the fused candidate set.

They now share a single _Weights snapshot (chunk→filename owners, the generated chunk-id set, the superseded filename set) built once per index generation and cached per process — the same invalidation contract _vector_matrix already used.

Why it stays correct

  • Keyed on the index generation, so a write from any process invalidates it on the next query, not just a local refresh() (which also clears it explicitly).
  • The classification rules are unchanged: same okf_type/filename prefixes for generated notes, same Supersedes:/Superseded by: alias resolution. Only the number of times they run changed.
  • New regression test test_weighting_maps_are_built_once_per_generation pins both halves: a second query reuses the cache, and a refresh after a new note rebuilds it.

Version

Bumped to 6.0.0. main was already carrying two unreleased breaking changes with no release cut — the MCP 2.x SDK adoption (#192) and the removal of the deprecated graph-* MCP aliases (#185) — so the next number off 5.0.1 is a major regardless of this change. The ## [Unreleased] changelog section is closed out as 6.0.0.

Gates

ruff check . · mypy src (strict, 46 files) · pytest (842 passed, 1 skipped) · pip-audit — all green locally.

🤖 Generated with Claude Code

`_owners`, `_weight_generated`, and `_weight_superseded` each rebuilt their
maps from a full scan of `chunks`/`notes` on every call, so query latency grew
with vault size rather than with the fused candidate set — three full scans per
query on top of the fusion work.

They now share one `_Weights` snapshot built once per index `generation` and
cached per process, the same invalidation contract `_vector_matrix` already
used: a local refresh clears it, and a write from another process bumps the
generation so the next query rebuilds. Behaviour is unchanged; the superseded
alias resolution and the generated-note classification are byte-for-byte the
same rules, just computed once.

Closes #186.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Performance

    • Improved search query performance by reusing cached weighting data instead of rescanning the full index for each query.
    • Preserved accurate results when indexed content changes and the index is refreshed.
  • Bug Fixes

    • Ensured newly indexed worklog content is correctly recognized and included in search weighting.
  • Release

    • Updated the project version to 6.0.0.

Walkthrough

Search weighting data now uses a per-generation cache. Refresh invalidates the cache. Generated and superseded weighting reuse shared maps. Tests cover cache rebuilding, new worklog chunks, and version 6.0.0 metadata.

Changes

Generation-scoped weighting cache

Layer / File(s) Summary
Cache state and refresh invalidation
src/omind/searchindex.py
SearchIndex stores _Weights data beside the vector cache and invalidates it when the index generation changes.
Shared weighting-map construction
src/omind/searchindex.py
A shared pass builds chunk ownership and generated or superseded note sets. Weighting methods reuse the cached data while preserving score penalties and sorting.
Regression coverage and release metadata
tests/test_searchindex.py, CHANGELOG.md, BACKLOG.md, pyproject.toml, src/omind/__init__.py
Tests cover cache reuse and refresh rebuilding. Release metadata records version 6.0.0 and the completed performance work.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SearchIndex
  participant SQLite
  participant WeightingMethods
  SearchIndex->>SQLite: Build weighting maps for the current generation
  SQLite-->>SearchIndex: Return ownership and note sets
  SearchIndex->>WeightingMethods: Supply cached _Weights data
  WeightingMethods-->>SearchIndex: Apply generated and superseded weighting
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: caching search weighting maps per index generation.
Description check ✅ Passed The description explains the caching change, correctness guarantees, version update, tests, and validation results.
Linked Issues check ✅ Passed The PR addresses issue #186 by caching generated, superseded, and ownership maps per index generation with regression coverage.
Out of Scope Changes check ✅ Passed The changes remain within scope: performance caching, regression coverage, release metadata, and documentation updates for issue #186.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/searchindex-weighting-cache

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 10-11: Insert a blank line between the “### Performance” heading
and its list item in CHANGELOG.md to satisfy markdownlint MD022.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8340fa86-731f-40cd-8d08-b7612bdd2077

📥 Commits

Reviewing files that changed from the base of the PR and between 4fa7f34 and 9ff7899.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • BACKLOG.md
  • CHANGELOG.md
  • pyproject.toml
  • src/omind/__init__.py
  • src/omind/searchindex.py
  • tests/test_searchindex.py
📜 Review details
⏰ Context from checks skipped due to timeout. (11)
  • GitHub Check: test (ubuntu-latest, 3.14)
  • GitHub Check: test (macos-latest, 3.10)
  • GitHub Check: test (ubuntu-latest, 3.13)
  • GitHub Check: test (windows-latest, 3.10)
  • GitHub Check: test (ubuntu-latest, 3.10)
  • GitHub Check: test (windows-latest, 3.14)
  • GitHub Check: test (ubuntu-latest, 3.12)
  • GitHub Check: test (ubuntu-latest, 3.11)
  • GitHub Check: test (macos-latest, 3.14)
  • GitHub Check: MCP conformance (live stdio)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Retrieval must fail open: every search layer returns None on errors and callers fall back to the older path. Missing models, corrupt indexes, locked databases, and missing FTS5 support must degrade search rather than break it; failure branches must be tested.
All note writes must go through OmiStore; external writers use notes.upsert_note. Writes must retain flocking, atomic rename, Lamport Rev: stamping, and soft deletion. Deletes archive notes with Disabled: true; only omind mesh purge permanently removes them.
OmiStore.safe_name must guard every note read and write so path traversal remains impossible.
Credential notes must be de-prioritized in search and gate suggestions unless the query concerns credentials, using _CREDENTIAL_PENALTY; the gate must never steer agents toward secrets notes.
MCP tools must never return unbounded output. Every list-shaped tool must paginate through server._page and expose limit, offset, total, and has_more.
index.md and Memory Template.md are scaffolding, not memories; reading either must not clear the consult gate. Preserve paths.NON_CONSULT_FILENAMES.
Recency is a ranking leg only: it may re-rank notes already matched by content legs but must never add unmatched notes to search results.
link_targets() must preserve wikilink case for dangling-link reports; only link resolution lowercases it.
Do not mutate a NoteSummary returned from _cached_summary; it is shared cached state. Use dataclasses.replace, as in store._indexed_search.
Treat embed.encode results as potentially plain lists in tests; coerce vectors through searchindex._query_vector rather than assuming a .shape attribute.
Do not reintroduce document-frequency query-term filtering; use threshold-free graded matching, where all-words matches rank above any-words matches.

Files:

  • src/omind/__init__.py
  • tests/test_searchindex.py
  • src/omind/searchindex.py
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Markdown note files are the source of truth; indexes, caches, and vectors are derived and disposable and must live only in paths.state_dir(), never in the vault.

Files:

  • BACKLOG.md
  • CHANGELOG.md
🪛 markdownlint-cli2 (0.23.1)
CHANGELOG.md

[warning] 10-10: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🔇 Additional comments (5)
src/omind/searchindex.py (1)

179-192: LGTM!

Also applies to: 478-479, 636-636, 994-1047, 1063-1079

tests/test_searchindex.py (1)

266-281: LGTM!

BACKLOG.md (1)

17-20: LGTM!

pyproject.toml (1)

3-3: LGTM!

src/omind/__init__.py (1)

5-5: LGTM!

Comment thread CHANGELOG.md
Comment on lines +10 to +11
### Performance
- **The search weighting passes no longer re-scan the whole index on every

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a blank line after the heading.

markdownlint-cli2 reports MD022 at Line 10. Insert one blank line before the list item at Line 11.

🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 10-10: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 10 - 11, Insert a blank line between the “###
Performance” heading and its list item in CHANGELOG.md to satisfy markdownlint
MD022.

Source: Linters/SAST tools

@CryptoJones
CryptoJones merged commit d28eecb into main Aug 2, 2026
16 checks passed
@CryptoJones
CryptoJones deleted the perf/searchindex-weighting-cache branch August 2, 2026 10:51
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.

Perf: per-query full-table Python scans in searchindex._weight_generated/_weight_superseded (and per-call _owners)

1 participant