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
native: save() of a saved record merges every property, not the ones that changed #245
save() of a saved record on the native build runs UPDATE :rid MERGE :row with row = every property the object holds, whether or not it changed. Since #194 the stored row is compared with the copy of the values as read before the statement runs, so every value that is as read is already stored: sending it again costs parameter encoding, a larger crossing payload and an engine merge of values that do not change. For a record with a vector (768 floats) and 20 other properties, one changed property sends the whole vector each time.
Proposal (Python only, src/arcadedb/_native/records.py): send only the values that changed, as the caller holds them (an ndarray stays an array). A save that changes nothing still runs its UPDATE with every value, so update events fire as before.
What this does not change: the insert path. save() of a new record adopts the stored row from the statement's reply (the engine converts values to the declared property types, which the record object does not know), so that reply is needed; the update path already skips its read-back for values stored as sent (#194), and command rows are already pulled but not encoded until read (arcadedb_n_cmd). A cheaper insert reply needs a change in the native library (an image rebuild), which is out of scope here.
Measurement: a step-0 probe (untimed) and then a timed A/B on the #194 workload, written to the repo's study standard before any run. The PR is stacked on #194 (the save path it changes) and stays draft until the study is read.
Timed A/B of PR #246 on the native build (laptop timed lane, tools/ab.py --noise, ABBA order, round 0 dropped, an A/A control). Arm A = main 7e64c5e plus PR #194ea2b429, arm B = PR #246324e41d. Per-save time, B/A median of the per-round ratios with its 95% interval (below 1 = PR #246 faster):
workload
role
B/A median [95% CI]
small record (vertex P, id and n), one property changed
gating
1.002 [0.991, 1.007]
medium record (20 properties and a 768-float vector), one property changed
gating
0.797 [0.792, 0.802]
first save of a new small record
control
1.014 [0.995, 1.017]
first save of a new medium record
control
0.995 [0.990, 1.002]
unchanged first touch
control
1.011 [1.003, 1.013]
Clean pairs per figure: 11; every A/A median between 0.998 and 1.002.
By the rule written before the run, PR #246 ships only if both gating figures are faster. The medium record saves faster (0.797 [0.792, 0.802]), while the small record's interval lies inside [0.97, 1.03] (1.002 [0.991, 1.007]), so the verdict is GRAY. Speed was the only reason for the change and the rule makes a GRAY final, so save() keeps the behaviour of #194 and PR #246 is closed. The medium-record result is recorded here in case a later change targets saves of large records.
Activity
tae898 commentedon Nov 4, 2026
Timed A/B of PR #246 on the native build (laptop timed lane,
tools/ab.py --noise, ABBA order, round 0 dropped, an A/A control). Arm A = main7e64c5eplus PR #194ea2b429, arm B = PR #246324e41d. Per-save time, B/A median of the per-round ratios with its 95% interval (below 1 = PR #246 faster):P,idandn), one property changedClean pairs per figure: 11; every A/A median between 0.998 and 1.002.
By the rule written before the run, PR #246 ships only if both gating figures are faster. The medium record saves faster (0.797 [0.792, 0.802]), while the small record's interval lies inside [0.97, 1.03] (1.002 [0.991, 1.007]), so the verdict is GRAY. Speed was the only reason for the change and the rule makes a GRAY final, so
save()keeps the behaviour of #194 and PR #246 is closed. The medium-record result is recorded here in case a later change targets saves of large records.