11#include "postgres.h"
22
3+ #include <math.h>
4+
35#include "access/relscan.h"
46#include "hnsw.h"
57#include "pgstat.h"
@@ -21,25 +23,57 @@ GetScanItems(IndexScanDesc scan, Datum value)
2123int m ;
2224HnswElement entryPoint ;
2325char * base = NULL ;
24- HnswQuery q ;
25-
26- q .value = value ;
26+ HnswQuery * q = & so -> q ;
2727
2828/* Get m and entry point */
2929HnswGetMetaPageInfo (index ,& m ,& entryPoint );
3030
31+ q -> value = value ;
32+ so -> m = m ;
33+
3134if (entryPoint == NULL )
3235return NIL ;
3336
34- ep = list_make1 (HnswEntryCandidate (base ,entryPoint ,& q ,index ,support , false));
37+ ep = list_make1 (HnswEntryCandidate (base ,entryPoint ,q ,index ,support , false));
3538
3639for (int lc = entryPoint -> level ;lc >=1 ;lc -- )
3740{
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 );
3942ep = w ;
4043}
4144
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 );
4377}
4478
4579/*
@@ -83,6 +117,8 @@ hnswbeginscan(Relation index, int nkeys, int norderbys)
83117so = (HnswScanOpaque )palloc (sizeof (HnswScanOpaqueData ));
84118so -> typeInfo = HnswGetTypeInfo (index );
85119so -> first = true;
120+ so -> v .tids = NULL ;
121+ so -> discarded = NULL ;
86122so -> tmpCtx = AllocSetContextCreate (CurrentMemoryContext ,
87123"Hnsw scan temporary context" ,
88124ALLOCSET_DEFAULT_SIZES );
@@ -103,7 +139,15 @@ hnswrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int no
103139{
104140HnswScanOpaque so = (HnswScanOpaque )scan -> opaque ;
105141
142+ if (so -> v .tids != NULL )
143+ tidhash_reset (so -> v .tids );
144+
145+ if (so -> discarded != NULL )
146+ pairingheap_reset (so -> discarded );
147+
106148so -> first = true;
149+ so -> tuples = 0 ;
150+ so -> previousDistance = - INFINITY ;
107151MemoryContextReset (so -> tmpCtx );
108152
109153if (keys && scan -> numberOfKeys > 0 )
@@ -165,22 +209,100 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
165209#endif
166210}
167211
168- while ( list_length ( so -> w ) > 0 )
212+ for (;; )
169213{
170214char * base = NULL ;
171- HnswSearchCandidate * sc = llast ( so -> w ) ;
172- HnswElement element = HnswPtrAccess ( base , sc -> element ) ;
215+ HnswSearchCandidate * sc ;
216+ HnswElement element ;
173217ItemPointer heaptid ;
174218
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+
175281/* Move to next element if no valid heap TIDs */
176282if (element -> heaptidsLength == 0 )
177283{
178284so -> 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+
179293continue ;
180294}
181295
182296heaptid = & element -> heaptids [-- element -> heaptidsLength ];
183297
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+
184306MemoryContextSwitchTo (oldCtx );
185307
186308scan -> xs_heaptid = * heaptid ;