Skip to content

Commit 5e08d77

Browse files
authored
[MOD-13819] Add two new metrics for tiered index observability: frontend buffer size and HNSW main thread insertion (#901)
* add tiered metrics - wip * format * add a test with write in place
1 parent bd3769b commit 5e08d77

File tree

5 files changed

+101
-0
lines changed

5 files changed

+101
-0
lines changed

src/VecSim/algorithms/hnsw/hnsw_tiered.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ class TieredHNSWIndex : public VecSimTieredIndex<DataType, DistType> {
9494
// associated swap jobs.
9595
std::mutex idToRepairJobsGuard;
9696

97+
// Counter for vectors inserted directly into HNSW by the main thread (bypassing flat buffer).
98+
// This happens in WriteInPlace mode or when the flat buffer is full.
99+
// Not atomic since it's only accessed from the main thread.
100+
size_t directHNSWInsertions{0};
101+
97102
void executeInsertJob(HNSWInsertJob *job);
98103
void executeRepairJob(HNSWRepairJob *job);
99104

@@ -211,6 +216,7 @@ class TieredHNSWIndex : public VecSimTieredIndex<DataType, DistType> {
211216
// needed.
212217
VecSimIndexDebugInfo debugInfo() const override;
213218
VecSimIndexBasicInfo basicInfo() const override;
219+
VecSimIndexStatsInfo statisticInfo() const override;
214220
VecSimDebugInfoIterator *debugInfoIterator() const override;
215221
VecSimBatchIterator *newBatchIterator(const void *queryBlob,
216222
VecSimQueryParams *queryParams) const override {
@@ -729,6 +735,8 @@ int TieredHNSWIndex<DataType, DistType>::addVector(const void *blob, labelType l
729735
this->lockMainIndexGuard();
730736
hnsw_index->addVector(storage_blob.get(), label);
731737
this->unlockMainIndexGuard();
738+
// Track direct insertion to HNSW (bypassing flat buffer)
739+
++this->directHNSWInsertions;
732740
return ret;
733741
}
734742
if (this->frontendIndex->indexSize() >= this->flatBufferLimit) {
@@ -746,6 +754,8 @@ int TieredHNSWIndex<DataType, DistType>::addVector(const void *blob, labelType l
746754
// index.
747755
auto storage_blob = this->frontendIndex->preprocessForStorage(blob);
748756
this->insertVectorToHNSW<false>(hnsw_index, label, storage_blob.get());
757+
// Track direct insertion to HNSW (flat buffer was full)
758+
++this->directHNSWInsertions;
749759
return ret;
750760
}
751761
// Otherwise, we fall back to the "regular" insertion into the flat buffer
@@ -1151,6 +1161,13 @@ void TieredHNSWIndex<DataType, DistType>::TieredHNSW_BatchIterator::filter_irrel
11511161
results.resize(cur_end - results.begin());
11521162
}
11531163

1164+
template <typename DataType, typename DistType>
1165+
VecSimIndexStatsInfo TieredHNSWIndex<DataType, DistType>::statisticInfo() const {
1166+
auto stats = VecSimTieredIndex<DataType, DistType>::statisticInfo();
1167+
stats.directHNSWInsertions = this->directHNSWInsertions;
1168+
return stats;
1169+
}
1170+
11541171
template <typename DataType, typename DistType>
11551172
VecSimIndexDebugInfo TieredHNSWIndex<DataType, DistType>::debugInfo() const {
11561173
auto info = VecSimTieredIndex<DataType, DistType>::debugInfo();

src/VecSim/vec_sim_common.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,9 @@ typedef struct {
343343
size_t memory;
344344
size_t numberOfMarkedDeleted; // The number of vectors that are marked as deleted (HNSW/tiered
345345
// only).
346+
size_t directHNSWInsertions; // Count of vectors inserted directly into HNSW by main thread
347+
// (bypassing flat buffer). Tiered HNSW only.
348+
size_t flatBufferSize; // Current flat buffer size. Tiered indexes only.
346349
} VecSimIndexStatsInfo;
347350

348351
typedef struct {

src/VecSim/vec_sim_index.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ struct VecSimIndexAbstract : public VecSimIndexInterface {
198198
return VecSimIndexStatsInfo{
199199
.memory = this->getAllocationSize(),
200200
.numberOfMarkedDeleted = 0,
201+
.directHNSWInsertions = 0,
202+
.flatBufferSize = 0,
201203
};
202204
}
203205

src/VecSim/vec_sim_tiered_index.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,8 @@ VecSimIndexStatsInfo VecSimTieredIndex<DataType, DistType>::statisticInfo() cons
320320
auto stats = VecSimIndexStatsInfo{
321321
.memory = this->getAllocationSize(),
322322
.numberOfMarkedDeleted = this->getNumMarkedDeleted(),
323+
.directHNSWInsertions = 0, // Base tiered index returns 0; TieredHNSWIndex overrides
324+
.flatBufferSize = this->frontendIndex->indexSize(),
323325
};
324326

325327
return stats;

tests/unit/test_hnsw_tiered.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2761,6 +2761,9 @@ TYPED_TEST(HNSWTieredIndexTest, testInfo) {
27612761
EXPECT_EQ(info.tieredInfo.backgroundIndexing, false);
27622762
EXPECT_EQ(info.tieredInfo.bufferLimit, 1000);
27632763
EXPECT_EQ(info.tieredInfo.specificTieredBackendInfo.hnswTieredInfo.pendingSwapJobsThreshold, 1);
2764+
// Verify new tiered-specific stats
2765+
EXPECT_EQ(stats.flatBufferSize, 0);
2766+
EXPECT_EQ(stats.directHNSWInsertions, 0);
27642767

27652768
// Validate that Static info returns the right restricted info as well.
27662769
VecSimIndexBasicInfo s_info = VecSimIndex_BasicInfo(tiered_index);
@@ -2787,6 +2790,9 @@ TYPED_TEST(HNSWTieredIndexTest, testInfo) {
27872790
info.tieredInfo.frontendCommonInfo.memory);
27882791
EXPECT_EQ(info.commonInfo.memory, stats.memory);
27892792
EXPECT_EQ(info.tieredInfo.backgroundIndexing, true);
2793+
// Vector is in flat buffer, no direct insertions yet
2794+
EXPECT_EQ(stats.flatBufferSize, 1);
2795+
EXPECT_EQ(stats.directHNSWInsertions, 0);
27902796

27912797
mock_thread_pool.thread_iteration();
27922798
info = tiered_index->debugInfo();
@@ -2803,6 +2809,9 @@ TYPED_TEST(HNSWTieredIndexTest, testInfo) {
28032809
info.tieredInfo.frontendCommonInfo.memory);
28042810
EXPECT_EQ(info.commonInfo.memory, stats.memory);
28052811
EXPECT_EQ(info.tieredInfo.backgroundIndexing, false);
2812+
// Vector moved from flat buffer to HNSW by background thread
2813+
EXPECT_EQ(stats.flatBufferSize, 0);
2814+
EXPECT_EQ(stats.directHNSWInsertions, 0);
28062815

28072816
if (TypeParam::isMulti()) {
28082817
GenerateAndAddVector<TEST_DATA_T>(tiered_index, dim, 1, 1);
@@ -2839,6 +2848,74 @@ TYPED_TEST(HNSWTieredIndexTest, testInfo) {
28392848
EXPECT_EQ(info.tieredInfo.backgroundIndexing, false);
28402849
}
28412850

2851+
TYPED_TEST(HNSWTieredIndexTest, testDirectHNSWInsertionsStats) {
2852+
// Test that directHNSWInsertions counter is incremented when flat buffer is full.
2853+
size_t dim = 4;
2854+
size_t buffer_limit = 5;
2855+
HNSWParams params = {.type = TypeParam::get_index_type(),
2856+
.dim = dim,
2857+
.metric = VecSimMetric_L2,
2858+
.multi = TypeParam::isMulti()};
2859+
VecSimParams hnsw_params = CreateParams(params);
2860+
auto mock_thread_pool = tieredIndexMock();
2861+
2862+
// Create index with small buffer limit
2863+
auto *tiered_index =
2864+
this->CreateTieredHNSWIndex(hnsw_params, mock_thread_pool, 1, buffer_limit);
2865+
2866+
// Fill the flat buffer
2867+
for (size_t i = 0; i < buffer_limit; i++) {
2868+
GenerateAndAddVector<TEST_DATA_T>(tiered_index, dim, i, i);
2869+
}
2870+
2871+
VecSimIndexStatsInfo stats = tiered_index->statisticInfo();
2872+
EXPECT_EQ(stats.flatBufferSize, buffer_limit);
2873+
EXPECT_EQ(stats.directHNSWInsertions, 0);
2874+
2875+
// Add more vectors - these should go directly to HNSW
2876+
size_t extra_vectors = 3;
2877+
for (size_t i = buffer_limit; i < buffer_limit + extra_vectors; i++) {
2878+
GenerateAndAddVector<TEST_DATA_T>(tiered_index, dim, i, i);
2879+
}
2880+
2881+
stats = tiered_index->statisticInfo();
2882+
EXPECT_EQ(stats.flatBufferSize, buffer_limit);
2883+
EXPECT_EQ(stats.directHNSWInsertions, extra_vectors);
2884+
2885+
// Drain the flat buffer by starting threads and waiting for them to finish
2886+
mock_thread_pool.init_threads();
2887+
mock_thread_pool.thread_pool_join();
2888+
2889+
stats = tiered_index->statisticInfo();
2890+
EXPECT_EQ(stats.flatBufferSize, 0);
2891+
// Direct insertions counter should be preserved
2892+
EXPECT_EQ(stats.directHNSWInsertions, extra_vectors);
2893+
2894+
// Test write-in-place mode: vectors should go directly to HNSW even when buffer is not full
2895+
VecSim_SetWriteMode(VecSim_WriteInPlace);
2896+
2897+
size_t write_in_place_vectors = 4;
2898+
size_t label_offset = buffer_limit + extra_vectors;
2899+
for (size_t i = 0; i < write_in_place_vectors; i++) {
2900+
GenerateAndAddVector<TEST_DATA_T>(tiered_index, dim, label_offset + i, label_offset + i);
2901+
}
2902+
2903+
stats = tiered_index->statisticInfo();
2904+
// Flat buffer should still be empty (vectors went directly to HNSW)
2905+
EXPECT_EQ(stats.flatBufferSize, 0);
2906+
// Direct insertions counter should include the write-in-place vectors
2907+
EXPECT_EQ(stats.directHNSWInsertions, extra_vectors + write_in_place_vectors);
2908+
2909+
// Verify all vectors are in the backend index
2910+
VecSimIndexDebugInfo info = tiered_index->debugInfo();
2911+
EXPECT_EQ(info.tieredInfo.backendCommonInfo.indexSize,
2912+
buffer_limit + extra_vectors + write_in_place_vectors);
2913+
EXPECT_EQ(info.tieredInfo.frontendCommonInfo.indexSize, 0);
2914+
2915+
// Reset to async mode
2916+
VecSim_SetWriteMode(VecSim_WriteAsync);
2917+
}
2918+
28422919
TYPED_TEST(HNSWTieredIndexTest, testInfoIterator) {
28432920
// Create TieredHNSW index instance with a mock queue.
28442921
size_t dim = 4;

0 commit comments

Comments
 (0)