Skip to content

Commit af26628

Browse files
committed
Enhance LanceDB index creation and search functionality with improved error handling and configuration options
1 parent 4f1fb07 commit af26628

6 files changed

Lines changed: 318 additions & 62 deletions

bindings/python/examples/11_vector_index_build.py

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -546,16 +546,54 @@ def create_index_lancedb(
546546
table,
547547
max_connections: int,
548548
beam_width: int,
549-
) -> None:
550-
partitions = max(1, min(256, int(max_connections) * 8))
551-
table.create_index(
552-
metric="cosine",
553-
vector_column_name="vector",
554-
index_type="IVF_HNSW_SQ",
555-
num_partitions=partitions,
556-
m=int(max_connections),
557-
ef_construction=int(beam_width),
558-
)
549+
) -> dict:
550+
common_kwargs = {
551+
"metric": "cosine",
552+
"vector_column_name": "vector",
553+
"m": int(max_connections),
554+
"ef_construction": int(beam_width),
555+
}
556+
attempts = [
557+
("HNSW", {}),
558+
("IVF_HNSW_SQ", {"num_partitions": 1}),
559+
]
560+
last_error: Exception | None = None
561+
562+
for index_type, extra_kwargs in attempts:
563+
try:
564+
table.create_index(
565+
index_type=index_type,
566+
**common_kwargs,
567+
**extra_kwargs,
568+
)
569+
config = {
570+
"metric": "cosine",
571+
"index_type": index_type,
572+
"hnsw_m": int(max_connections),
573+
"hnsw_ef_construct": int(beam_width),
574+
}
575+
if "num_partitions" in extra_kwargs:
576+
config["num_partitions"] = int(extra_kwargs["num_partitions"])
577+
config["quantization"] = "SQ"
578+
else:
579+
config["quantization"] = "NONE"
580+
581+
print(
582+
"Using LanceDB index mode: "
583+
f"{index_type}"
584+
+ (
585+
f" (num_partitions={extra_kwargs['num_partitions']})"
586+
if "num_partitions" in extra_kwargs
587+
else ""
588+
)
589+
)
590+
return config
591+
except (AttributeError, RuntimeError, TypeError, ValueError) as exc:
592+
last_error = exc
593+
594+
raise RuntimeError(
595+
"Unable to create LanceDB index in HNSW-like mode"
596+
) from last_error
559597

560598

561599
def vector_to_pg_literal(vec: np.ndarray) -> str:
@@ -1825,6 +1863,7 @@ def record(name: str, result, dur: float, rss_start: float, rss_end: float):
18251863
phases[name].update(result)
18261864

18271865
runtime_versions: dict[str, str | None] = {}
1866+
lancedb_index_config: dict[str, object] | None = None
18281867

18291868
if args.backend == "arcadedb_sql":
18301869
stop_cpu = start_cpu_logger(2)
@@ -1969,15 +2008,15 @@ def record(name: str, result, dur: float, rss_start: float, rss_end: float):
19692008
)
19702009
record("ingest", {"ingested": int(ingested)}, dur, r0, r1)
19712010

