Skip to content

LDBC: an SNB Interactive v2 driver for DDIR, with DuckDB as a baseline - #1010

Merged
frankmcsherry merged 9 commits into
TimelyDataflow:master-nextfrom
frankmcsherry:ldbc-interactive-v2
Oct 24, 2026
Merged

frankmcsherry merged 9 commits into
TimelyDataflow:master-nextfrom
frankmcsherry:ldbc-interactive-v2

Conversation

@frankmcsherry

@frankmcsherry frankmcsherry commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

An LDBC SNB Interactive v2 driver for the DDIR server, with DuckDB as a baseline, and what it shows about DDIR. Everything is in interactive/bench/ldbc/interactive_v2/; its README has the details.

The driver reimplements the official Java driver's workload from its source, because the Java driver can't talk to the DDIR server:

  • every insert and delete as its own operation, at its own simulated time;
  • the 17 complex-read variants on their frequency grid, with the official paramgen's parameters;
  • each complex read followed by the driver's short-read walk;
  • the time compression ratio (TCR), and the audit: at most 5% of each operation type may start more than 1 s late.

Inputs are rebuilt from the BI SF1 CSV, because SURF's repository (official v2 update streams and parameters) was unreachable:

  • snb/operations.py turns the inserts and deletes into single operations with indexed cascades. Applied in order, they reproduce the BI daily journal at the end of all 33 days. test_operations.py, added to CI, checks the cascades against updates.State.
  • factors.py rebuilds the paramgen's factor tables; the official paramgen then runs on them unchanged.

Checks: with --validate, DDIR and DuckDB give identical answers on all 2,473 reads in the first 3,000 operations.

Results at SF1 on an M4 (4 performance and 6 efficiency cores):

lowest passing TCR throughput
DuckDB, 16 read threads 0.006 434 ops/s
DDIR, previous IC9/IC14 plans 0.008 330 ops/s
DDIR, rewritten IC9/IC14 0.007 377 ops/s
DDIR, rewritten plans, 64 threads, --batch-ms 50 0.006 441 ops/s
DDIR, complex reads one-off (--oneoff), 192 threads, --batch-ms 50 0.0025 1,049 ops/s

Where DDIR's time goes, from --tick-log and replay.py (8,000 ticks, 4 workers, previous plans):

  • Reads are 96% of server time, and about half of read work is retracting them.
  • Updates, including keeping all 21 plans' standing state current, take about 2 s of 71 s.
  • An empty tick costs about 1 ms, because every tick schedules all 1,122 operators.
  • From 1 to 4 workers, CPU grows from 116 to 212 s. Work is balanced across workers: per tick, the busiest does 1.09× the mean.

On one core, costs.py puts DDIR far ahead on short reads and paths. On IC1–IC12, DDIR does 1.6× DuckDB's work.

Plan changes (answers unchanged on 62 SF1 bindings and on the fixture check):

  • IC9: its final top 20 was keyed by request, so all candidates went to one worker. Each friend's own top 20 is now taken first. At 4 workers, answer plus retract goes from 223 to 116 ms.
  • IC14: Bellman-Ford over costs alone, then every least-cost predecessor, then the lexicographically first path, recovered by a walk from the source. 121 → 74 ms.

Also: client.py passes DDIR_DIAGNOSTICS through, as in #1009.

