mirror of
https://github.com/qdrant/qdrant.git
synced 2026-08-04 00:51:06 -05:00
* Fix proxy deleted_mask race dropping live points from scored search ProxySegment::new snapshots the wrapped segment's deleted_mask while the optimizer holds only the upgradable-read lock, before the write lock freezes the segment. An upsert racing onto the still-appendable wrapped segment in that window lands at an internal offset past the snapshot. The scored search path (PlainVectorIndex::search) consults the proxy mask in place of the segment's live deleted state, and check_deleted_condition defaults any out-of-range offset to deleted (unwrap_or(true)) — so the live point is silently dropped from filtered KNN while scroll/count/retrieve still return it. Re-snapshot deleted_mask in the optimizer once the holder write lock is held (segment frozen) and before the proxy goes live, so the mask covers the segment's full final point range. The fresh read also captures any deletes that raced in, closing the ghost direction too. Adds a proxy-level regression test that reproduces the race without the model-testing harness. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Make proxy deleted_mask sync a type-state, read once ProxySegment::new now returns UnsyncedProxySegment instead of a ready-to-use ProxySegment. The deleted_mask snapshot is deferred to UnsyncedProxySegment::finalize(), which reads the wrapped segment's deleted bitvec exactly once. The only way to obtain a ProxySegment (and thus put it in a SegmentHolder) is via finalize(), so the sync under the holder write lock can no longer be forgotten, and the mask is no longer read twice (once in new, once in resync). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Allow clippy::new_ret_no_self on ProxySegment::new new deliberately returns the unsynced UnsyncedProxySegment stage rather than Self, since a usable ProxySegment only exists once deleted_mask is synced via finalize(). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Name the real constructor UnsyncedProxySegment::new, keep ProxySegment::new for tests Instead of allowing clippy::new_ret_no_self on a ProxySegment::new that returned UnsyncedProxySegment, give the two-phase constructor its natural home: UnsyncedProxySegment::new returns Self and is what production code (optimize, snapshot) uses, finalizing under the holder write lock. ProxySegment::new becomes a #[cfg(feature = "testing")] convenience that builds and finalizes in one step (returns Self), so existing test call sites stay terse and don't need an explicit .finalize(). The shard testing feature is enabled for both shard's own tests and collection's dev-dependency, and excluded from production builds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: generall <andrey@vasnetsov.com>