Skip to content

feat(lancedb): enhance index types and CLI integration#787

Merged
XuanYang-cn merged 2 commits into
zilliztech:mainfrom
XuQianJin-Stars:feature/lancedb-enhancement
Jul 2, 2026
Merged

feat(lancedb): enhance index types and CLI integration#787
XuanYang-cn merged 2 commits into
zilliztech:mainfrom
XuQianJin-Stars:feature/lancedb-enhancement

Conversation

@XuQianJin-Stars

Copy link
Copy Markdown
Contributor

Summary

Rewrites the lancedb client for LanceDB, adding support for IVF_HNSW_SQ and IVF_HNSW_PQ index types, standard filter queries, PyArrow batch insert optimization, COS/GooseFS remote storage, and full CLI integration.

What's added

  • Client implementation: vectordb_bench/backend/clients/lancedb/{lancedb.py,config.py,cli.py}
  • CLI commands: vectordbbench LanceDB ... / LanceDBAutoIndex / LanceDBIVFPQ / LanceDBIVFHNSWSQ / LanceDBIVFHNSWPQ
  • Index type registration: vectordb_bench/backend/clients/api.py — add IVF_HNSW_SQ and IVF_HNSW_PQ to IndexType enum
  • Unit tests: tests/test_lancedb_config.py — 10 offline tests covering config defaults, to_dict connection options, index_param / search_param generation, metric parsing, case-config registry, and CLI structure
  • Entry point: vectordb_bench/cli/vectordbbench.py — register all new LanceDB CLI commands

Algorithms supported

Algorithm Build params Search params
IVF_PQ (default) num_partitions, num_sub_vectors, nbits, sample_rate, max_iterations nprobes, refine_factor
AutoIndex (auto — metric only) nprobes, refine_factor
IVF_HNSW_SQ num_partitions, m, ef_construction ef, nprobes, refine_factor
IVF_HNSW_PQ num_partitions, num_sub_vectors, m, ef_construction ef, nprobes, refine_factor
NONE (brute-force) refine_factor

Key improvements

  • Filter support: Implement standard prepare_filter() pattern (NumGE / StrEqual)
  • Batch insert: Use PyArrow FixedSizeListArray for batch writes instead of per-row dicts
  • Multi-process: Custom __deepcopy__ to solve Rust handle serialization issue
  • Remote storage: storage_options for COS/GooseFS backends
  • Optimize: compact_files + cleanup_old_versions support

Example

vectordbbench LanceDBIVFHNSWSQ --case-type Performance768D1M --k 10 \
    --uri /tmp/lancedb \
    --num-partitions 256 --m 16 --ef-construction 128 \
    --ef 64 --nprobes 20 --refine-factor 25
vectordbbench LanceDBIVFPQ --case-type Performance768D1M --k 10 \
    --uri s3://my-bucket/lancedb \
    --storage-options '{"aws_access_key_id":"...","aws_secret_access_key":"..."}' \
    --num-partitions 256 --num-sub-vectors 48 --nprobes 20

…upport, batch insert optimization and full CLI integration

Core changes:
- api.py: Add IVF_HNSW_SQ and IVF_HNSW_PQ to IndexType enum
- config.py: Rewrite into 5 independent config classes (IVF_PQ / NONE / AUTOINDEX / IVF_HNSW_SQ /
  IVF_HNSW_PQ); all IVF variants share refine_factor search param; add storage_options for remote storage
- lancedb.py: Implement standard prepare_filter() pattern (NumGE/StrEqual); support scalar labels insert;
  use PyArrow FixedSizeListArray for batch writes instead of per-row dicts; unify search param passing;
  optimize() supports compact_files + cleanup_old_versions; custom __deepcopy__ to solve
  multi-process Rust handle serialization issue; explicit select _distance to suppress lance deprecation warning
- cli.py: Add 5 CLI commands (LanceDB/AutoIndex/IVFPQ/IVFHNSWSQ/IVFHNSWPQ);
  fix IndexType.NONE lookup bug; add COS/GooseFS remote storage_options builder
- vectordbbench.py: Register all new LanceDB CLI commands

New files:
- docs/lancedb-enhancement-plan.md: Development plan and implementation notes
- docs/lancedb-integration.md: Integration verification report
- tests/test_lancedb_config.py: 10 offline unit tests covering config/registration/CLI structure
- scripts/bench_lancedb_500k.sh: One-click 500K three-index comparison benchmark script
- scripts/aggregate_lancedb_results.py: Aggregate results into Markdown comparison table
@XuQianJin-Stars

Copy link
Copy Markdown
Contributor Author

/assign @XuanYang-cn

@XuQianJin-Stars

Copy link
Copy Markdown
Contributor Author

Hi @XuanYang-cn, all checks have passed and there are no conflicts. Could you help review this PR when you get a chance? Thanks!

@XuanYang-cn XuanYang-cn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

IndexType.NONE skips create_index(), but search_embedding() never applies case_config.metric_type to the LanceDB query. For cosine benchmark cases, no-index LanceDB searches therefore use LanceDB's default L2 distance while recall is measured against cosine ground truth. Please apply .metric(self.case_config.parse_metric()) for the no-index path, or otherwise make search use the dataset metric consistently.

Previously search_embedding() relied on the index's metric being applied
implicitly. This works for indexed paths (IVF_PQ / IVF_HNSW_SQ /
IVF_HNSW_PQ / AutoIndex) but silently falls back to LanceDB's default
(L2) on the no-index brute-force path, which corrupts recall on
cosine / IP datasets whose ground-truth uses a different metric.

Fix: always call .metric(self.case_config.parse_metric()) on the query
builder. For indexed paths this is a no-op when the index metric
already matches; for the no-index path it aligns the scan with the
case-configured metric.
@XuQianJin-Stars

XuQianJin-Stars commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

IndexType.NONE skips create_index(), but search_embedding() never applies case_config.metric_type to the LanceDB query. For cosine benchmark cases, no-index LanceDB searches therefore use LanceDB's default L2 distance while recall is measured against cosine ground truth. Please apply .metric(self.case_config.parse_metric()) for the no-index path, or otherwise make search use the dataset metric consistently.

Thanks for the detailed review, @XuanYang-cn! You're absolutely right about the no-index path.

I've updated the PR to always pin the query metric to the case metric in search_embedding() (commit 8b799ff). Now the search consistently applies .metric(self.case_config.parse_metric()) regardless of whether an index is created, so the IndexType.NONE / cosine case will no longer fall back to LanceDB's default L2 distance while recall is measured against cosine ground truth.

Could you take another look when you get a chance? Thanks!

@XuanYang-cn XuanYang-cn self-requested a review July 2, 2026 13:56
@sre-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: XuanYang-cn, XuQianJin-Stars

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@XuanYang-cn XuanYang-cn merged commit ce06767 into zilliztech:main Jul 2, 2026
4 checks passed
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.

3 participants