1
1
#include "postgres.h"
2
2
3
+ #include <math.h>
4
+
3
5
#include "access/relscan.h"
4
6
#include "hnsw.h"
5
7
#include "pgstat.h"
@@ -21,25 +23,57 @@ GetScanItems(IndexScanDesc scan, Datum value)
21
23
int m ;
22
24
HnswElement entryPoint ;
23
25
char * base = NULL ;
24
- HnswQuery q ;
25
-
26
- q .value = value ;
26
+ HnswQuery * q = & so -> q ;
27
27
28
28
/* Get m and entry point */
29
29
HnswGetMetaPageInfo (index , & m , & entryPoint );
30
30
31
+ q -> value = value ;
32
+ so -> m = m ;
33
+
31
34
if (entryPoint == NULL )
32
35
return NIL ;
33
36
34
- ep = list_make1 (HnswEntryCandidate (base , entryPoint , & q , index , support , false));
37
+ ep = list_make1 (HnswEntryCandidate (base , entryPoint , q , index , support , false));
35
38
36
39
for (int lc = entryPoint -> level ; lc >= 1 ; lc -- )
37
40
{
38
- w = HnswSearchLayer (base , & q , ep , 1 , lc , index , support , m , false, NULL );
41
+ w = HnswSearchLayer (base , q , ep , 1 , lc , index , support , m , false, NULL , NULL , NULL , true , NULL );
39
42
ep = w ;
40
43
}
41
44
42
- return HnswSearchLayer (base , & q , ep , hnsw_ef_search , 0 , index , support , m , false, NULL );
45
+ return HnswSearchLayer (base , q , ep , hnsw_ef_search , 0 , index , support , m , false, NULL , & so -> v , hnsw_iterative_search != HNSW_ITERATIVE_SEARCH_OFF ? & so -> discarded : NULL , true, & so -> tuples );
46
+ }
47
+
48
+ /*
49
+ * Resume scan at ground level with discarded candidates
50
+ */
51
+ static List *
52
+ ResumeScanItems (IndexScanDesc scan )
53
+ {
54
+ HnswScanOpaque so = (HnswScanOpaque ) scan -> opaque ;
55
+ Relation index = scan -> indexRelation ;
56
+ List * ep = NIL ;
57
+ char * base = NULL ;
58
+ int batch_size = hnsw_ef_search ;
59
+
60
+ if (pairingheap_is_empty (so -> discarded ))
61
+ return NIL ;
62
+
63
+ /* Get next batch of candidates */
64
+ for (int i = 0 ; i < batch_size ; i ++ )
65
+ {
66
+ HnswSearchCandidate * sc ;
67
+
68
+ if (pairingheap_is_empty (so -> discarded ))
69
+ break ;
70
+
71
+ sc = HnswGetSearchCandidate (w_node , pairingheap_remove_first (so -> discarded ));
72
+
73
+ ep = lappend (ep , sc );
74
+ }
75
+
76
+ return HnswSearchLayer (base , & so -> q , ep , batch_size , 0 , index , & so -> support , so -> m , false, NULL , & so -> v , & so -> discarded , false, & so -> tuples );
43
77
}
44
78
45
79
/*
@@ -83,6 +117,8 @@ hnswbeginscan(Relation index, int nkeys, int norderbys)
83
117
so = (HnswScanOpaque ) palloc (sizeof (HnswScanOpaqueData ));
84
118
so -> typeInfo = HnswGetTypeInfo (index );
85
119
so -> first = true;
120
+ so -> v .tids = NULL ;
121
+ so -> discarded = NULL ;
86
122
so -> tmpCtx = AllocSetContextCreate (CurrentMemoryContext ,
87
123
"Hnsw scan temporary context" ,
88
124
ALLOCSET_DEFAULT_SIZES );
@@ -103,7 +139,15 @@ hnswrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int no
103
139
{
104
140
HnswScanOpaque so = (HnswScanOpaque ) scan -> opaque ;
105
141
142
+ if (so -> v .tids != NULL )
143
+ tidhash_reset (so -> v .tids );
144
+
145
+ if (so -> discarded != NULL )
146
+ pairingheap_reset (so -> discarded );
147
+
106
148
so -> first = true;
149
+ so -> tuples = 0 ;
150
+ so -> previousDistance = - INFINITY ;
107
151
MemoryContextReset (so -> tmpCtx );
108
152
109
153
if (keys && scan -> numberOfKeys > 0 )
@@ -165,22 +209,100 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
165
209
#endif
166
210
}
167
211
168
- while ( list_length ( so -> w ) > 0 )
212
+ for (;; )
169
213
{
170
214
char * base = NULL ;
171
- HnswSearchCandidate * sc = llast ( so -> w ) ;
172
- HnswElement element = HnswPtrAccess ( base , sc -> element ) ;
215
+ HnswSearchCandidate * sc ;
216
+ HnswElement element ;
173
217
ItemPointer heaptid ;
174
218
219
+ if (list_length (so -> w ) == 0 )
220
+ {
221
+ if (hnsw_iterative_search == HNSW_ITERATIVE_SEARCH_OFF )
222
+ break ;
223
+
224
+ /* Empty index */
225
+ if (so -> discarded == NULL )
226
+ break ;
227
+
228
+ /* Reached max number of additional tuples */
229
+ if (hnsw_iterative_search_max_tuples != -1 && so -> tuples >= hnsw_iterative_search_max_tuples )
230
+ {
231
+ if (pairingheap_is_empty (so -> discarded ))
232
+ break ;
233
+
234
+ /* Return remaining tuples */
235
+ so -> w = lappend (so -> w , HnswGetSearchCandidate (w_node , pairingheap_remove_first (so -> discarded )));
236
+ }
237
+ /* Prevent scans from consuming too much memory */
238
+ else if (MemoryContextMemAllocated (so -> tmpCtx , false) > (Size ) work_mem * 1024L )
239
+ {
240
+ if (pairingheap_is_empty (so -> discarded ))
241
+ {
242
+ ereport (DEBUG1 ,
243
+ (errmsg ("hnsw index scan exceeded work_mem after " INT64_FORMAT " tuples" , so -> tuples ),
244
+ errhint ("Increase work_mem to scan more tuples." )));
245
+
246
+ break ;
247
+ }
248
+
249
+ /* Return remaining tuples */
250
+ so -> w = lappend (so -> w , HnswGetSearchCandidate (w_node , pairingheap_remove_first (so -> discarded )));
251
+ }
252
+ else
253
+ {
254
+ /*
255
+ * Locking ensures when neighbors are read, the elements they
256
+ * reference will not be deleted (and replaced) during the
257
+ * iteration.
258
+ *
259
+ * Elements loaded into memory on previous iterations may have
260
+ * been deleted (and replaced), so when reading neighbors, the
261
+ * element version must be checked.
262
+ */
263
+ LockPage (scan -> indexRelation , HNSW_SCAN_LOCK , ShareLock );
264
+
265
+ so -> w = ResumeScanItems (scan );
266
+
267
+ UnlockPage (scan -> indexRelation , HNSW_SCAN_LOCK , ShareLock );
268
+
269
+ #if defined(HNSW_MEMORY )
270
+ elog (INFO , "memory: %zu KB" , MemoryContextMemAllocated (so -> tmpCtx , false) / 1024 );
271
+ #endif
272
+ }
273
+
274
+ if (list_length (so -> w ) == 0 )
275
+ break ;
276
+ }
277
+
278
+ sc = llast (so -> w );
279
+ element = HnswPtrAccess (base , sc -> element );
280
+
175
281
/* Move to next element if no valid heap TIDs */
176
282
if (element -> heaptidsLength == 0 )
177
283
{
178
284
so -> w = list_delete_last (so -> w );
285
+
286
+ /* Mark memory as free for next iteration */
287
+ if (hnsw_iterative_search != HNSW_ITERATIVE_SEARCH_OFF )
288
+ {
289
+ pfree (element );
290
+ pfree (sc );
291
+ }
292
+
179
293
continue ;
180
294
}
181
295
182
296
heaptid = & element -> heaptids [-- element -> heaptidsLength ];
183
297
298
+ if (hnsw_iterative_search == HNSW_ITERATIVE_SEARCH_STRICT )
299
+ {
300
+ if (sc -> distance < so -> previousDistance )
301
+ continue ;
302
+
303
+ so -> previousDistance = sc -> distance ;
304
+ }
305
+
184
306
MemoryContextSwitchTo (oldCtx );
185
307
186
308
scan -> xs_heaptid = * heaptid ;
0 commit comments