You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 13e5c26
Browse filesBrowse the repository at this point in the historyBrowse files
Native find_nearest: include_vector=False returns hits without the indexed vector (explicit opt-in) (#204)
* Native find_nearest: include_vector=False returns hits without the indexed vector, loaded by RID on first read
find_nearest keeps its default: every hit carries its vector, and the default search is the code of the query-as-float32 change (#203), untouched. A new keyword-only include_vector=False asks for hits built without the vector, for callers that need only ids or distances. On the native wheel the search then runs SELECT *, !<vector property>, !record FROM (SELECT expand(vectorNeighbors(...))) and each hit loads its record once by RID the first time the vector or the whole record is needed. The JVM build accepts the keyword and ignores it (it loads whole records either way).
Reworks the earlier lazy-by-default version after its timed A/B: hits not read 0.66 and 0.805 (1536 and 256 dims), but vector read 1.011 and 1.336, so lazy-by-default is a cost for callers that read the vector. Tests run on both backends (values equal lookup_by_rid, keyword accepted) and on the native wheel (default ships the vector with no lookup and no dropped column, opt-in ships none and looks up once per record).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* find_nearest include_vector: document the measured opt-in figures; compare hit distances like the query-form tests
The "Hits without the vector" section of vectors.adoc now carries the timed A/B of the opt-in (native wheel 26.11.1.dev0, engine 26.10.2, k = 10, 12 clean pairs per figure): p50 B/A with 95% intervals for include_vector=False (hits not read, ids read, vector of every hit read) and for the unchanged default call, at 1536 and 256 dimensions. The figures are generated from the study's score file by a script, not typed. The text says the keyword is an opt-in for callers that do not read the vector (256 dimensions: reading every vector afterwards costs more than the default).
test_the_keyword_changes_no_answer compared distances with ==, which the JVM build does not guarantee between two calls (main's _assert_same rule): ids compare exactly, distances exactly on the native build and to a few float32 ulps on the JVM build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/modules/ROOT/pages/how-to/vectors.adoc
+50-1Lines changed: 50 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -162,7 +162,7 @@ print([row["name"] for row in rows])
162
162
[[find-nearest]]
163
163
=== find_nearest
164
164
165
-
`VectorIndex.find_nearest(query_vector, k=10, ef_search=None, allowed_rids=None)` searches without a statement. It takes a list or a NumPy array and returns a list of `(record, distance)` tuples, nearest first. Use it when you already hold the index object, or when you have a list of record ids to search within.
165
+
`VectorIndex.find_nearest(query_vector, k=10, ef_search=None, allowed_rids=None, *, include_vector=True)` searches without a statement. It takes a list or a NumPy array and returns a list of `(record, distance)` tuples, nearest first. Use it when you already hold the index object, or when you have a list of record ids to search within.
166
166
167
167
On the native package the query vector crosses as one `float32` buffer whatever its form (a list, a `float64` array, or a `float32` array), so you do not need to convert it first. A vector index stores `float32`, so the numbers are rounded the same way as before. The table gives the median `find_nearest` latency with this change over the median without it (95% interval, below 1 is faster), measured on the native wheel `26.11.1.dev0` on one Linux x86_64 laptop with 11 to 12 clean pairs per figure. The wheel built before this change was the baseline.
168
168
@@ -199,6 +199,55 @@ python-ish
199
199
python
200
200
----
201
201
202
+
=== Hits without the vector
203
+
204
+
Every hit of `find_nearest` carries its indexed vector, so a search at 1536 dimensions ships and decodes 1536 numbers per hit whether you read them or not. If you need only the ids, the distances or other properties of the hits, pass `include_vector=False`. On the native build each hit is then built without the vector. It loads the vector, with the rest of the record, in one lookup by record id the first time you read it (`get` of the vector, `has_property`, `to_dict`, `get_property_names`, `modify` or `save`). The values are the ones `db.lookup_by_rid` returns, and a hit deleted since the search raises `ArcadeDBError` on that first read. The arcadedb-jvm package accepts the argument and returns whole records either way, so the call costs the same there.
205
+
206
+
This is an opt-in for callers that do not read the vector. The default stays `include_vector=True`: every hit carries its vector, and the default call is the one described above, unchanged. A loop that reads the vector of every hit after passing `include_vector=False` pays one lookup per hit and does more work than the default call, so do not pass it for that.
207
+
208
+
The table gives the median `find_nearest` latency over the median of the default call before this change (95% interval, below 1 is faster), with k = 10 on 100,000 vectors per dimension count. It was measured on the native wheel `26.11.1.dev0` (the engine inside it is 26.10.2) on one Linux x86_64 laptop with 12 clean pairs per figure. The default call has not changed: its intervals lie between 0.993 and 1.011. With the keyword set, a search whose hits are not read, or whose ids are read, is faster at both sizes. If every vector is read afterwards, the call takes 1.0 [0.999, 1.004] times as long as the default call at 1536 dimensions and 1.348 [1.34, 1.352] times as long at 256 dimensions.
209
+
210
+
[cols="3,2,2",options="header"]
211
+
|===
212
+
| Call | 1536 dimensions | 256 dimensions
213
+
214
+
| `include_vector=False`, hits not read
215
+
| 0.637 [0.633, 0.641]
216
+
| 0.8 [0.793, 0.818]
217
+
218
+
| `include_vector=False`, hit ids read
219
+
| 0.637 [0.633, 0.638]
220
+
| 0.797 [0.793, 0.803]
221
+
222
+
| `include_vector=False`, then the vector of every hit read
223
+
| 1.0 [0.999, 1.004]
224
+
| 1.348 [1.34, 1.352]
225
+
226
+
| the default call, hits not read (not changed)
227
+
| 1.0 [0.996, 1.002]
228
+
| 1.005 [0.996, 1.011]
229
+
230
+
| the default call, hit ids read (not changed)
231
+
| 1.001 [0.997, 1.005]
232
+
| 1.002 [0.994, 1.005]
233
+
234
+
| the default call, the vector of every hit read (not changed)
235
+
| 0.997 [0.995, 1.005]
236
+
| 0.997 [0.993, 1.007]
237
+
|===
238
+
239
+
[source,python]
240
+
----
241
+
for record, distance in index.find_nearest(query, k=2, include_vector=False):
242
+
print(record.get("name"))
243
+
----
244
+
245
+
[source,text]
246
+
----
247
+
python-ish
248
+
python
249
+
----
250
+
202
251
=== Filtered search
203
252
204
253
`allowed_rids` restricts `find_nearest` to the records whose ids you list, as strings. The search considers only those records, so it still returns `k` of them.
0 commit comments