GG-49932 Decode vector query responses in one pass (2/2) - #86
GG-49932 Decode vector query responses in one pass (2/2)#86PakhomovAlexander wants to merge 5 commits into
Conversation
|
e2e against a real 2-node GridGain cluster: both nodes started from the enterprise release assembled from CE/EE master + gg-50942 + gg-50943 ( |
|
Added differential tests ( |
…nt on the async path
|
Bug found by the rich-value e2e, fixed in |
|
Rich-value e2e on the real 2-node cluster (release assembled from CE/EE master + gg-50942 + gg-50943): a value type with ten fields — string, float vector, int, double, long, bool, string array, timestamp, a |
Ticket: GG-49932 · Part 2 of 2, stacked on
gg-49932(part 1: the ctypes-class cache and the paging default)What this changes
Vector query responses are decoded in one pass, straight off the response buffer, by a new
VectorResponse(the same specialisation pattern as the existingSQLResponse). Today a vectorrow costs three walks: the page parse builds a ctypes class tree per element and copies every
wrapped binary value out as an opaque blob (
WrappedDataObject), and the cursor then re-parseseach blob (
unwrap_binary->BinaryObject.parse->to_python). This is the ticket's"
unwrap_binaryfast path": keys, values and scores leaveparse()as final Python values,already shaped the way the cursor yields them, and the vector cursors stop calling
unwrap_binaryentirely.Scope and safety rails:
OP_QUERY_VECTORand its cursor pages). Scan/SQL/get keep thegeneric path (which part 1 already made cheaper).
raw float scores, and binary objects made of those plus float arrays, resolved once per
(type_id, schema_id) per page. Anything else falls back to the generic machinery at the same
stream position, one element at a time - unknown type codes, raw-data or non-user-type
objects, unknown schemas, big-endian hosts. The realistic failure mode for an exotic payload is
the old speed, not wrong data.
key-value map - on the sync and asyncio clients.
Query.performrelies on is kept byte-exact: the row section isdescribed as one opaque byte-blob field, and the decoder tests assert the returned class spans
the response exactly.
Behaviour notes for review
Two deliberate edge differences, both invisible at the cursor level:
(key, value)rows were alist(anasyncio.gatherartifact); they arenow a
tuple, matching the sync cursor.pygridgain.api.sql.vector()result, the legacydatavalue changes from adict to a list of
(key, value)tuples (wire order kept).Measured (incremental over part 1, same bracketed invocation)
(key, value)k=10 · mean ms (QPS)(key, value)k=100(key, value)k=100 · client CPU msAfter this PR, ~38% of the remaining
(key, value)CPU is the float-list boxing(
memoryview.cast('f').tolist()) - that ceiling belongs to a possible future opt-in numpy return,not to this PR.
How it was verified
tests/test_vector_response_decode.py, server-free): frames assembled with theclient's own writers - every row shape, paging pages, the legacy map,
Nones, the genericfallback (a UUID element), empty results, asyncio parity, and the exact-span framing contract.
environment-only failures).
flake83.8.4: no new finding.legs (all shapes, paging, asyncio; scores bit-exact as float32); private EE suite 53/53.
result posted as a PR comment.
Review guide
Everything lives in
queries/response.py(VectorResponse+_NeedsAsync), with two thinintegration edits:
api/sql.pypoints the two vector ops at it, andcursors.pydrops theper-row unwrap/shaping. The decoder makes the same two assumptions the existing generic
BinaryObjectpath already makes - field order comes from the complex-types registry, and theschema footer is skipped via the header's length - so a future wire change touches the same
assumptions in two places instead of one; both are named in comments.