Since opening (rebased onto master-next for the move to interactive/bench/, #1012):

  • Delete cascades in the graph program (graph.py). DDIR now receives each delete as its explicit target and works out the cascade itself, as the official implementations do. Over the SF1 stream the live relations equal the BI daily journal at three checkpoints; 20,000 operations answer identically either way; test_cascade.py, added to CI, checks the rules on a fixture.
  • The driver streams its inputs (stream.py): the snapshot in chunks, the operations a day at a time. It peaks at 181 MB at SF1 instead of 8 GB. The server peaks at 7.5 GB instead of 4.7, for the arrangements the cascades look rows up by. DuckDB still gets exact deltas from the driver (--cascade client).
  • One-off complex reads (--oneoff, oneoff/): each IC plan split into a standing part and a per-read part that is installed, answered and dropped instead of retracted. With DDIR: every import is read as of the time its reader starts #1024 and Half joins read imported arrangements as of a frontier #1025 merged, a late install reads published traces consistently. Live at SF1 with --batch-ms 50: passes TCR 0.0025 (1,059 ops/s; rerun on master-next 7eb7da8, 1,049 ops/s) and fails 0.002. DuckDB's best is 0.006 (434 ops/s). This is the last row of the results table.
  • --sf 0.1: SF1's query mix on SF0.1's data. DuckDB passes 0.0001 (2,521 ops/s), DDIR 0.00007 (3,586 ops/s).

🤖 Generated with Claude Code

https://claude.ai/code/session_01GVgz9LXpYfNWPmu352Yt1u

frankmcsherry and others added 8 commits October 24, 2026 01:44
interactive_v2/driver.py reimplements the official Java driver's workload from its
source: per-operation updates on the simulation clock, complex reads on their
frequency grid with the paramgen's parameters, the short-read walk, TCR, and the
per-type 95% on-time audit. systems.py runs it against DDIR (group-commit ticks)
and DuckDB (concurrent connections). snb/operations.py turns the BI CSV inserts and
deletes into single operations with indexed cascades; factors.py rebuilds the
paramgen's inputs so the official paramgen can run. README.md lists what is
reproduced, where it departs, and first SF1 results.

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

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GVgz9LXpYfNWPmu352Yt1u
…hen recovers the path; driver --batch-ms

IC9's final top 20 is keyed by request, so every candidate went to one worker; each
slow-path friend now keeps their own 20 first (4 workers: 223 -> 116 ms answer plus
retract). IC14 runs Bellman-Ford on costs alone, keeps every least-cost predecessor,
and walks the smallest next node from the source over the nodes on a cheapest path
(121 -> 74 ms). Answers equal the previous plans on 62 SF1 bindings and the fixture.
The driver can hold DDIR ticks so they are fuller; the client passes DDIR_DIAGNOSTICS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GVgz9LXpYfNWPmu352Yt1u
…nd attribution

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GVgz9LXpYfNWPmu352Yt1u
oneoff/split.py splits IC1, IC9, IC10, IC12, IC13 and IC14 into a standing program
(their binding-independent statements, published) and a one-off program that each
read installs, answers once and drops at the next tick instead of retracting.
Experimental: a one-off installed late reads published traces without TimelyDataflow#982's
consistent-frontier import.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GVgz9LXpYfNWPmu352Yt1u
…--oneoff and --only

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GVgz9LXpYfNWPmu352Yt1u
… streams its inputs

graph.py's program takes inserted rows and explicit delete targets and derives the
live relations by snb.updates.State's rules, ahead of the unchanged graph program.
Over the SF1 stream they equal the BI daily journal (three checkpoints); 20,000
operations answer identically either way; test_cascade.py checks the rules in CI.
stream.py reads the snapshot in chunks and the operations a day at a time, and
complex reads are generated lazily: the driver peaks at 181 MB at SF1 (was 8 GB);
the server at 7.5 GB (was 4.7). --cascade client keeps the old path; DuckDB uses it.
Also --sf 0.1 (SF1's mix on SF0.1 data) and factors.py reading plain CSV partitions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GVgz9LXpYfNWPmu352Yt1u
…side snapshot

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GVgz9LXpYfNWPmu352Yt1u
…ejoin the results table

Since TimelyDataflow#1024 and TimelyDataflow#1025 a one-off reads every import as of the tick it starts, so the
README no longer calls the one-off runs experimental. The 0.0025 run, rerun on
master-next 7eb7da8, passes again (1,049 ops/s); 0.002 still fails. The one-off row
was split from the results table by a blank line.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GVgz9LXpYfNWPmu352Yt1u
@frankmcsherry
frankmcsherry merged commit 5fa6a6d into TimelyDataflow:master-next Oct 24, 2026
7 checks passed
@frankmcsherry
frankmcsherry deleted the ldbc-interactive-v2 branch October 24, 2026 17:51
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