Postmortem: Silent Data Loss in an In-Process Vector Store, and the Case for Durable Execution A developer discovered that calling optimize() on a ZVec collection recovered from an unclean process termination silently discards documents restored by WAL replay, reporting index_completeness: 1.0 while doc_count regresses to 0. The bug is specific to WAL-replayed data and highlights the need for durable execution guarantees in in-process vector stores used with workflow orchestrators. optimize on a ZVec Collection that had recovered from an unclean process termination silently discarded the documents restored by WAL replay. The call raised no exception, and stats on the same handle reported index completeness: 1.0 — a false signal of success. The loss only became visible when the collection was reopened from a separate process. doc count regressed from 150 to 0 after optimize on a crash-recovered collection with no prior flush calls. alibaba/zvec optimize treated the writing segment as having no WAL records and skipped persisting the recovered data.ZVec is an in-process vector database: no server process, no connection string, storage backed by write-ahead logging WAL on the local filesystem. That simplicity is attractive for embedding retrieval directly inside a workflow orchestrator, where the vector store is expected to behave like any other durable resource an orchestrator's Activities touch. For that use case, "crash-durable" is not a nice-to-have — it is a precondition for adoption. If a storage engine can silently lose state on process failure, it cannot be trusted as the backing store for anything a workflow engine expects to survive a crash and resume correctly. Before this bug surfaces, the base case is worth establishing, since it is where trust in the system starts. A hard-killed process os. exit 1 , equivalent to kill -9 with unflushed inserts fully recovers on zvec.open : crashed = zvec.open "./zvec crash test" print crashed.stats doc count=150 index completeness={'embedding': 0.0} doc count is correct immediately after a crash, purely from automatic WAL replay — no manual recovery call required. index completeness: 0.0 at this point is not an anomaly; it is the default state of any collection, crashed or not, until optimize has been called on it, and query still returns correct results while it holds. Calling optimize on a crash-recovered collection does not raise. Worse, stats on the same handle immediately afterward reports the pre-existing doc count unchanged, alongside index completeness: 1.0 — every observable signal on that handle says the optimize succeeded cleanly: optimize before: doc count=150 index completeness={'embedding': 0.0} optimize same-handle after optimize : doc count=150 index completeness={'embedding': 1.0} Neither the return value nor an exception nor the immediately-following stats call exposes the defect. This is the core hazard: a call that completes cleanly and reports a plausible, successful-looking state can still have discarded data. The regression is invisible on the handle that called optimize because that handle still holds the in-memory view of the data. It becomes visible only when a separate process reopens the same collection from disk: after-optimize doc count=0 index completeness={'embedding': 0.0} doc count regresses to whatever was last durably segmented to disk before the crash — in this case, 0, since no flush was ever called. The gap between the in-memory state a handle believes is true and the state actually persisted to disk is exactly what optimize 's bug exploited. A plausible but wrong hypothesis at this point is " optimize loses unflushed data." A control experiment rules that out: a collection with unflushed data but no crash history — created, populated, and shut down normally — retains 100% of its documents after optimize , unflushed data included. The loss is therefore not a property of "unflushed data" in general. It is specific to documents that were recovered via WAL replay from a genuine unclean termination. optimize 's index-rebuild path excluded that WAL-replayed data from the rebuilt segment rather than merging it in. A second experiment narrowed the loss boundary precisely. With a periodic flush cadence — every 100 documents, 551 total inserted — optimize on the crash-recovered collection left exactly 501 documents; IDs 501–550, everything inserted after the last flush at ID 500, were missing. The loss boundary is exactly the last durably-flushed segment, not "everything since the crash" and not "everything unflushed." A single experiment result is not treated as proof of a causal claim here — the crash-history-vs-no-crash-history comparison is what isolates the trigger. Root cause, in one sentence: segment recovery replayed WAL records into a local WAL handle, then closed it without restoring the member WAL handle on the segment — so optimize 's subsequent flush treated the writing segment as having no WAL records and skipped persisting them. The patch to src/db/index/segment/segment.cc : @@ -4212,8 +4212,6 @@ Status SegmentImpl::recover { wal file path.c str ; return Status::OK ; } - AILEGO DEFER & { recover wal file- close ; } ; - std::array