33#include " vec_sim_index.h"
44#include " algorithms/brute_force/brute_force.h"
55#include " VecSim/batch_iterator.h"
6- #include " VecSim/utils/merge_results .h"
6+ #include " VecSim/utils/query_result_utils .h"
77
88#include < shared_mutex>
99
@@ -20,6 +20,9 @@ struct AsyncJob : public VecsimBaseObject {
2020 : VecsimBaseObject(allocator), jobType(type), Execute(callback), index(index_ref) {}
2121};
2222
23+ // All read operations (including KNN, range, batch iterators and get-distance-from) are guaranteed
24+ // to consider all vectors that were added to the index before the query was submitted. The results
25+ // may include vectors that were added after the query was submitted, with no guarantees.
2326template <typename DataType, typename DistType>
2427class VecSimTieredIndex : public VecSimIndexInterface {
2528protected:
@@ -62,6 +65,9 @@ class VecSimTieredIndex : public VecSimIndexInterface {
6265
6366 VecSimQueryResult_List topKQuery (const void *queryBlob, size_t k,
6467 VecSimQueryParams *queryParams) override ;
68+ VecSimQueryResult_List rangeQuery (const void *queryBlob, double radius,
69+ VecSimQueryParams *queryParams,
70+ VecSimQueryResult_Order order) override ;
6571
6672 // Return the current state of the global write mode (async/in-place).
6773 static VecSimWriteMode getWriteMode () { return VecSimIndexInterface::asyncWriteMode; }
@@ -83,12 +89,13 @@ class VecSimTieredIndex : public VecSimIndexInterface {
8389 }
8490
8591 virtual VecSimQueryResult_List rangeQueryWrapper (const void *queryBlob, double radius,
86- VecSimQueryParams *queryParams) override {
92+ VecSimQueryParams *queryParams,
93+ VecSimQueryResult_Order order) override {
8794 // Will be used only if a processing stage is needed
8895 char processed_blob[this ->backendIndex ->getDataSize ()];
8996 const void *query_to_send = this ->backendIndex ->processBlob (queryBlob, processed_blob);
9097
91- return this ->rangeQuery (query_to_send, radius, queryParams);
98+ return this ->rangeQuery (query_to_send, radius, queryParams, order );
9299 }
93100
94101 virtual VecSimBatchIterator *
@@ -151,3 +158,74 @@ VecSimTieredIndex<DataType, DistType>::topKQuery(const void *queryBlob, size_t k
151158 }
152159 }
153160}
161+
162+ template <typename DataType, typename DistType>
163+ VecSimQueryResult_List
164+ VecSimTieredIndex<DataType, DistType>::rangeQuery(const void *queryBlob, double radius,
165+ VecSimQueryParams *queryParams,
166+ VecSimQueryResult_Order order) {
167+ this ->flatIndexGuard .lock_shared ();
168+
169+ // If the flat buffer is empty, we can simply query the main index.
170+ if (this ->frontendIndex ->indexSize () == 0 ) {
171+ // Release the flat lock and acquire the main lock.
172+ this ->flatIndexGuard .unlock_shared ();
173+
174+ // Simply query the main index and return the results while holding the lock.
175+ this ->mainIndexGuard .lock_shared ();
176+ auto res = this ->backendIndex ->rangeQuery (queryBlob, radius, queryParams);
177+ this ->mainIndexGuard .unlock_shared ();
178+
179+ // We could have passed the order to the main index, but we can sort them here after
180+ // unlocking it instead.
181+ sort_results (res, order);
182+ return res;
183+ } else {
184+ // No luck... first query the flat buffer and release the lock.
185+ auto flat_results = this ->frontendIndex ->rangeQuery (queryBlob, radius, queryParams);
186+ this ->flatIndexGuard .unlock_shared ();
187+
188+ // If the query failed (currently only on timeout), return the error code and the partial
189+ // results.
190+ if (flat_results.code != VecSim_QueryResult_OK) {
191+ return flat_results;
192+ }
193+
194+ // Lock the main index and query it.
195+ this ->mainIndexGuard .lock_shared ();
196+ auto main_results = this ->backendIndex ->rangeQuery (queryBlob, radius, queryParams);
197+ this ->mainIndexGuard .unlock_shared ();
198+
199+ // Merge the results and return, avoiding duplicates.
200+ // At this point, the return code of the FLAT index is OK, and the return code of the MAIN
201+ // index is either OK or TIMEOUT. Make sure to return the return code of the MAIN index.
202+ if (BY_SCORE == order) {
203+ sort_results_by_score_then_id (main_results);
204+ sort_results_by_score_then_id (flat_results);
205+
206+ // Keep the return code of the main index.
207+ auto code = main_results.code ;
208+
209+ // Merge the sorted results with no limit (all the results are valid).
210+ VecSimQueryResult_List ret;
211+ if (this ->backendIndex ->isMultiValue ()) {
212+ ret = merge_result_lists<true >(main_results, flat_results, -1 );
213+ } else {
214+ ret = merge_result_lists<false >(main_results, flat_results, -1 );
215+ }
216+ // Restore the return code and return.
217+ ret.code = code;
218+ return ret;
219+
220+ } else { // BY_ID
221+ // Notice that we don't modify the return code of the main index in any step.
222+ concat_results (main_results, flat_results);
223+ if (this ->backendIndex ->isMultiValue ()) {
224+ filter_results_by_id<true >(main_results);
225+ } else {
226+ filter_results_by_id<false >(main_results);
227+ }
228+ return main_results;
229+ }
230+ }
231+ }
0 commit comments