Skip to content

Native one-row writes: create and update cost more boundary crossings than the point reads of #229 #317

Open

Description

@tae898

This comes from a read-only audit of the tag 26.11.1 (commit 2f28a328be); everything below marked read was seen in the code with the file and line, nothing was run or timed, and no speed claim is made. Related closed work that this builds on and does not repeat: #229 (native point reads), #231 (native insert_many ragged lists), #235 (JVM insert_many cyclic GC), #243 (JVM GraphBatch property shapes), #174 (commit sizes).

Read: a created record (save(), new_edge) makes two crossings: the lazy arcadedb_n_cmd call and then arcadedb_n_next_bin to read the row it always reads (_native/records.py:341, :448-456). An update does is_transaction_active, a SELECT and an UPDATE, with the stale-record check done in Python (records.py:351-399). The JVM wheel pays two plus N crossings to create and one to update. The eager execution form that avoids the second call already exists (_native/database.py:632-673).

Proposal: switch these callers to the eager form and read the stale-record check from the update's own result, keeping every result and error message as it is, with tests that spy on the number of crossings (they fail on main and pass with the change), as #229 did. Bindings only, no native image change.

Measure first with the profile of #229 (per-op microseconds and crossings), then a pre-registered A/B before the change leaves draft. Note: #229 settled that lookup_by_rid keeps the record id in its text, so that is not part of this.

Activity

  1. added
    nativeThe arcadedb package (native library build)
    performanceSpeed or memory of the bindings, with a measurement
    pythonPull requests that update python code
    on Nov 9, 2026
  2. added this to the 26.12.1 milestone on Nov 9, 2026
  3. self-assigned this
    on Nov 9, 2026
  4. tae898 commented on Nov 12, 2026

    @tae898
    CollaboratorAuthor

    This is the pre-registered A/B for this issue, which I ran on 2026-11-10 on my laptop (Intel Core Ultra X9 388H, Linux 7.0.0, Python 3.12, two cores). A is the released arcadedb==26.11.1 wheel and B is the candidate wheel of the same engine (a CI build of the release/26.11.1 line before 26.11.1.post1, sha256 starting 144091c9), which has PR #329 in it. B is not the published 26.11.1.post2 wheel: PR #329 is on main and in the 26.11.1.post2 tag, and I did not re-run this on the published file.

    Each figure is 10 transactions of 1,000 operations (10,000 per figure), 14 clean pairs. The rule was written before the first run: A and B alternate in paired rounds, B/A is the median of the paired ratios with a bootstrap 95 percent interval, and below 1.00 means B is faster. A WIN needs the upper end of the interval below 0.90. No change needs the whole interval inside 0.97 to 1.03, and anything in between is GRAY.

    operation A B per operation, A to B B/A [95% CI] verdict
    save() of a new vertex 0.283 s 0.258 s 28.3 to 25.8 µs 0.911 [0.907, 0.913] GRAY
    save() after set() on a saved vertex 0.591 s 0.560 s 59.1 to 56.0 µs 0.947 [0.939, 0.952] GRAY
    new_edge 0.455 s 0.424 s 45.5 to 42.4 µs 0.930 [0.924, 0.935] GRAY

    All three are 5 to 9 percent faster and no interval contains 1.00, and A against itself in the same rounds came out at 0.997 to 1.001, so I read the gain as real but small. By my own rule it is neither a win nor no change, so I do not call it a win. Memory did not move (peak RSS B/A 0.992 to 0.993), and 500 creates with one transaction each, which are bound by the commit, come out at 0.987 [0.979, 0.996] (information only). The crossing counts (one instead of two for a create, three instead of four for an update) are asserted by tests/test_native_one_row_crossings.py, not by this run.

    Still missing: the per-operation profile of #229 (microseconds and crossings together) is not part of this run, and the A/B has not been repeated on the published 26.11.1.post2 wheel. I am leaving the issue open until one of the two says whether 5 to 9 percent is the gain to expect.

  5. tae898 commented on Nov 14, 2026

    @tae898
    CollaboratorAuthor

    Status. #329 is in 26.11.1.post1 and 26.11.1.post2 (merged 2026-11-10): a create is one crossing instead of two and an update three instead of four, and tests/test_native_one_row_crossings.py asserts the counts. The PR said it closes this issue, but it was merged into the release branch and not the default branch, so GitHub did not close it. By my rule it should not close yet.

    Measured: the A/B above, on a post1 candidate wheel and not on the published file. save() of a new vertex 0.911, save() after set() 0.947, new_edge 0.930 (B/A, intervals in the comment above): all GRAY by my rule, 5 to 9 percent faster and not a win.

    #385 skips the is_transaction_active crossing in save() of a saved record when auto-transaction is on, and it is in 26.11.1.post3 (on PyPI since 2026-11-14). It changes nothing with auto-transaction off, the default. The timed A/B of the CI-built post3 wheel against 26.11.1.post2 has a one-row set() and save() figure, but it ran inside explicit transactions with auto-transaction off, so it does not time the path that #385 changes, and I make no speed claim for #385. The stale-record SELECT is still its own crossing, so an update is still more than the JVM wheel's one.

    What remains: a timed figure for save() with auto-transaction on, which is the path #385 changes, and a decision whether the stale-record check should come from the update's own result. I will close the issue then.

    Edited 2026-11-14: the one-row update figure ran inside explicit transactions with auto-transaction off, the default, and not with auto-transaction on as this text first said. It therefore does not time the path that #385 changes (#385 only changes save() with auto-transaction on, see its own crossing table); the figure measures the post3 build on the default path, and its gain may come from #384. A timed figure for the auto-transaction-on path is still missing.

  6. tae898 commented on Nov 14, 2026

    @tae898
    CollaboratorAuthor

    I edited my comment above: it attributed the 0.932 one-row update figure of the post3 A/B to #385, but that figure ran with auto-transaction off, the default, where #385 changes nothing. I now make no speed claim for #385 and name the missing figure (save() with auto-transaction on) under "what remains". The issue stays open for that figure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

nativeThe arcadedb package (native library build)performanceSpeed or memory of the bindings, with a measurementpythonPull requests that update python code

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions