fix(vector): address PR review round 4 — backfill resilience + degraded-mode docs
- Remove three stale `# Use numeric file ID` / `# Pass file path` comments in scanner.py. file_id is already normalized to str() above each call site, so the inline comments mislead readers. - Wrap `_backfill_doc_id_to_string` scroll loop + sentinel upsert in try/except Exception. The qdrant_client singleton is assigned before this migration runs, so a transient scroll failure was leaving the process holding a usable client with int payloads permanently unbackfilled until the next restart. Catch broadly, log ERROR with exc_info, and return without writing the sentinel — next process restart retries from scratch. - Note `:memory:` mode behavior near the sentinel constants so future readers don't read the every-start scroll as a bug. - Document the two degraded-migration ERROR log signals in docs/configuration.md so operators know when a clean restart is required to recover indexing. - Add unit test asserting scroll-time exceptions are logged and swallowed without writing the sentinel. Closes round-4 review feedback on PR #773. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
02744a50e0
commit
b97ac23228
@@ -387,3 +387,34 @@ async def test_backfill_handles_payload_with_explicit_none_doc_id(mocker):
|
||||
points=[2],
|
||||
wait=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
async def test_backfill_logs_and_returns_when_scroll_raises(mocker, caplog):
|
||||
"""A scroll-time exception is logged and swallowed; sentinel is not written.
|
||||
|
||||
The singleton client in get_qdrant_client is already assigned by the
|
||||
time _backfill_doc_id_to_string runs, so re-raising here would leave
|
||||
the process holding a usable client with the migration silently
|
||||
skipped on every subsequent call. Catching, logging, and returning
|
||||
without writing the sentinel preserves retry-on-next-restart behavior.
|
||||
"""
|
||||
client = mocker.AsyncMock()
|
||||
client.retrieve.return_value = [] # No sentinel — backfill must run
|
||||
client.scroll.side_effect = RuntimeError("boom")
|
||||
|
||||
with caplog.at_level("ERROR", logger="nextcloud_mcp_server.vector.qdrant_client"):
|
||||
await _backfill_doc_id_to_string(
|
||||
client, "test-collection", _backfill_dimension()
|
||||
)
|
||||
|
||||
# No sentinel written — next process restart will retry from scratch.
|
||||
client.upsert.assert_not_awaited()
|
||||
client.set_payload.assert_not_awaited()
|
||||
errors = [r for r in caplog.records if r.levelname == "ERROR"]
|
||||
assert len(errors) == 1
|
||||
assert "doc_id backfill failed" in errors[0].getMessage()
|
||||
assert "test-collection" in errors[0].getMessage()
|
||||
# exc_info=True attaches the original exception to the log record.
|
||||
assert errors[0].exc_info is not None
|
||||
assert errors[0].exc_info[0] is RuntimeError
|
||||
|
||||
Reference in New Issue
Block a user