Skip to content

Native: columnar bulk insert by address for strings, nulls and vectors (no JSON text) - #514

Closed
tae898 wants to merge 3 commits into
native-previewfrom
native-columnar-strings
Closed

tae898 wants to merge 3 commits into
native-previewfrom
native-columnar-strings

Conversation

@tae898

@tae898 tae898 commented Oct 25, 2026 •

Copy link
Copy Markdown

What

The native wheel's bulk insert no longer has to go through JSON text when every column maps to a supported kind. arcadedb_n_insert_colp is a new entry point that reads the caller's buffers in place (an address table of six longs per column: data, aux, null bitmap) and builds the documents inside the image without a parser, a HashMap per row or a boxed number. arcadedb_n_insert_cols keeps its single-buffer form (old callers and old libraries keep working) and shares the same loop.

The design and the build decision are in the untimed study .notes/bench/repros/native-json-transport-20261025 (JSON text was 43-76% of the native bulk-insert wall on the mixed 1M-document load, rule verdict BUILD). The public API is unchanged and the JVM wheel is untouched.

Column kinds

kind layout used for
q, d, b int64, float64, bool byte ints, floats, bools (as before)
i, e int32, float32 only when the property is declared INTEGER / FLOAT; otherwise an int32 or float32 column widens to q / d, as the JSON path stores it
f float32 matrix, dim per row numpy 2-D arrays and lists of float arrays (as before), and lists of float lists or Arrow list columns when the property is declared ARRAY_OF_FLOATS
s int32 offsets[n+1] plus UTF-8 bytes (Arrow string layout; offsets[0] need not be 0, so a sliced Arrow array crosses without a copy) str columns, Arrow string / large_string
nulls Arrow validity bitmap, bit per row None in a list column, Arrow nulls, pandas <NA>

Every length, offset and bitmap size is checked in the image before the first document is built: a wrong table is an exception with a message, never a fault, and nothing is written. Same transaction contract as before (a transaction opened by the call is committed every commit_every rows and rolled back on failure; a caller's transaction is left alone).

Where it is used

  • insert_many: a chunk (10,000 rows) of dicts with the same keys whose columns each hold one value type is transposed to columns and sent by address.
  • insert_columns, insert_dataframe, insert_arrow: string and nullable Arrow columns stay Arrow arrays and cross in place; the declared types of the type are read once per call (the DataFrame path already has them).
  • Fallback to the existing JSON path, per chunk or per call, with identical stored records: nested maps and lists, mixed-type columns (including int with float), rows with different keys, date-times, decimals, bytes, integers past 64 bits, lone surrogates, undeclared list-of-float columns (the JSON path keeps their doubles), parallel=True, vertex types through GraphBatch.
  • ARCADEDB_NATIVE_BULK=0 turns the columnar entry off (JSON path), for A/B runs in one image.

Tests

  • tests/test_native_columnar_pack.py (all backends, no native library): the layouts read back from the addresses, null bitmaps, which columns are refused, the address table and header, Arrow offsets of sliced arrays.
  • tests/test_native_columnar_insert.py (collected on native only, per conftest.py): the same data through the JSON path and through the columnar entry on the probe workload's column types (INTEGER, DOUBLE, 20-value category, variable-length text, float32 vectors) via insert_many, insert_columns, insert_dataframe, insert_arrow, with the stored records compared value by value and type by type; unicode, empty, NUL-containing and 70 KB strings; nulls (list columns and pandas nullable dtypes); int64 edges, NaN and infinities; int32 and float32 columns; vertex types; every fallback case, with a spy on the crossing so a silent fall back cannot pass; duplicate-key failure and rollback on both paths; chunking and commit interval; and calling the entry with a wrong address table, which must give an error and write nothing.
  • Local native run against the library built from this branch: 1019 passed, 185 skipped (the existing NOTYET / NA-JVM / no-optional-dependency skips), 9 failed, all in test_docs_examples.py and test_wheel_packages.py, whose child processes cannot import the package or setuptools outside an installed wheel (the parity run installs the wheel, see native-wheel/parity).

Status

Ready for review; the timed check is still to come. Done: the image builds (native-spike at 8a7d4b9, -march=compatibility, about 2 minutes on the laptop), the native tests above pass against it, a wheel 26.10.2.dev1+colstr is built from it, and the study's native arms are re-run on that wheel (.notes/bench/repros/native-json-transport-20261025 README, Step 3). Untimed and indicative, 1M mixed documents, two runs per arm, one process each, GC logs on: with the UNIQUE index on id, insert_many of the same dict rows 43.5 s -> 19.0 s and one insert_columns call 15.1 s (the floor is the engine's per-document work plus the commit with the index check, 13.6 s for the numeric and vector columns that already had a binary path); without the index 34.6 s -> 10.0 s and 6.1 s. In-image allocation with the index 15.2 GB -> 9.4 GB. The JSON path inside the new image is as fast as in dev1, so the image change itself costs nothing. These are not speed claims. The timed ab.py study (dev1 wheel against this wheel, rows and frame loads, rule written first) is prepared in .notes/bench/repros/native-columnar-ab-laptop-20261025 and not started; the prototype goes into a preview only if its result says ok and both ratios have the upper end of the 95% CI below 0.80. CI: the native parity run on this branch is the check that matters.

🤖 Generated with Claude Code

tae898 and others added 2 commits October 25, 2026 11:53
…int32, float32 columns and null bitmaps, no JSON

The columnar entry now reads the caller's buffers in place (an address table of six longs per column) and builds the documents without a text parser. New kinds: s (UTF-8 string as Arrow offsets plus bytes), i (int32), e (float32); Arrow validity bitmaps for nulls. arcadedb_n_insert_cols keeps its single-buffer form and shares the loop. Every length and offset is checked before the first document is built.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ntry when every column maps to a supported kind

_native/columnar.py lays out whole columns (int64, float64, bool, int32/float32 for INTEGER/FLOAT properties, UTF-8 strings as Arrow offsets plus bytes, float32 vectors for ARRAY_OF_FLOATS properties, null bitmaps) and hands their addresses to arcadedb_n_insert_colp. insert_many transposes a chunk of same-keyed dict rows into columns; insert_dataframe/insert_arrow keep string and nullable Arrow columns as Arrow arrays. Nested values, mixed-type columns, date-times, decimals, integers past 64 bits and lone surrogates keep the JSON path. Public API unchanged; ARCADEDB_NATIVE_BULK=0 turns the columnar path off.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@tae898
tae898 marked this pull request as ready for review October 25, 2026 03:09
@tae898
tae898 marked this pull request as draft October 25, 2026 03:11
@tae898

tae898 commented Oct 25, 2026

Copy link
Copy Markdown
Author

Back to draft on purpose: untimed it is 2.3x (indexed) to 3.5x (no index) faster on 1M mixed documents, but the prototype merges only after the timed laptop A/B (dev1 wheel vs this wheel, ab.py --noise, rule written first: both gating figures need a 95% CI upper end below 0.80). That study is queued right after the running heap study.

…it B608, test-owned type names)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tae898

tae898 commented Oct 26, 2026

Copy link
Copy Markdown
Author

Closing here: the bindings moved to ArcadeData/arcadedb-python, and this change is merged there as ArcadeData/arcadedb-python#72 (columnar bulk insert by address). The branch stays.

@tae898 tae898 closed this Oct 26, 2026
@tae898
tae898 deleted the native-columnar-strings branch November 8, 2026 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant