Skip to content

feat(kg): add LanceDB unified storage backend#3380

Open
shyuan wants to merge 6 commits into
HKUDS:mainfrom
shyuan:feat/lancedb-storage-backend
Open

feat(kg): add LanceDB unified storage backend#3380
shyuan wants to merge 6 commits into
HKUDS:mainfrom
shyuan:feat/lancedb-storage-backend

Conversation

@shyuan

@shyuan shyuan commented Jul 8, 2026

Copy link
Copy Markdown

Closes #3379

What

Implements all four LightRAG storage types on embedded LanceDB in a single local directory, with no external services:

  • LanceDBKVStorage / LanceDBVectorStorage / LanceDBGraphStorage / LanceDBDocStatusStorage, on the native async API (connect_async) with a ref-counted shared connection, per-table write locks, strong read consistency
  • Deferred-embedding vector upserts, cosine query with similarity threshold, embedding-model-suffixed tables for model isolation
  • CJK-friendly BM25 full-text search (bigram tokenizer)
  • Canonical undirected edge storage, client-side BFS knowledge graph; passes the cross-backend graph conformance suite
  • Maintenance tools (rebuild_vdb / clean_llm_query_cache / migrate_llm_cache) extended to support LanceDB
  • lightrag-gunicorn multi-worker guard; docs, env.example, packaging

Testing

  • 73 offline tests (tests/kg/lancedb_impl) on real tmp-dir LanceDB
  • Cross-backend graph conformance suite (8/8)
  • ruff clean

Known limitations & follow-up enhancements

Deliberately deferred, tracked for future work:

  1. get_docs_paginated loads the full table then paginates in Python — fine for embedded-scale doc_status tables (hundreds–thousands of rows), but could push pagination into the DB layer for very large sets.
  2. LANCEDB_WORKSPACE env override bypasses validate_workspace — the value still passes through _sanitize_table_name (no safety risk), but validation should be consistent with the constructor path.
  3. A timestamp test uses asyncio.sleep(1.1) — could mock time or use a shorter sleep + tolerance to speed up the suite.

Notes

  • Single-process only (embedded DB + per-table locks); multi-worker deployments should use PostgreSQL/MongoDB/OpenSearch. lightrag-gunicorn refuses to start with multiple workers when a LanceDB backend is selected.

@danielaskdd

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 04546be925

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lightrag/kg/lancedb_impl.py Outdated
@shyuan

shyuan commented Jul 9, 2026

Copy link
Copy Markdown
Author

@danielaskdd Both issues have been addressed:

  • Codex finding (edge ID collision): Fixed in 5636d82. _canonical_edge_id now JSON-encodes the sorted endpoints before hashing, so pairs like ("A-B", "C") and ("A", "B-C") no longer collide. Added a regression test (test_hyphenated_ids_do_not_collide).
  • Linting / pre-commit CI failure: Fixed in 63c8361 by applying ruff format. All pre-commit hooks now pass locally, and the LanceDB test suite (74 tests) passes.

@danielaskdd

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 63c8361440

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lightrag/kg/lancedb_impl.py Outdated
@shyuan

shyuan commented Jul 10, 2026

Copy link
Copy Markdown
Author

Systematic self-review pass (1690c43)

To converge faster than one bot finding per round, I ran a systematic multi-angle review of the whole branch (line-by-line scan, removed-behavior audit, cross-file contract tracing against base.py/operate.py/sibling backends, plus reuse/simplification/efficiency/altitude passes), focusing on the classes of issues flagged so far: lock ordering/interleaving and encoding invariants. Findings were verified against sibling-backend behavior before fixing.

Fixed in 1690c43

Concurrency hardening (same family as the two earlier review comments):

  • drop_and_recreate_table now takes the per-table write lock, so a drop can't yank a table from under an in-flight merge_insert
  • Vector drop() holds _buffer_lock across clear+recreate, so an in-flight flush can't re-persist cleared pending docs
  • Lock order is uniform everywhere: _buffer_lock → table_lock → creation_lock; no reverse acquisition exists
  • Regression tests added for the delete-vs-flush race (verified to fail on the pre-fix ordering) and drop-vs-flush serialization

Known limitation #2 from the PR description is now closed: the LANCEDB_WORKSPACE env override goes through validate_workspace like the constructor path, with a test.

Correctness/UX: LANCEDB_ENABLE_FTS now accepts the standard truthy set (1/yes/t/on) instead of only the literal true; full_text_search fails fast with a clear error when FTS is disabled instead of surfacing a raw missing-index error.

Efficiency on the query hot path: _edges_touching (backs node_degrees_batch/BFS) selects only id/src/tgt so edge payload JSON stays out of degree counting; _fetch_rows_by_ids runs id-chunk scans concurrently.

Maintainability: the gunicorn multi-worker guard is now driven by a MULTIPROCESS_UNSAFE_STORAGES registry set in lightrag/kg instead of a hard-coded class-name prefix check, so future embedded backends only touch the registry; removed 4 hand-written dataclass __init__s (milvus/redis parity).

Checked and deliberately unchanged (sibling parity)

For reviewer reference, these were examined and confirmed to match the established behavior of existing backends, so they were left as-is: created_at overwrite on re-upsert (nano/milvus/qdrant/mongo all do this; postgres is the lone exception), class-level asyncio.Lock in ClientManager (identical in mongo/postgres/opensearch, documented in lightrag.py), source-only node auto-creation in upsert_edge (mongo parity; all callers pre-create both endpoints), and raw-payload returns from get_doc_by_file_path-style lookups (json/mongo/postgres parity, callers handle sentinels).

All 77 LanceDB tests + tools suite pass; ruff clean.

@shyuan
shyuan force-pushed the feat/lancedb-storage-backend branch 3 times, most recently from 70b094a to 2977709 Compare July 15, 2026 16:49
shyuan added 6 commits July 17, 2026 15:39
Implement all four LightRAG storage types on embedded LanceDB in a
single local directory with no external services:

- LanceDBKVStorage / LanceDBVectorStorage / LanceDBGraphStorage /
  LanceDBDocStatusStorage in lightrag/kg/lancedb_impl.py, built on the
  native async API (connect_async) with a ref-counted shared connection,
  per-table write locks, and strong read consistency
- Deferred-embedding vector upserts (batched at index_done_callback),
  cosine query with similarity threshold, embedding-model-suffixed
  table names for model isolation
- CJK-friendly BM25 full-text search on content via ngram bigram
  tokenizer (LanceDBVectorStorage.full_text_search)
- Canonical undirected edge storage, client-side BFS knowledge graph,
  label search; passes the cross-backend graph conformance suite
- Guard lightrag-gunicorn against multi-worker use with LanceDB
- Tests (tests/kg/lancedb_impl, 59 offline tests on real tmp-dir
  LanceDB), runnable demo, docs, env.example and packaging updates

generated by Claude Code
Add LanceDB support to the three KV maintenance tools so the backend is no
longer excluded from vector-db rebuild and LLM cache maintenance:

- LanceDBKVStorage.get_all_keys() for id enumeration (the API the tools
  need but BaseKVStorage lacks)
- rebuild_vdb / clean_llm_query_cache / migrate_llm_cache: add
  LanceDBKVStorage branches to every storage dispatch, mirroring the
  existing OpenSearch/PG implementations
- tests on real tmp-dir LanceDB for the enumeration method and all three
  tools' LanceDB paths
- docs/lancedb.md Limitations and tool READMEs updated

generated by Claude Code
JSON-encode the sorted endpoints before hashing so pairs like
("A-B", "C") and ("A", "B-C") no longer map to the same edge ID.

generated by Claude Code
delete_entity_relation deleted persisted rows before pruning the pending
buffer, so an in-flight index_done_callback flush (which holds
_buffer_lock throughout) could merge_insert a pending relation for the
same entity after the table.delete ran and pop it before the prune saw
it, leaving an orphaned relation vector.

Prune the pending buffer first and nest table_lock inside _buffer_lock so
the delete and flush are serialized. Apply the same nesting to delete()
for consistency. Lock order stays buffer -> table, matching the flush
path, so no deadlock.

generated by Claude Code
…kend

Concurrency/validation hardening:
- Serialize drop_and_recreate_table with in-flight writers via table_lock
  (nested inside creation_lock ordering; no reverse acquisition exists)
- Hold _buffer_lock across vector drop()'s clear+recreate so an in-flight
  flush cannot re-persist cleared pending docs
- Validate LANCEDB_WORKSPACE env override with validate_workspace, same
  as the constructor path (closes known limitation HKUDS#2)
- Guard maybe_optimize after flush when the client was finalized

Correctness/UX:
- Parse LANCEDB_ENABLE_FTS with the same truthy set as get_env_value
  ("1"/"yes"/"t"/"on" no longer silently disable FTS)
- full_text_search now fails fast with a clear error when FTS is disabled

Efficiency:
- _edges_touching selects only id/src/tgt, keeping edge payload JSON out
  of degree counting and BFS (query hot path)
- _fetch_rows_by_ids runs id-chunk scans concurrently

Cleanup:
- Replace the gunicorn LanceDB name-prefix check with a registry-driven
  MULTIPROCESS_UNSAFE_STORAGES set in lightrag/kg
- Drop four hand-written dataclass __init__s; coerce workspace in
  __post_init__ (milvus/redis parity)
- Simplify table_lock to a single setdefault

Tests: regression tests for the delete-vs-flush race (verified to fail on
the pre-fix ordering), drop-vs-flush serialization, and env workspace
validation. 77 LanceDB tests + tools suite pass; ruff clean.

generated by Claude Code
@shyuan
shyuan force-pushed the feat/lancedb-storage-backend branch from 2977709 to 8745842 Compare July 17, 2026 07:40
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.

Support LanceDB as an embedded Vector + Graph (unified) storage backend

2 participants