@@ -234,6 +234,11 @@ class HNSWIndex : public VecSimIndexAbstract<DistType>,
234234 double getEpsilon () const ;
235235 size_t indexSize () const override ;
236236 size_t indexCapacity () const override ;
237+ /* *
238+ * Checks if the index capacity is full to hint the caller a resize is needed.
239+ * @note Must be called with indexDataGuard locked.
240+ */
241+ size_t isCapacityFull () const ;
237242 size_t getEfConstruction () const ;
238243 size_t getM () const ;
239244 size_t getMaxLevel () const ;
@@ -313,6 +318,15 @@ class HNSWIndex : public VecSimIndexAbstract<DistType>,
313318 */
314319 virtual void getDataByLabel (labelType label,
315320 std::vector<std::vector<DataType>> &vectors_output) const = 0;
321+
322+ size_t getStoredVectorsCount () const {
323+ size_t actual_stored_vec = 0 ;
324+ for (auto &block : vectorBlocks) {
325+ actual_stored_vec += block.getLength ();
326+ }
327+
328+ return actual_stored_vec;
329+ }
316330#endif
317331
318332protected:
@@ -358,6 +372,11 @@ size_t HNSWIndex<DataType, DistType>::indexCapacity() const {
358372 return this ->maxElements ;
359373}
360374
375+ template <typename DataType, typename DistType>
376+ size_t HNSWIndex<DataType, DistType>::isCapacityFull() const {
377+ return indexSize () == this ->maxElements ;
378+ }
379+
361380template <typename DataType, typename DistType>
362381size_t HNSWIndex<DataType, DistType>::getEfConstruction() const {
363382 return this ->efConstruction ;
@@ -1289,44 +1308,69 @@ template <typename DataType, typename DistType>
12891308void HNSWIndex<DataType, DistType>::resizeIndexCommon(size_t new_max_elements) {
12901309 assert (new_max_elements % this ->blockSize == 0 &&
12911310 " new_max_elements must be a multiple of blockSize" );
1292- this ->log (VecSimCommonStrings::LOG_VERBOSE_STRING,
1293- " Updating HNSW index capacity from %zu to %zu " , this -> maxElements , new_max_elements);
1311+ this ->log (VecSimCommonStrings::LOG_VERBOSE_STRING, " Resizing HNSW index from %zu to %zu " ,
1312+ idToMetaData. capacity () , new_max_elements);
12941313 resizeLabelLookup (new_max_elements);
12951314 visitedNodesHandlerPool.resize (new_max_elements);
1315+ assert (idToMetaData.capacity () == idToMetaData.size ());
12961316 idToMetaData.resize (new_max_elements);
12971317 idToMetaData.shrink_to_fit ();
1298-
1299- maxElements = new_max_elements;
1318+ assert (idToMetaData.capacity () == idToMetaData.size ());
13001319}
13011320
13021321template <typename DataType, typename DistType>
13031322void HNSWIndex<DataType, DistType>::growByBlock() {
1304- size_t new_max_elements = maxElements + this ->blockSize ;
1305-
13061323 // Validations
13071324 assert (vectorBlocks.size () == graphDataBlocks.size ());
13081325 assert (vectorBlocks.empty () || vectorBlocks.back ().getLength () == this ->blockSize );
1326+ assert (this ->maxElements % this ->blockSize == 0 );
1327+ assert (this ->maxElements == indexSize ());
1328+ assert (graphDataBlocks.size () == this ->maxElements / this ->blockSize );
1329+ assert (idToMetaData.capacity () == maxElements ||
1330+ idToMetaData.capacity () == maxElements + this ->blockSize );
13091331
1332+ this ->log (VecSimCommonStrings::LOG_VERBOSE_STRING,
1333+ " Updating HNSW index capacity from %zu to %zu" , maxElements,
1334+ maxElements + this ->blockSize );
1335+ maxElements += this ->blockSize ;
13101336 vectorBlocks.emplace_back (this ->blockSize , this ->dataSize , this ->allocator , this ->alignment );
13111337 graphDataBlocks.emplace_back (this ->blockSize , this ->elementGraphDataSize , this ->allocator );
13121338
1313- resizeIndexCommon (new_max_elements);
1339+ if (idToMetaData.capacity () == indexSize ()) {
1340+ resizeIndexCommon (maxElements);
1341+ }
13141342}
13151343
13161344template <typename DataType, typename DistType>
13171345void HNSWIndex<DataType, DistType>::shrinkByBlock() {
1318- assert (maxElements >= this ->blockSize );
1319- size_t new_max_elements = maxElements - this ->blockSize ;
1320-
1321- // Validations
1322- assert (vectorBlocks.size () == graphDataBlocks.size ());
1323- assert (!vectorBlocks.empty ());
1324- assert (vectorBlocks.back ().getLength () == 0 );
1325-
1326- vectorBlocks.pop_back ();
1327- graphDataBlocks.pop_back ();
1328-
1329- resizeIndexCommon (new_max_elements);
1346+ assert (this ->maxElements >= this ->blockSize );
1347+ assert (this ->maxElements % this ->blockSize == 0 );
1348+ if (indexSize () % this ->blockSize == 0 ) {
1349+ assert (vectorBlocks.back ().getLength () == 0 );
1350+ this ->log (VecSimCommonStrings::LOG_VERBOSE_STRING,
1351+ " Updating HNSW index capacity from %zu to %zu" , maxElements,
1352+ maxElements - this ->blockSize );
1353+ vectorBlocks.pop_back ();
1354+ graphDataBlocks.pop_back ();
1355+ assert (graphDataBlocks.size () * this ->blockSize == indexSize ());
1356+
1357+ if (idToMetaData.capacity () >= (indexSize () + 2 * this ->blockSize )) {
1358+ resizeIndexCommon (idToMetaData.capacity () - this ->blockSize );
1359+ } else if (idToMetaData.capacity () == this ->blockSize ) {
1360+ // Special case to handle last block.
1361+ // This special condition resolves the ambiguity: when capacity==blockSize, we can't
1362+ // tell if this block came from growth (should shrink to 0) or initial capacity (should
1363+ // keep it). We choose to always shrink to 0 to maintain the one-block removal
1364+ // guarantee. In contrast, newer branches without initial capacity support use simpler
1365+ // logic: immediately shrink to 0 whenever index size becomes 0.
1366+ assert (vectorBlocks.empty ());
1367+ assert (indexSize () == 0 );
1368+ assert (maxElements == this ->blockSize );
1369+ resizeIndexCommon (0 );
1370+ }
1371+ // Take the lower bound into account.
1372+ maxElements -= this ->blockSize ;
1373+ }
13301374}
13311375
13321376template <typename DataType, typename DistType>
@@ -1686,9 +1730,7 @@ void HNSWIndex<DataType, DistType>::removeAndSwap(idType internalId) {
16861730
16871731 // If we need to free a complete block and there is at least one block between the
16881732 // capacity and the size.
1689- if (curElementCount % this ->blockSize == 0 ) {
1690- shrinkByBlock ();
1691- }
1733+ shrinkByBlock ();
16921734}
16931735
16941736template <typename DataType, typename DistType>
@@ -1764,6 +1806,16 @@ void HNSWIndex<DataType, DistType>::removeVectorInPlace(const idType element_int
17641806template <typename DataType, typename DistType>
17651807AddVectorCtx HNSWIndex<DataType, DistType>::storeNewElement(labelType label,
17661808 const void *vector_data) {
1809+ if (isCapacityFull ()) {
1810+ growByBlock ();
1811+ } else if (curElementCount % this ->blockSize == 0 ) {
1812+ // If we had an initial capacity, we might have to initialize new blocks for the data and
1813+ // meta-data.
1814+ this ->vectorBlocks .emplace_back (this ->blockSize , this ->dataSize , this ->allocator ,
1815+ this ->alignment );
1816+ this ->graphDataBlocks .emplace_back (this ->blockSize , this ->elementGraphDataSize ,
1817+ this ->allocator );
1818+ }
17671819 AddVectorCtx state{};
17681820
17691821 // Choose randomly the maximum level in which the new element will be in the index.
@@ -1791,17 +1843,6 @@ AddVectorCtx HNSWIndex<DataType, DistType>::storeNewElement(labelType label,
17911843 throw e;
17921844 }
17931845
1794- if (indexSize () > indexCapacity ()) {
1795- growByBlock ();
1796- } else if (state.newElementId % this ->blockSize == 0 ) {
1797- // If we had an initial capacity, we might have to allocate new blocks for the data and
1798- // meta-data.
1799- this ->vectorBlocks .emplace_back (this ->blockSize , this ->dataSize , this ->allocator ,
1800- this ->alignment );
1801- this ->graphDataBlocks .emplace_back (this ->blockSize , this ->elementGraphDataSize ,
1802- this ->allocator );
1803- }
1804-
18051846 // Insert the new element to the data block
18061847 this ->vectorBlocks .back ().addElement (vector_data);
18071848 this ->graphDataBlocks .back ().addElement (cur_egd);
0 commit comments