Skip to content

Automatic Cypher→DataFusion scan routing and faster Turso MVCC loads - #5

Closed
alexy wants to merge 9 commits into
mainfrom
grust-best
Closed

alexy wants to merge 9 commits into
mainfrom
grust-best

Conversation

@alexy

@alexy alexy commented Sep 15, 2026

Copy link
Copy Markdown
Member

Summary

  • Automatic Cypher→DataFusion routing (5b5a708). grust_datafusion::cypher::RoutedGraph admits each read once and picks DataFusion automatically for bounded single-node scans on graphs of at least 3,000 nodes. It charges candidate work and intermediate bytes against the caller's ReadQueryPolicy. Joins and unsupported shapes stay on the reference executor, and errors after execution starts are not rerouted. Large single-batch tables are split into zero-copy partitions, which fixed the intermittent 256 MiB memory exhaustion at one million nodes. The commit also adds LadybugConfig::max_db_bytes, capped at 16 GiB in tests.
  • Turso: retired edge-source index; MVCC loads end in TRUNCATE (1d1e41f). Turso's planner already answers source lookups from the edge primary key, so the index only cost a write per edge.

Turso MVCC checkpoints: measured, then reverted

On the real web-Google edge list (bulk_load --snap, MVCC, quegee):

build edges/s peak RSS
previous harness pin 0176718 13,321 1.8 GB
this branch (1d1e41f behavior) 16,356 1.7 GB
+ 64 MiB checkpoint threshold 15,914 3.5 GB
+ PASSIVE automatic checkpoints 5,138 11.5 GB

Turning automatic checkpoints off entirely was fast but kept every row version of the load resident: 13.4 GB at 5M edges, growing about 0.4 GB per million. It would not fit com-Orkut on a 32 GB host. PASSIVE, the 64 MiB threshold and checkpoints-off (3e8b13a, 0291906, a26fdff) are therefore reverted in 41f352d, 3d3ba0b and 5dff9ac.

The adversarial-graph harness itself on lakecat, web-Google turso-mvcc LOAD: 0176718 took 570 s (8,950 edges/s, 3.9 GB); this branch took 431 s (11,835 edges/s, 3.85 GB).

Test plan

  • cargo fmt --all -- --check
  • cargo test -p grust-turso -p grust-sql-core
  • Full workspace tests and warnings-denied Clippy on 5b5a708 (1,675 passed, 0 failed, 49 ignored), run with the uncommitted lbug git pin and LBUG_LOCALIZE_BUNDLED_SYMBOLS=1
  • Full workspace gate on the branch head
  • Strain reruns (harness pinned at 5dff9ac): turso-wal vs turso-mvcc up to com-Orkut, running on grust, eigen and quegee

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sak2FQcdgs5pkL2ruKrSUZ

alexy and others added 9 commits September 14, 2026 23:35
Add grust_datafusion::cypher::RoutedGraph: one graph captured as a typed
index and an Arrow snapshot, one bounded-read admission per query, and
DataFusion execution for single-node scan plans whose every physical
operator charges candidate work and intermediate bytes against the
caller's ReadQueryPolicy. Joins, unsupported shapes, graphs under 3,000
nodes and plans over a limit stay on the reference executor through the
new run_prepared_read_query_indexed, which keeps the original deadline.

Split large single-batch Arrow tables into zero-copy partitions so no
round-robin repartition charges each queued slice its shared parent
buffers; that intermittently exhausted a 256 MiB pool at one million
nodes.

Add LadybugConfig::max_db_bytes and cap unit-test databases at 16 GiB;
parallel tests exhausted x86-64 address space with 8 TiB reservations.

Workspace gate on quegee: 1,675 passed, 0 failed, 49 ignored; Clippy clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZ7SjQFbe58VDZs6xR3nCo
…RUNCATE

Profiling an MVCC bulk load (pprof, bulk_load example, 1M edges over 50k
nodes) put 60% of the time in the upserts' index work in the MVCC store and
31% in the automatic checkpoints that write the same rows into the B-tree.
The edge table carried three indexes: the primary key (from_id, label,
to_id, identity_key), to_idx, and from_idx on from_id alone. Turso's planner
never reads from_idx: source lookups and every traversal hop already use the
primary key's from_id prefix, with or without from_idx present (EXPLAIN
QUERY PLAN). The index only cost a write per edge.

Turso's dialect now reports edge_source_index() = false (a new
GraphSqlDialect method, default true, so Postgres keeps its index), the
universal bootstrap omits the index for it, and bootstrap drops it from
existing Turso stores. Query plans and results are unchanged.

MVCC loads now end in PRAGMA wal_checkpoint(TRUNCATE) inside put_graph, as
WAL loads already did, so a load returns with its rows in the database file
and the in-memory store drained. Under the default blocking automatic
checkpoints this costs nothing measurable.

Optional mimalloc feature: Turso's own default allocator (turso/mimalloc),
which installs it as the binary's global allocator; MVCC loads ran about 9%
faster with it. Off by default, because it changes the allocator of every
backend in a binary.

1M edges, 4-core host: MVCC 12.7k -> 15.9k edges/s, WAL 43.6k -> 51.5k.

New tests: an MVCC bulk load with checkpoints mid-load stores exactly the
rows of the WAL load (identity_key included), survives a reopen and takes
concurrent BEGIN CONCURRENT writers afterwards; bootstrap omits the index
and drops a legacy one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MVCC connections open with turso::Builder::
experimental_mvcc_passive_checkpoint(true), so the automatic checkpoints
taken every mvcc_checkpoint_threshold bytes of logical log (4,120,000 by
default in Turso 0.7.2) run PASSIVE instead of TRUNCATE and do not block
the store's other readers and writers. put_graph still ends in one TRUNCATE
checkpoint, so a load returns durable in the database file. WAL connections
are unchanged; the option only affects MVCC checkpoints.

