Repository navigation
Conversation
…op turn out() and the float32-tail reply wrote the reply with p.write(i, b[i]) in a Java loop. Both now fill a direct ByteBuffer view of the malloc'd block (Unsafe.copyMemory); the bytes written are the same. New test: replies of one, several and many batches, multi-byte text, megabyte rows and vectors round-trip exactly. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The previous commit widened off + 4 * sink.n to long, which UnmanagedMemory.malloc does not accept (found by the first image build). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…hort, empty and missing vectors Passes against the current library (vectors of later batches travel as number text); it must keep passing when arcadedb_n_next carries them in a float32 tail. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e float32 tail arcadedb_n_exec flag 4 already sends float[] values of 8 or more elements in a binary tail, but only for the first reply (256 rows). Every later batch (arcadedb_n_next, 2048 rows) sent them as number text, which Python parses with json.loads. New entry point arcadedb_n_next_bin encodes a batch with the same sink; the tail code of replyOf is now one helper (withFloatTail) used by both. lib.py uses it when the library has it (ARCADEDB_NATIVE_NEXT_BIN=0 and ARCADEDB_NATIVE_BIN_REPLY=0 turn it off); arcadedb_n_next is unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…t_col), one crossing into a reused buffer The read-side counterpart of the columnar insert. A batch of rows is answered as one frame: a fixed header, a small JSON header with the column names and kinds, then one buffer per column in the layout of arcadedb_n_insert_colp (int64, float64, bool bytes, Arrow string offsets plus blob, float32 vectors, validity and presence bitmaps). Python rebuilds the rows from the buffers without parsing text (_native/colres.py). The reply is written into a per-thread buffer that Python owns and reuses, so a reply that fits needs no malloc, no arcadedb_free crossing and no strlen scan. A batch of fewer than ARCADEDB_NATIVE_COLRES_MIN rows (16) answers as the JSON text of arcadedb_n_next_bin, and so does every batch the columns cannot carry (nested map, list or record, a column that mixes kinds, an unpaired surrogate, more than 256 columns, a mostly empty table). ARCADEDB_NATIVE_COLRES=0 forces JSON in the same image. Tests first: bit-exact rows in every mode on mixed types. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…m 65 record rows on) The meta columns of a record result (@Rid, @type) are string columns without bitmaps, but only the rid column was marked so; the type column still wrote a validity bitmap of (rows+7)/8 bytes from its 8-byte array, which fails from 65 rows on (the 256-row first batch of SELECT FROM D in the transport probe: IndexOutOfBoundsException Range [0, 0 + 32) out of bounds for length 8). The 20-row cases of the first tests and of the JVM gate hid it. Tests first: record rows in batches of 256 and 2048 (SELECT FROM T over 5,000 rows) and the probe's wide table (3,000 rows, record rows and a projection) in every mode; both failed with that error on the image built before the fix. The JVM gate now has 700 rows per table and batches of 256 and 2048. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Oct 25, 2026
Author
|
Closing here: the bindings moved to ArcadeData/arcadedb-python. The branch stays for the queued A/B native-colres-ab-laptop-20261025; if it passes, the change is ported there. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DRAFT. Prototype of backlog row N-R2: query results as columns, the read-side counterpart of the columnar insert (#514). Untimed work only: the design note, the tests, a JVM-side gate and one image build with an untimed before/after probe (queued, see Pending). A timed ab.py A/B is queued before any merge; do not merge before it.
STACKED ON #521 (which is stacked on #520): the reply code in
ArcadeNative(replyOf,withFloatTail,out) is what those two PRs change, and the JSON fallback of this change is #521'sarcadedb_n_next_binreply, so this branch starts fromnative-next-float-tail. The first four commits are #520's and #521's; review the last one. When #520 and #521 merge this PR needs only a rebase.Why (found by the study in
.notes/bench/repros/native-result-transport-20261025, local notes repo): a warm query is not the engine alone. Point lookups: engine 27-41%, JSON encode 11-20%, Python JSON decode 18-24%, other (ctypes call, thearcadedb_freecrossing,string_at, dict building) 27-30%. Wide SELECTs: Python decode about 50% (json.loadswith the object_hook), encode 8-36%. The byte-loop copy is 0-2% (#520 is a cleanup). The target is the cost of building the Python objects from columns, the "columnar floor" of that study.Change:
arcadedb_n_exec_colandarcadedb_n_next_col. A batch is answered as one frame: a 32-byte header, a small JSON header (column names, kinds, offsets), one buffer per column in the layout ofarcadedb_n_insert_colp(int64, float64, bool bytes, Arrow string offsets plus UTF-8 blob, float32 vectors, validity and presence bitmaps; dates as days or microseconds; record ids as text), the record meta as three more columns.ColResult.java; the frame is documented at its top and in the design notenative-columnar-results-20261025/README.md.{"a": None}and{}stay different.BigDecimal/BigInteger/Set/other arrays, a column that mixes kinds (int and double), a year outside 1-9999, an unpaired surrogate, more than 256 columns, a table less than a quarter full, or fewer thanARCADEDB_NATIVE_COLRES_MINrows (default 16: a point lookup gains nothing from a frame). The JSON batch is Native: later result batches carry vectors in the float32 tail (arcadedb_n_next_bin) (draft, stacked on #520) #521's reply, vectors in the float32 tail.malloc, noarcadedb_freecrossing and nostrlen. A reply that does not fit, and every error, comes in a malloc'd block that Python copies out and frees (the returned pointer says which)._native/colres.py(the decoder:memoryview.castplustolist(), numpy only for big vector matrices),lib.exec_col/lib.next_col,NativeDatabase._execandNativeResultSet._filluse them for queries when the library has them.ARCADEDB_NATIVE_COLRES=0forces the JSON text of Native: later result batches carry vectors in the float32 tail (arcadedb_n_next_bin) (draft, stacked on #520) #521 in the same image;ARCADEDB_NATIVE_COLRES_MIN=<n>sets the smallest columnar batch. Result objects,to_list,to_columns,to_dataframeand the cursor are unchanged. Old entry points are unchanged, so an older Python on this image keeps working.Local checks, done:
bindings/python/tests/test_native_columnar_results.py, 8 tests): mixed types with nulls, unicode and control characters, ints at 2^31/2^53/2^62 and the int64 edges, doubles with -0.0/inf/nan/1e-300/MAX/MIN, float32 vectors of 0/3/8/32 elements and missing, record ids, DATE and DATETIME, record rows with meta, missing and null properties, results the columns cannot carry, point lookups, empty results, 5,000 rows in several batches, a cursor read row by row; each query runs in JSON mode, in columns for every batch and with the default, and the three must agree bit for bit (types and key order included) and match the inserted literals. Against the unmodified Native: later result batches carry vectors in the float32 tail (arcadedb_n_next_bin) (draft, stacked on #520) #521 image: 6 pass, the 2 asserts that the columnar entry points were used fail (red), as intended.jvm_gate.shin the notes folder): the encoder on a JVM against the engine jars, 18 queries incl. dense and sparse records, projections with nulls, nested lists, mixed int/double, batches of 5-7 rows; the Python decoder must reproduce rows, meta anddrainedof the oldNativeCodec.appendBatchtext of the same batch: 0 mismatches; every fallback is one of the listed reasons.native-spikeon the final tree: passes. black and isort via pre-commit: pass.Found by the first probe run and fixed here (commit b5012e2): the record meta columns (
@rid,@type) wrote a validity bitmap out of an 8-byte array, so a record-row batch of more than 64 rows failed (IndexOutOfBoundsException: Range [0, 0 + 32) out of bounds for length 8). The 20-row cases and the projection tests had hidden it. Tests first:test_record_rows_of_several_batchesandtest_the_probes_wide_table_in_every_modefailed with that error on the image built before the fix; the JVM gate (700 rows per table, batches of 256 and 2048) reproduced it. Then one rebuild (2m33s,XMX=8g, cores 8-15) of this tree.Local checks on the fixed image: the new test file and the #520/#521 transport tests, 16 passed with
ARCADEDB_NATIVE_COLRES=1and 16 passed with=0; JVM gate 0 mismatches; Maven compile gate; black and isort. Digests of decoded rows (5,000 mixed rows incl. nulls, unicode, ints at 2^31/2^53, -0.0/inf/nan, vectors of every length, dates, sparse records, nested values, point lookup, aggregation): JVM wheel = native JSON = native columns = native default on all six queries (record ids compared by shape across wheels; theRIDvsDatabaseRIDtype difference is backlog P-02 and untouched, exact text equal across the three native modes).Measured (2026-10-25, untimed, laptop, 2 runs per arm; stage clocks, not an ab.py A/B; the timed A/B is queued before any merge)
Microseconds per warm query (mean of runs). wall = what a user pays; engine = engine-only pass (varies +-10% between arms); fetch = foreign calls plus copy and free; py decode = text or column decode; other = the rest.
Reading: wide SELECTs take 63% (
wide10k_proj), 72% (wide10k_star) and 75% (wide100k_proj) of the JSON arm's wall (Python decode 36.1 -> 18.5 ms and 443 -> 266 ms for the projections). Point lookups with the default min of 16 (JSON text in the reused buffer) are 95-103% of JSON: no visible change untimed. One-row frames (min 1) are 16-22% slower, so the default minimum stays above 1. 2-hop Cypher (100 rows) 83%, aggregation 93% (engine-dominated, inside the pass-to-pass noise). Peak RSS 1.05-1.15 GB in every arm. Untimed and indicative: the timed ab.py A/B decides the defaults (rule in the design notenative-columnar-results-20261025/README.md, written before any measurement).🤖 Generated with Claude Code