Version: lancedb 0.30.0, Python 3.11, Windows 11
What Happened
After calling table.optimize() on a table with a scalar index on the merge key column, subsequent merge_insert("path").when_matched_update_all() calls silently fail to match existing rows. No error is raised — the merge_insert completes but no rows are updated.
Steps to Reproduce
import lancedb
import pyarrow as pa
db = lancedb.connect("/tmp/test_merge_compact")
schema = pa.schema([
pa.field("vector", pa.list_(pa.float32(), 4), nullable=True),
pa.field("path", pa.utf8()),
pa.field("status", pa.utf8()),
pa.field("file_size", pa.int64()),
])
# 1. Create table with 500 rows
tbl = db.create_table("test", schema=schema)
records = [{"vector": None, "path": f"/img/{i}.jpg", "status": "pending", "file_size": 1000 + i} for i in range(500)]
tbl.add(pa.Table.from_pylist(records, schema=schema))
# 2. Create scalar index on the merge key
tbl.create_scalar_index("path")
# 3. merge_insert 128 rows — WORKS
updates = [{"path": f"/img/{i}.jpg", "vector": [0.1]*4, "status": "indexed"} for i in range(128)]
tbl.merge_insert("path").when_matched_update_all().execute(pa.Table.from_pylist(updates))
# Verify: 128 indexed ✅
# 4. compact
from datetime import timedelta
tbl.optimize(cleanup_older_than=timedelta(minutes=0))
# 5. merge_insert 128 more — SILENTLY FAILS
updates2 = [{"path": f"/img/{i}.jpg", "vector": [0.2]*4, "status": "indexed"} for i in range(128, 256)]
tbl.merge_insert("path").when_matched_update_all().execute(pa.Table.from_pylist(updates2))
# Check: only 128 indexed, not 256
import pyarrow.compute as pc
arrow = tbl.to_arrow().select(["status"])
n_indexed = pc.sum(pc.equal(arrow.column("status"), "indexed")).as_py()
print(f"Indexed: {n_indexed}") # Expected: 256, Actual: 128
Expected Behavior
After step 5, 256 rows should have status="indexed".
Actual Behavior
Only 128 rows are indexed. The merge_insert after optimize() silently fails to match paths, even though the paths exist in the table.
Root Cause Hypothesis
The scalar BTree index on the merge key column (path) becomes inconsistent after optimize() rebuilds it. The merge_insert join uses the scalar index for matching, but the rebuilt index references new fragment IDs that don't align with the merge_insert's internal state.
Workaround
- Don't call
optimize() between merge_insert calls
- Call
optimize() only after all merge_insert operations complete
- Adding
tbl.checkout_latest() after optimize does NOT fix it
Related
Environment
- lancedb: 0.30.0
- lance: 3.0.0
- Python: 3.11.14
- OS: Windows 11
Version: lancedb 0.30.0, Python 3.11, Windows 11
What Happened
After calling
table.optimize()on a table with a scalar index on the merge key column, subsequentmerge_insert("path").when_matched_update_all()calls silently fail to match existing rows. No error is raised — the merge_insert completes but no rows are updated.Steps to Reproduce
Expected Behavior
After step 5, 256 rows should have
status="indexed".Actual Behavior
Only 128 rows are indexed. The merge_insert after optimize() silently fails to match paths, even though the paths exist in the table.
Root Cause Hypothesis
The scalar BTree index on the merge key column (
path) becomes inconsistent afteroptimize()rebuilds it. The merge_insert join uses the scalar index for matching, but the rebuilt index references new fragment IDs that don't align with the merge_insert's internal state.Workaround
optimize()betweenmerge_insertcallsoptimize()only after all merge_insert operations completetbl.checkout_latest()after optimize does NOT fix itRelated
Environment