Repository navigation
feat(csr): fold the delta into a new base with optimize_csr() - #98
Merged
Merged
Conversation
refresh() applies Lance writes as a delta over the base CSR and rebuilds on its own only once pending changes pass 25% of the base. Below that, every lookup carries the delta and users had no way to fold it in. This is the optimize step #75 asks for, the CSR's counterpart of Lance's optimize_indices(). optimize_csr() (Rust and Python) rebuilds every CSR that carries a delta, at the version the engine is pinned to, and reuses everything else. It shares refresh()'s state building, with a flag that sends delta-carrying CSRs straight to a full build, and takes the same lock, so the two run one at a time and the replacement is validated before it's swapped in. The CSR guide maps the base CSR, delta, unindexed_edges and optimize_csr() onto Lance's index coverage, num_unindexed_rows and optimize_indices(), links Lance's indexing guide and index format, and adds a tip on when to call it rather than refresh(). Closes #75.
Deploying lance-csr with
|
| Latest commit: |
8ee2ca3
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://d679b183.lance-csr.pages.dev |
| Branch Preview URL: | https://feat-csr-optimize.lance-csr.pages.dev |
prrao87
marked this pull request as ready for review
October 31, 2026 02:10
prrao87
added a commit
that referenced
this pull request
Oct 31, 2026
A patch release: nothing since 0.5.0 breaks the Python or Rust API, so RELEASE.md's rule gives a patch bump. It ships the two-hop count rewrite (#97), `optimize_csr()` (#98), per-chunk in-memory relationship filters (#100) and the node lookup after grouping by id (#101). Also refresh the version in the three lockfiles, which bump-my-version does not update.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
refresh()applies Lance writes to the CSR as a delta over its base, and rebuilds on its own only once pending changes pass 25% of the base. Below that threshold every lookup also reads the delta, and there was no way to fold it in short of rebuilding the engine. #93 added the reporting half of #75 (base_version,unindexed_edges); this adds the explicit optimize step, so users choose when to rebuild. Closes #75.What changed
optimize_csr(), in Python and Rust (engine.optimize_csr().await), rebuilds every CSR that carries a delta at the version the engine is pinned to. Afterwardscsr_stats()reportsupdate"rebuild",delta_edges,tombstonesandunindexed_edgesat 0, andbase_versionequal toversion. A CSR without a delta, node indexes and everything else are reused, and withoutuse_csr=Trueit does nothing. Query results never change.refresh()stays the call for that. Both share one state-building path, andoptimize_csr()only adds a flag that sends delta-carrying CSRs straight to a full build. It takesrefresh()'s lock, so the two run one at a time, and the replacement is built in full before it's swapped in.refresh()is meant to be cheap enough to run on a timer, while a rebuild scans every edge's endpoint columns again. Keeping them apart follows Lance, where picking up new data andoptimize_indices()are separate steps.The CSR guide's new "Folding the delta in" section says this mirrors Lance's index design and maps one onto the other:
base_versionnum_unindexed_rowsin index statisticsunindexed_edgesincsr_stats()optimize_indices()optimize_csr()It links Lance's indexing guide and index format, runs an example, and adds a tip on when to reach for
optimize_csr()rather thanrefresh(): after a bulk load, before a latency-sensitive run of queries, or on a schedule, not after every refresh. Theupdatefield description, the "How to keep the CSR fresh" steps, the Python and Rust comparison table and the docstring (which feeds the API reference) mention it too.User impact: one new method on
CypherEnginein Python and Rust. Existing calls behave as before.Validation
optimize_csr(), checking every stats field and the query results, and that a second call reuses the CSR.optimize_csr(); no-op withoutuse_csr.cargo fmt --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test --workspace,make test(1660 passed) andmake lintpass locally, as dodocs/check_snippets.py(the new example printsrebuild 0),zensical build --strictandtypos. CI is pending.optimize_csr()is called.🤖 Generated with Claude Code