Skip to content

bug: merge_insert fails silently after optimize() with scalar index on match column #3177

Description

@Ishannaik

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions