Invariant — Proton LID Reuse Requires Monotonic RCU Generation Drain
In vespa/searchcore (proton), a freed 32-bit Local Document ID (LID) in ReferenceLidTracker / LidAllocator must never be reassigned to a new document until every concurrent search reader holding a GenerationHandler::Guard from the removal generation or earlier has completed (first_used_generation > removal_generation).
Why It Holds (Chesterton Fence)
Search threads in proton read AttributeVector arrays and HNSW link lists without per-lookup locks (O(1) direct array indexing by LID; see proton-lid-space-and-attribute-vectors). If a removed LID were immediately reused while an in-flight query still held a posting list containing that LID, the query would silently score and return attributes belonging to the newly inserted document.
Seductive Wrong Takeaway (CRIT-001)
- Misconception:
remove(docId)immediately frees the integer LID for the nextput(docId). - Falsification:
LidAllocator::unregister_lidplaces removed LIDs onto a generation-tagged hold list (_holdLids) drained only aftergenerationHandler.update_oldest_used_generation()advances past the hold generation. - Do-Not-Import Boundary: Never bypass
GenerationHandlerhold queues when adding custom columnar or graph structures invespa/searchlib.