pgvector.git / summary / log / commit / refs
commit eedba7ee144c4fa889a820cc2bd936cd5d973cf4
Author: Andrew Kane <andrew@ankane.org>
Date: Tue Jun 16 16:44:34 2026 +0000
Fixed possible index corruption with HNSW vacuuming - resolves #988
Co-authored-by: Bhagyesh Chaturvedi <bhagyeshc@google.com>
CHANGELOG.md | 1 +
src/hnsw.h | 1 +
src/hnswvacuum.c | 55 +++++++++++++++++++++++++++++++++++++++++++++----------
3 files changed, 47 insertions(+), 10 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fa998baa..f7134485 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
## 0.8.3 (unreleased)
+- Fixed possible index corruption with HNSW vacuuming
- Fixed performance regression with Hamming distance and Jaccard distance with Postgres 18
## 0.8.2 (2026-02-25)
diff --git a/src/hnsw.h b/src/hnsw.h
index 65a74e8a..f4492b5d 100644
--- a/src/hnsw.h
+++ b/src/hnsw.h
@@ -412,6 +412,7 @@ typedef struct HnswVacuumState
BufferAccessStrategy bas;
HnswNeighborTuple ntup;
HnswElementData highestPoint;
+ HnswElementData fallbackPoint;
/* Memory */
MemoryContext tmpCtx;
diff --git a/src/hnswvacuum.c b/src/hnswvacuum.c
index 5de9b43b..fe0e9809 100644
--- a/src/hnswvacuum.c
+++ b/src/hnswvacuum.c
@@ -37,17 +37,20 @@ RemoveHeapTids(HnswVacuumState * vacuumstate)
{
BlockNumber blkno = HNSW_HEAD_BLKNO;
HnswElement highestPoint = &vacuumstate->highestPoint;
+ HnswElement fallbackPoint = &vacuumstate->fallbackPoint;
Relation index = vacuumstate->index;
BufferAccessStrategy bas = vacuumstate->bas;
- HnswElement entryPoint = HnswGetEntryPoint(vacuumstate->index);
IndexBulkDeleteResult *stats = vacuumstate->stats;
- /* Store separately since highestPoint.level is uint8 */
+ /* Store separately since HnswElement level is uint8 */
int highestLevel = -1;
+ int fallbackLevel = -1;
- /* Initialize highest point */
+ /* Initialize highest point and fallback point */
highestPoint->blkno = InvalidBlockNumber;
highestPoint->offno = InvalidOffsetNumber;
+ fallbackPoint->blkno = InvalidBlockNumber;
+ fallbackPoint->offno = InvalidOffsetNumber;
while (BlockNumberIsValid(blkno))
{
@@ -119,14 +122,31 @@ RemoveHeapTids(HnswVacuumState * vacuumstate)
tidhash_insert(vacuumstate->deleted, ip, &found);
Assert(!found);
}
- else if (etup->level > highestLevel && !(entryPoint != NULL && blkno == entryPoint->blkno && offno == entryPoint->offno))
+ else if (etup->level > highestLevel)
{
- /* Keep track of highest non-entry point */
+ if (BlockNumberIsValid(highestPoint->blkno))
+ {
+ /* Current highest point becomes fallback */
+ fallbackPoint->blkno = highestPoint->blkno;
+ fallbackPoint->offno = highestPoint->offno;
+ fallbackPoint->level = highestPoint->level;
+ fallbackLevel = highestLevel;
+ }
+
+ /* Keep track of highest point */
highestPoint->blkno = blkno;
highestPoint->offno = offno;
highestPoint->level = etup->level;
highestLevel = etup->level;
}
+ else if (etup->level > fallbackLevel)
+ {
+ /* Keep track of second highest point */
+ fallbackPoint->blkno = blkno;
+ fallbackPoint->offno = offno;
+ fallbackPoint->level = etup->level;
+ fallbackLevel = etup->level;
+ }
}
blkno = HnswPageGetOpaque(page)->nextblkno;
@@ -269,12 +289,27 @@ RepairGraphEntryPoint(HnswVacuumState * vacuumstate)
/* Get a shared lock */
LockPage(index, HNSW_UPDATE_LOCK, ShareLock);
- /* Load element */
- HnswLoadElement(highestPoint, NULL, NULL, index, support, true, NULL);
+ /* Get latest entry point */
+ entryPoint = HnswGetEntryPoint(index);
+
+ /* Use fallback point if highest point is entry point */
+ if (entryPoint != NULL && entryPoint->blkno == highestPoint->blkno && entryPoint->offno == highestPoint->offno)
+ {
+ highestPoint = &vacuumstate->fallbackPoint;
+
+ if (!BlockNumberIsValid(highestPoint->blkno))
+ highestPoint = NULL;
+ }
- /* Repair if needed */
- if (NeedsUpdated(vacuumstate, highestPoint))
- RepairGraphElement(vacuumstate, highestPoint, HnswGetEntryPoint(index));
+ if (highestPoint != NULL)
+ {
+ /* Load element */
+ HnswLoadElement(highestPoint, NULL, NULL, index, support, true, NULL);
+
+ /* Repair if needed */
+ if (NeedsUpdated(vacuumstate, highestPoint))
+ RepairGraphElement(vacuumstate, highestPoint, entryPoint);
+ }
/* Release lock */
UnlockPage(index, HNSW_UPDATE_LOCK, ShareLock);
[parent: 32284ba28a21]