@@ -233,6 +233,11 @@ class HNSWIndex : public VecSimIndexAbstract<DataType, DistType>,
233233 double getEpsilon () const ;
234234 size_t indexSize () const override ;
235235 size_t indexCapacity () const override ;
236+ /* *
237+ * Checks if the index capacity is full to hint the caller a resize is needed.
238+ * @note Must be called with indexDataGuard locked.
239+ */
240+ size_t isCapacityFull () const ;
236241 size_t getEfConstruction () const ;
237242 size_t getM () const ;
238243 size_t getMaxLevel () const ;
@@ -312,6 +317,15 @@ class HNSWIndex : public VecSimIndexAbstract<DataType, DistType>,
312317 idToMetaData.shrink_to_fit ();
313318 resizeLabelLookup (idToMetaData.size ());
314319 }
320+
321+ size_t getStoredVectorsCount () const {
322+ size_t actual_stored_vec = 0 ;
323+ for (auto &block : vectorBlocks) {
324+ actual_stored_vec += block.getLength ();
325+ }
326+
327+ return actual_stored_vec;
328+ }
315329#endif
316330
317331protected:
@@ -357,6 +371,11 @@ size_t HNSWIndex<DataType, DistType>::indexCapacity() const {
357371 return this ->maxElements ;
358372}
359373
374+ template <typename DataType, typename DistType>
375+ size_t HNSWIndex<DataType, DistType>::isCapacityFull() const {
376+ return indexSize () == this ->maxElements ;
377+ }
378+
360379template <typename DataType, typename DistType>
361380size_t HNSWIndex<DataType, DistType>::getEfConstruction() const {
362381 return this ->efConstruction ;
@@ -1288,44 +1307,69 @@ template <typename DataType, typename DistType>
12881307void HNSWIndex<DataType, DistType>::resizeIndexCommon(size_t new_max_elements) {
12891308 assert (new_max_elements % this ->blockSize == 0 &&
12901309 " new_max_elements must be a multiple of blockSize" );
1291- this ->log (VecSimCommonStrings::LOG_VERBOSE_STRING,
1292- " Updating HNSW index capacity from %zu to %zu " , this -> maxElements , new_max_elements);
1310+ this ->log (VecSimCommonStrings::LOG_VERBOSE_STRING, " Resizing HNSW index from %zu to %zu " ,
1311+ idToMetaData. capacity () , new_max_elements);
12931312 resizeLabelLookup (new_max_elements);
12941313 visitedNodesHandlerPool.resize (new_max_elements);
1314+ assert (idToMetaData.capacity () == idToMetaData.size ());
12951315 idToMetaData.resize (new_max_elements);
12961316 idToMetaData.shrink_to_fit ();
1297-
1298- maxElements = new_max_elements;
1317+ assert (idToMetaData.capacity () == idToMetaData.size ());
12991318}
13001319
13011320template <typename DataType, typename DistType>
13021321void HNSWIndex<DataType, DistType>::growByBlock() {
1303- size_t new_max_elements = maxElements + this ->blockSize ;
1304-
13051322 // Validations
13061323 assert (vectorBlocks.size () == graphDataBlocks.size ());
13071324 assert (vectorBlocks.empty () || vectorBlocks.back ().getLength () == this ->blockSize );
1325+ assert (this ->maxElements % this ->blockSize == 0 );
1326+ assert (this ->maxElements == indexSize ());
1327+ assert (graphDataBlocks.size () == this ->maxElements / this ->blockSize );
1328+ assert (idToMetaData.capacity () == maxElements ||
1329+ idToMetaData.capacity () == maxElements + this ->blockSize );
13081330
1331+ this ->log (VecSimCommonStrings::LOG_VERBOSE_STRING,
1332+ " Updating HNSW index capacity from %zu to %zu" , maxElements,
1333+ maxElements + this ->blockSize );
1334+ maxElements += this ->blockSize ;
13091335 vectorBlocks.emplace_back (this ->blockSize , this ->dataSize , this ->allocator , this ->alignment );
13101336 graphDataBlocks.emplace_back (this ->blockSize , this ->elementGraphDataSize , this ->allocator );
13111337
1312- resizeIndexCommon (new_max_elements);
1338+ if (idToMetaData.capacity () == indexSize ()) {
1339+ resizeIndexCommon (maxElements);
1340+ }
13131341}
13141342
13151343template <typename DataType, typename DistType>
13161344void HNSWIndex<DataType, DistType>::shrinkByBlock() {
1317- assert (maxElements >= this ->blockSize );
1318- size_t new_max_elements = maxElements - this ->blockSize ;
1319-
1320- // Validations
1321- assert (vectorBlocks.size () == graphDataBlocks.size ());
1322- assert (!vectorBlocks.empty ());
1323- assert (vectorBlocks.back ().getLength () == 0 );
1324-
1325- vectorBlocks.pop_back ();
1326- graphDataBlocks.pop_back ();
1327-
1328- resizeIndexCommon (new_max_elements);
1345+ assert (this ->maxElements >= this ->blockSize );
1346+ assert (this ->maxElements % this ->blockSize == 0 );
1347+ if (indexSize () % this ->blockSize == 0 ) {
1348+ assert (vectorBlocks.back ().getLength () == 0 );
1349+ this ->log (VecSimCommonStrings::LOG_VERBOSE_STRING,
1350+ " Updating HNSW index capacity from %zu to %zu" , maxElements,
1351+ maxElements - this ->blockSize );
1352+ vectorBlocks.pop_back ();
1353+ graphDataBlocks.pop_back ();
1354+ assert (graphDataBlocks.size () * this ->blockSize == indexSize ());
1355+
1356+ if (idToMetaData.capacity () >= (indexSize () + 2 * this ->blockSize )) {
1357+ resizeIndexCommon (idToMetaData.capacity () - this ->blockSize );
1358+ } else if (idToMetaData.capacity () == this ->blockSize ) {
1359+ // Special case to handle last block.
1360+ // This special condition resolves the ambiguity: when capacity==blockSize, we can't
1361+ // tell if this block came from growth (should shrink to 0) or initial capacity (should
1362+ // keep it). We choose to always shrink to 0 to maintain the one-block removal
1363+ // guarantee. In contrast, newer branches without initial capacity support use simpler
1364+ // logic: immediately shrink to 0 whenever index size becomes 0.
1365+ assert (vectorBlocks.empty ());
1366+ assert (indexSize () == 0 );
1367+ assert (maxElements == this ->blockSize );
1368+ resizeIndexCommon (0 );
1369+ }
1370+ // Take the lower bound into account.
1371+ maxElements -= this ->blockSize ;
1372+ }
13291373}
13301374
13311375template <typename DataType, typename DistType>
@@ -1685,9 +1729,7 @@ void HNSWIndex<DataType, DistType>::removeAndSwap(idType internalId) {
16851729
16861730 // If we need to free a complete block and there is at least one block between the
16871731 // capacity and the size.
1688- if (curElementCount % this ->blockSize == 0 ) {
1689- shrinkByBlock ();
1690- }
1732+ shrinkByBlock ();
16911733}
16921734
16931735template <typename DataType, typename DistType>
@@ -1763,6 +1805,16 @@ void HNSWIndex<DataType, DistType>::removeVectorInPlace(const idType element_int
17631805template <typename DataType, typename DistType>
17641806AddVectorCtx HNSWIndex<DataType, DistType>::storeNewElement(labelType label,
17651807 const void *vector_data) {
1808+ if (isCapacityFull ()) {
1809+ growByBlock ();
1810+ } else if (curElementCount % this ->blockSize == 0 ) {
1811+ // If we had an initial capacity, we might have to initialize new blocks for the data and
1812+ // meta-data.
1813+ this ->vectorBlocks .emplace_back (this ->blockSize , this ->dataSize , this ->allocator ,
1814+ this ->alignment );
1815+ this ->graphDataBlocks .emplace_back (this ->blockSize , this ->elementGraphDataSize ,
1816+ this ->allocator );
1817+ }
17661818 AddVectorCtx state{};
17671819
17681820 // Choose randomly the maximum level in which the new element will be in the index.
@@ -1790,17 +1842,6 @@ AddVectorCtx HNSWIndex<DataType, DistType>::storeNewElement(labelType label,
17901842 throw e;
17911843 }
17921844
1793- if (indexSize () > indexCapacity ()) {
1794- growByBlock ();
1795- } else if (state.newElementId % this ->blockSize == 0 ) {
1796- // If we had an initial capacity, we might have to allocate new blocks for the data and
1797- // meta-data.
1798- this ->vectorBlocks .emplace_back (this ->blockSize , this ->dataSize , this ->allocator ,
1799- this ->alignment );
1800- this ->graphDataBlocks .emplace_back (this ->blockSize , this ->elementGraphDataSize ,
1801- this ->allocator );
1802- }
1803-
18041845 // Insert the new element to the data block
18051846 this ->vectorBlocks .back ().addElement (vector_data);
18061847 this ->graphDataBlocks .back ().addElement (cur_egd);
0 commit comments