1972-
(_, dur, r0, r1) = timed_section(
2011+
(lancedb_index_config, dur, r0, r1) = timed_section(
19732012
"create_index",
19742013
lambda: create_index_lancedb(
19752014
table,
19762015
max_connections=args.max_connections,
19772016
beam_width=args.beam_width,
19782017
),
19792018
)
1980-
record("create_index", {}, dur, r0, r1)
2019+
record("create_index", lancedb_index_config, dur, r0, r1)
19812020

19822021
close_db_fn = getattr(db, "close", None)
19832022
(_, dur, r0, r1) = timed_section(
@@ -2379,8 +2418,18 @@ def server_pid_provider() -> int | None:
23792418
"lancedb": {
23802419
"data_dir": str(db_path / "lancedb-data"),
23812420
"table": "vectordata",
2382-
"metric": "cosine",
2383-
"index_type": "IVF_HNSW_SQ",
2421+
"metric": (
2422+
str((lancedb_index_config or {}).get("metric"))
2423+
if lancedb_index_config
2424+
else "cosine"
2425+
),
2426+
"index_type": (
2427+
str((lancedb_index_config or {}).get("index_type"))
2428+
if lancedb_index_config
2429+
else "IVF_HNSW_SQ"
2430+
),
2431+
"num_partitions": ((lancedb_index_config or {}).get("num_partitions")),
2432+
"quantization": ((lancedb_index_config or {}).get("quantization")),
23842433
"hnsw_m": args.max_connections,
23852434
"hnsw_ef_construct": args.beam_width,
23862435
},

bindings/python/examples/12_vector_search.py

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
- ArcadeDB/jvector uses `overquery_factor` directly.
1919
- HNSW backends expose `ef_search`.
2020
- These knobs are not semantically identical, so this benchmark uses a fixed
21-
normalization: `ef_search = 0.5 * k * overquery_factor` for pgvector/qdrant/milvus.
21+
normalization: `ef_search = 0.5 * k * overquery_factor` for
22+
faiss/pgvector/qdrant/milvus and LanceDB HNSW-like search tuning.
2223
"""
2324

2425
from __future__ import annotations
@@ -608,18 +609,48 @@ def search_lancedb(
608609
gt_full: dict[int, List[int]],
609610
k: int,
610611
overquery_factor: float,
612+
build_config: dict,
611613
) -> dict:
612614
latencies_ms: List[float] = []
613615
recalls: List[float] = []
614616

615-
nprobes = max(1, int(round(overquery_factor * 4)))
617+
lancedb_cfg = build_config.get("lancedb") if isinstance(build_config, dict) else {}
618+
if not isinstance(lancedb_cfg, dict):
619+
lancedb_cfg = {}
620+
621+
index_type = str(lancedb_cfg.get("index_type") or "IVF_HNSW_SQ").upper()
622+
ef_search = overquery_to_ef_search(k, overquery_factor)
623+
nprobes = None
624+
if index_type.startswith("IVF_"):
625+
nprobes = (
626+
1
627+
if int(lancedb_cfg.get("num_partitions") or 1) <= 1
628+
else max(1, int(round(overquery_factor * 4)))
629+
)
630+
631+
applied_ef_search = None
632+
applied_nprobes = None
616633

617634
for q_idx, qid in enumerate(qids):
618635
start = time.perf_counter()
619636
search = table.search(queries[q_idx].tolist()).metric("cosine").limit(int(k))
620-
if hasattr(search, "nprobes"):
637+
if hasattr(search, "ef"):
638+
try:
639+
search = search.ef(int(ef_search))
640+
applied_ef_search = int(ef_search)
641+
except Exception:
642+
pass
643+
elif hasattr(search, "ef_search"):
644+
try:
645+
search = search.ef_search(int(ef_search))
646+
applied_ef_search = int(ef_search)
647+
except Exception:
648+
pass
649+
650+
if nprobes is not None and hasattr(search, "nprobes"):
621651
try:
622652
search = search.nprobes(int(nprobes))
653+
applied_nprobes = int(nprobes)
623654
except Exception:
624655
pass
625656
rows = search.to_list()
@@ -646,6 +677,8 @@ def search_lancedb(
646677
"latency_ms_mean": lat_mean,
647678
"latency_ms_p95": lat_p95,
648679
"recall_count": len(recalls),
680+
"effective_ef_search": applied_ef_search,
681+
"effective_nprobes": applied_nprobes,
649682
}
650683

651684

@@ -1957,7 +1990,8 @@ def main() -> None:
19571990
default="1,2,3,4,6,8",
19581991
help=(
19591992
"Sweep values. For arcadedb: overquery factors. "
1960-
"For pgvector/qdrant/milvus: ef_search = 0.5 * k * factor. "
1993+
"For faiss/pgvector/qdrant/milvus and LanceDB HNSW-like tuning: "
1994+
"ef_search = 0.5 * k * factor. "
19611995
"Default: 1,2,3,4,6,8"
19621996
),
19631997
)
@@ -2267,6 +2301,7 @@ def record_phase(
22672301
gt_full,
22682302
k=args.k,
22692303
overquery_factor=overquery,
2304+
build_config=build_config,
22702305
),
22712306
queries=queries,
22722307
qids=qids,
@@ -2287,7 +2322,8 @@ def record_phase(
22872322
sweeps.append(
22882323
{
22892324
"overquery_factor": overquery,
2290-
"effective_nprobes": max(1, int(round(overquery * 4))),
2325+
"effective_ef_search": stats.get("effective_ef_search"),
2326+
"effective_nprobes": stats.get("effective_nprobes"),
22912327
"phases": phases,
22922328
"recall_mean": stats.get("recall_mean"),
22932329
"recall_count": stats.get("recall_count"),
@@ -2765,6 +2801,27 @@ def server_pid_provider() -> int | None:
27652801
}
27662802
for factor in overqueries
27672803
],
2804+
"lancedb_search_mapping": (
2805+
[
2806+
{
2807+
"factor": factor,
2808+
"effective_ef_search": overquery_to_ef_search(args.k, factor),
2809+
"effective_nprobes": (
2810+
1
2811+
if str(
2812+
((build_config.get("lancedb") or {}).get("index_type"))
2813+
or ""
2814+
)
2815+
.upper()
2816+
.startswith("IVF_")
2817+
else None
2818+
),
2819+
}
2820+
for factor in overqueries
2821+
]
2822+
if args.backend == "lancedb"
2823+
else None
2824+
),
27682825
"sweeps": sweeps,
27692826
},
27702827
"peak_rss_mb": peak_rss_mb,
@@ -2877,10 +2934,13 @@ def server_pid_provider() -> int | None:
28772934
recall_text = f"{recall:.4f}" if recall is not None else "n/a"
28782935
lat_text = f"{lat:.2f}" if lat is not None else "n/a"
28792936
p95_text = f"{p95:.2f}" if p95 is not None else "n/a"
2880-
if args.backend in {"pgvector", "qdrant", "milvus", "faiss"}:
2937+
if args.backend in {"pgvector", "qdrant", "milvus", "faiss", "lancedb"}:
28812938
ef_text = str(sweep.get("effective_ef_search"))
2939+
extra = ""
2940+
if args.backend == "lancedb" and sweep.get("effective_nprobes") is not None:
2941+
extra = f" | nprobes={sweep.get('effective_nprobes')}"
28822942
print(
2883-
f"factor={oq:>4} | ef_search={ef_text} | "
2943+
f"factor={oq:>4} | ef_search={ef_text}{extra} | "
28842944
f"recall@{args.k}={recall_text} | latency_mean_ms={lat_text} | "
28852945
f"latency_p95_ms={p95_text}"
28862946
)

bindings/python/examples/scripts/run_11_vector_index_build_matrix.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ source "$HELPERS_SH"
1515
# Large 10,000 32GB 16
1616
# X-Large 25,000 64GB 32
1717

18-
DATASET="stackoverflow-large"
18+
DATASET="stackoverflow-xlarge"
1919
BATCH_SIZE=10000
2020
MEM_LIMIT="32g"
2121
THREADS=4
@@ -49,8 +49,8 @@ MILVUS_PORT=19530
4949
MILVUS_COMPOSE_VERSION="v2.6.10"
5050
MILVUS_COLLECTION="vectordata"
5151

52-
# BACKENDS_RAW="arcadedb_sql,lancedb,faiss,pgvector,qdrant,milvus"
53-
BACKENDS_RAW="lancedb"
52+
BACKENDS_RAW="lancedb,pgvector,faiss,qdrant,milvus"
53+
# BACKENDS_RAW="arcadedb_sql"
5454
LABEL_PREFIX="sweep11"
5555

5656
if [[ $# -gt 0 ]]; then

0 commit comments

Comments
 (0)