Measured cost on a single-writer bulk load (bulk_load example, 4-core host,
no edge-source index): 5M edges 14.4k edges/s blocking -> 8.7k passive,
peak RSS 0.9 GB -> 2.8 GB. PASSIVE reclaims emptied version chains lazily,
so the in-memory store and each checkpoint's scan grow during the load,
and each put_graph pays its deferred work in the final TRUNCATE (~11 s per
1M-edge call). The option is experimental in Turso 0.7.2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
put_graph on an MVCC store sets PRAGMA mvcc_checkpoint_threshold = -1
for the load, so no automatic checkpoint writes the rows committed so
far into the B-tree mid-load (profiled at about a third of an MVCC
load). The load still ends in one TRUNCATE checkpoint, and the store's
previous threshold is restored whether or not the load succeeds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sak2FQcdgs5pkL2ruKrSUZ
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sak2FQcdgs5pkL2ruKrSUZ
Turning automatic checkpoints off for an MVCC put_graph kept every row
version of the load resident: 13.4 GB at 5M edges against 11.4 GB for
Turso's 4 MB default, heading past a 32 GB host well before com-Orkut.
Loads now raise the threshold to MVCC_LOAD_CHECKPOINT_BYTES (64 MiB)
and restore the store's own threshold afterwards.

bulk_load example on quegee, MVCC: 1M edges 11.8k -> 15.4k edges/s
(2.31 -> 2.35 GB peak); 5M edges 995 s -> 423 s, 11.4 GB both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sak2FQcdgs5pkL2ruKrSUZ
@alexy

alexy commented Sep 15, 2026

Copy link
Copy Markdown
Member Author

Harness confirmation on lakecat (4 cores, 15 GB), adversarial-graph ag run --backend turso-mvcc, same host and same harness for both pins:

dataset pin LOAD edges/s client peak RSS gates
web-Google (5.1 M edges) 0176718 (previous) pass, 570 s 8,950 3.9 GB pass
web-Google 5dff9ac (this branch) pass, 431 s 11,835 3.85 GB pass
cit-Patents (16.5 M edges) 0176718 pass 3,046 7.2 GB A1 A2 A4 A7 A12 pass
cit-Patents 5dff9ac pass, 3,470 s 4,760 7.1 GB A1 A2 A4 A7 A12 pass

The interim pins with PASSIVE checkpoints did not finish web-Google within 1,800 s (eigen, lakecat), and cit-Patents was OOM-killed at a 13 GiB cap.

@alexy

alexy commented Sep 15, 2026

Copy link
Copy Markdown
Member Author

turso-wal through the full strain ladder on grust (2xlarge class, 32 GB; harness pin a26fdff, whose WAL code is identical to this branch's). Every graph passed LOAD, A1, A2, A4, A7 and A12; A3 is unsupported as before.

dataset edges LOAD edges/s load s client peak RSS
web-Google 5.1 M 52,585 97 3.3 GB
soc-Pokec 30.6 M 41,397 740 2.8 GB
cit-Patents 16.5 M 42,632 387 7.0 GB
GAP-road 57.7 M 45,525 1,268 11.3 GB
sx-stackoverflow 63.5 M 34,954 1,817 7.7 GB
soc-LiveJournal1 69.0 M 33,992 2,030 10.4 GB
com-Orkut 117.2 M 26,197 4,473 12.8 GB

The previously published strain summary had Turso WAL passing every strict gate only up to GAP-road. turso-mvcc on this branch is still running on quegee and eigen.

@alexy

alexy commented Sep 15, 2026

Copy link
Copy Markdown
Member Author

Full workspace gate on the branch head 5dff9ac (grust host, 8 cores, 4 jobs under a 24 GiB cgroup cap): cargo test --workspace --all-features --no-fail-fast passed 1,677 tests with 0 failed and 49 ignored across 101 test binaries, and cargo clippy --workspace --all-features --all-targets -- -D warnings is clean. Like the earlier gate, it ran with the uncommitted lbug git pin (ladybug-rust 47bfd12e, which contains PR #33) and LBUG_LOCALIZE_BUNDLED_SYMBOLS=1, because no crates.io lbug release has that fix yet.

@alexy

alexy commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

Superseded by #6. grust-best head 5dff9ac is an exact ancestor of turso-mvcc-concurrency (no commits unique to this branch), and #6 adds 14 commits on top: durable group commit, parallel MVCC bulk loads with round checkpoints, live WAL↔MVCC conversion, and the full WAL-vs-MVCC head-to-head through com-Orkut.

The three evidence comments here are carried over verbatim to #6 (comment) — including the workspace gate on 5dff9ac, which documents the lbug git pin (ladybug-rust 47bfd12e / PR #33) and LBUG_LOCALIZE_BUNDLED_SYMBOLS=1 that make cargo test --workspace --all-features pass in full.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sak2FQcdgs5pkL2ruKrSUZ

@alexy alexy closed this Sep 16, 2026
alexy added a commit that referenced this pull request Sep 20, 2026
Documentation only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
alexy added a commit that referenced this pull request Sep 20, 2026
Main moved under the gate by codex-to-codex.md and ci-local.sh alone, so no
crate or benchmark source is unverified. Proposes the refinement that keeps the
standing verdict workable: restrict the recheck to code paths, or the task never
finishes while the board is active.

ACK G5 after G0. Also corrects the credit: the catch came from asking what else
the line was being quoted for, not from the check that passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
alexy added a commit that referenced this pull request Sep 20, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sak2FQcdgs5pkL2ruKrSUZ
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