Skip to content

Commit 0c76ef3

Browse files
author
Dmitry Razdoburdin
committed
fix translator size calculation
1 parent 0683f0c commit 0c76ef3

1 file changed

Lines changed: 35 additions & 11 deletions

File tree

include/svs/index/vamana/dynamic_index.h

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ class MutableVamanaIndex {
157157
std::vector<SlotMetadata> status_;
158158
size_t first_empty_ = 0;
159159
IDTranslator translator_;
160+
// Count of Valid slots. Maintained atomically in add_points/delete_entry.
161+
// Wrapped in unique_ptr because std::atomic is not movable.
162+
std::unique_ptr<std::atomic<size_t>> num_valid_{
163+
std::make_unique<std::atomic<size_t>>(0)};
160164
// Protects translator access: exclusive for writes (add/consolidate/compact),
161165
// shared for reads (delete/search). Wrapped in unique_ptr for movability.
162166
std::unique_ptr<std::shared_mutex> translator_mutex_{
@@ -200,6 +204,7 @@ class MutableVamanaIndex {
200204
, status_(data_.size(), SlotMetadata::Valid)
201205
, first_empty_{data_.size()}
202206
, translator_()
207+
, num_valid_{std::make_unique<std::atomic<size_t>>(data_.size())}
203208
, distance_{std::move(distance_function)}
204209
, threadpool_{threads::as_threadpool(std::move(threadpool_proto))}
205210
, search_parameters_{vamana::construct_default_search_parameters(data_)}
@@ -227,6 +232,7 @@ class MutableVamanaIndex {
227232
, status_(data_.size(), SlotMetadata::Valid)
228233
, first_empty_{data_.size()}
229234
, translator_()
235+
, num_valid_{std::make_unique<std::atomic<size_t>>(data_.size())}
230236
, distance_(std::move(distance_function))
231237
, threadpool_(threads::as_threadpool(std::move(threadpool_proto)))
232238
, search_parameters_(vamana::construct_default_search_parameters(data_))
@@ -293,6 +299,7 @@ class MutableVamanaIndex {
293299
, status_{data_.size(), SlotMetadata::Valid}
294300
, first_empty_{data_.size()}
295301
, translator_{std::move(translator)}
302+
, num_valid_{std::make_unique<std::atomic<size_t>>(data_.size())}
296303
, distance_{distance_function}
297304
, threadpool_{std::move(threadpool)}
298305
, search_parameters_{config.search_parameters}
@@ -368,7 +375,15 @@ class MutableVamanaIndex {
368375
///
369376
/// @brief Check whether the external ID `e` exists in the index.
370377
///
371-
bool has_id(size_t e) const { return translator_.has_external(e); }
378+
bool has_id(size_t e) const {
379+
if (!translator_.has_external(e)) {
380+
return false;
381+
}
382+
// Check slot is not Deleted (deferred translator cleanup).
383+
auto internal = translator_.get_internal(e);
384+
return std::atomic_ref<const SlotMetadata>(status_[internal])
385+
.load(std::memory_order_acquire) == SlotMetadata::Valid;
386+
}
372387

373388
///
374389
/// @brief Get the external ID mapped to be `i`.
@@ -390,8 +405,13 @@ class MutableVamanaIndex {
390405
/// each external ID in the index.
391406
///
392407
template <typename F> void on_ids(F&& f) const {
408+
// Skip entries whose slot is Deleted (deferred translator cleanup).
393409
for (auto pair : translator_) {
394-
f(pair.first);
410+
auto internal = pair.second;
411+
if (std::atomic_ref<const SlotMetadata>(status_[internal])
412+
.load(std::memory_order_acquire) == SlotMetadata::Valid) {
413+
f(pair.first);
414+
}
395415
}
396416
}
397417

@@ -405,11 +425,7 @@ class MutableVamanaIndex {
405425
}
406426

407427
/// @brief Return the number of **valid** (non-deleted) entries in the index.
408-
size_t size() const {
409-
// NB: Index translation should always be kept in-sync with the number of valid
410-
// elements.
411-
return translator_.size();
412-
}
428+
size_t size() const { return num_valid_->load(std::memory_order_acquire); }
413429

414430
///
415431
/// @brief Translate in-place a collection of internal IDs to external IDs.
@@ -734,6 +750,7 @@ class MutableVamanaIndex {
734750
std::atomic_ref<SlotMetadata>(status_[i])
735751
.store(SlotMetadata::Valid, std::memory_order_release);
736752
}
753+
num_valid_->fetch_add(slots.size(), std::memory_order_acq_rel);
737754

738755
return slots;
739756
}
@@ -778,10 +795,17 @@ class MutableVamanaIndex {
778795

779796
void delete_entry(size_t i) {
780797
auto& meta = getindex(status_, i);
781-
// allow silent double-deletions, requred for concurent deletions
782-
std::atomic_ref<SlotMetadata>(meta).store(
783-
SlotMetadata::Deleted, std::memory_order_release
784-
);
798+
// CAS Valid → Deleted. Only the thread that successfully transitions
799+
// decrements num_valid_; double-deletes silently no-op.
800+
SlotMetadata expected = SlotMetadata::Valid;
801+
if (std::atomic_ref<SlotMetadata>(meta).compare_exchange_strong(
802+
expected,
803+
SlotMetadata::Deleted,
804+
std::memory_order_acq_rel,
805+
std::memory_order_relaxed
806+
)) {
807+
num_valid_->fetch_sub(1, std::memory_order_acq_rel);
808+
}
785809
}
786810

787811
bool is_deleted(size_t i) const { return status_[i] != SlotMetadata::Valid; }

0 commit comments

Comments
 (0)