Skip to content

docs(gfql): reconcile the OLAP q9 figure and re-measure LadybugDB - #1826

Merged
lmeyerov merged 2 commits into
masterfrom
docs/reconcile-q9-and-remeasure-ladybug
Jul 28, 2026
Merged

docs(gfql): reconcile the OLAP q9 figure and re-measure LadybugDB#1826
lmeyerov merged 2 commits into
masterfrom
docs/reconcile-q9-and-remeasure-ladybug

Conversation

@lmeyerov

@lmeyerov lmeyerov commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Do not merge — for human review. Both figures here were defended-or-replaced, not deleted, per the rule that a published number is not withdrawn on the strength of a re-measurement alone.

Relationship to #1817 — resolved

#1817 has merged (7bc9b0077). This branch is rebased on top of it; the one-hunk
conflict on the OLAP paragraph was resolved by keeping #1817's q8 wording verbatim and
replacing only the q9 clause, and the duplicate CHANGELOG entry was dropped in favour of
#1817's. The diff against master is now exactly this PR's own change.


Claim A — the OLAP q9 "14.2×": verdict (b), reproduces only under a competitor handicap

The published 14.2× is 65.03 ms / 920.69 ms, and both halves survive as raw artifacts in pyg-bench (results/campaigns/2026-07-gfql-cypher-dominance/raw/tag-0.58.0-sweep/s3_100k_{tag0580_polars,kuzu_tag0580}.json). The Kuzu artifact records its query text verbatim, and it is not the query GFQL ran:

MATCH (b:Person) WHERE b.age < 50
OPTIONAL MATCH (a:Person)-[:FOLLOWS]->(b) WITH b, count(a) AS indeg
OPTIONAL MATCH (b)-[:FOLLOWS]->(c:Person) WHERE c.age > 25 WITH indeg, count(c) AS outdeg
RETURN sum(indeg * outdeg) AS numPaths

That is the old harness's OPTIONAL MATCH degree-product rewrite; GFQL ran the canonical two-hop path enumeration. Exactly q8's defect, on the same page, in the same sentence.

Re-run: dgx-spark, host perf lock held and verified released, master 1537e467, Kuzu 0.11.3, query text md5 6e7ae268…, 30 timed runs after 5 warmups, slots interleaved K G G K in one session, host load 0.96 → 4.53. Every slot value-identical (45,562,239 at 100k; 1,017,900 at 20k):

arm @100k K1/G1 K2/G2 published
GFQL engine='polars', canonical q9 66.26 ms 64.22 ms 65.03 ms
Kuzu 0.11.3, canonical q9 83.93 ms 83.13 ms
Kuzu 0.11.3, q9_degprod rewrite 878.51 ms 859.49 ms 920.69 ms
  • our side reproduces (within 2%)
  • the competitor's absolute reproduces under the rewrite (within 4.6–6.6%)
  • handicap = 10.3–10.5×
  • honest same-query ratio = 1.27–1.29×, consistent with the 1.26× already published with provenance as graphbench.100k.q9.polars_vs_kuzu

At 20k the published 14.17 vs 60.00 ms = 4.2× behaves identically: 60.00 is the rewrite (reproduced at 56.76–62.54), and against the canonical query that cell is a loss (0.62–0.67×).

→ the page now publishes the measured 66.6 ms vs 84.1 ms (1.26×).


Claim B — LadybugDB was actually installed and run

benchmarks/gfql/bench_ladybug_cypher.py:52-53 hardcoded LADYBUG = {"full_scan": 3789, "range": 7.5, "point": 0.3, "count": 3.3, "scan_rel_props": 15722, "scan_rel_rowid": 14562} — no URL, no version, no hardware, no citation, and the only competitor input to ~65× / ~3.5–3.7×. Verified by inspection.

LadybugDB 0.18.1 (pip install ladybug==0.18.1, cp312 aarch64 wheel) measured on dgx-spark under the perf lock, in one session, against GFQL engine='polars' (master 1537e467, graphistry/test-rapids-official:26.02-gfql-polars, polars 1.35.2) on one generated 5M-node / 20M-edge graph read by both arms (nodes.parquet sha256 22c8b239…, edges.parquet 80ededc5…). 2 warmups + 5 timed runs, slots L G G L, host load 1.53 → 2.54 (the lane waited for a quiet host). Result values digest-identical across both engines on every op and every slot — rows and values, so no ratio here is over mismatched rows.

Ladybug is timed at its fastest result-producing scope of four measured (Arrow / polars / pandas / Python row iterator). Choosing a slower scope for a competitor is the defect being corrected.

op GFQL polars Ladybug 0.18.1 ratio previously published
full_scan 58.95 ms 364.28 ms GFQL 6.18× ~65×
range 5.06 ms 7.57 ms GFQL 1.50×
point 3.53 ms 0.30 ms Ladybug already conceded on the page
count 2176.93 ms 3.08 ms Ladybug already conceded on the page
scan_rel_props 3256.91 ms 1925.61 ms Ladybug 1.69× GFQL ~3.5×
scan_rel_rowid 3157.46 ms 1449.13 ms Ladybug 2.18× GFQL ~3.7×

The literal is reconciled, not merely contradicted. It is a drain-scope measurement — rows pulled through the Python iterator, the protocol the upstream LadybugDB/kuzu-ladybug-benchmark runner uses — taken on hardware ~1.5× slower:

op literal LB drain here literal / drain
full_scan 3789 2490.95 1.52
scan_rel_props 15722 10093.61 1.56
scan_rel_rowid 14562 9594.86 1.52
range / point / count 7.5 / 0.3 / 3.3 7.65 / 0.30 / 3.10 0.98 / 0.99 / 1.06

The three ops where the scope is irrelevant reproduce to within 2–6%; the three scans carry one uniform factor. And our own side was right again~65× implies a 58.3 ms GFQL full scan; measured 58.95 ms. Effective handicap the literal applied to Ladybug: 10.4× / 8.2× / 10.0×.

Docs consequences:

  • ~65×6.2× — still a GFQL win, an order of magnitude smaller
  • ~3.5–3.7×withdrawn; measured fairly on one box those ops are not GFQL wins, so the page stops claiming them rather than restating them
  • the literal in bench_ladybug_cypher.py is replaced by the measured values with provenance in the module docstring, so a fabricated competitor column cannot be republished from it

The 1.8B-edge graph in <8 GB RAM line is not LadybugDB's claim

Not on ladybugdb.com, blog.ladybugdb.com, docs.ladybugdb.com, or their GitHub README. What LadybugDB documents is qualitative — "On-disk databases allow you to process larger-than-memory workloads" — which is what engines.rst now says. The 1.8B traces to our own commit e676d7cbe, whose message reads "GFQL is in-memory; single-node ceiling at ~1.8B": a statement about GFQL and the Friendster graph, later transposed onto the competitor. The <8 GB RAM half has no located source at all. Not citable → the number goes, the citable qualitative claim stays.


Provenance and gates

  • All measurement on dgx-spark under /tmp/pyg-bench-perf.lock, released and verified FREE after each lane. Nothing was measured on the dev box.
  • Raw JSON, runners and full provenance: pyg-bench docs(default no key) #116 (benchmarks/ladybug_scan/, results/ladybug-scan-20260728/, results/graphbench-q9-reconciliation-20260728/) — ./bin/lint clean, pytest 460 passed / 26 skipped, smoke CI green.
  • Docs build run on dgx (docs/ci.sh, Dockerised, --gpus not required for the sphinx lane): exit 0, build succeeded; zero warnings reference gfql/performance or gfql/engines (the log's warnings are pre-existing autodoc/notebook ones present on master).
  • Disclosure: the Ladybug lane publishes engine='polars' only. A pandas arm was started and abandoned mid-run for shared-host safety — another agent's unlocked job had the box at 3 GB free — so it is not reported rather than reported partially.
  • Follow-up recorded in pyg-bench docs(default no key) #116: these Ladybug cells are not yet exported through manifests/docs_numbers_spec.json (needs a new exporter kind, and the consumer docs(gfql): print the benchmark numbers pyg-bench publishes, and re-verify them #1818 is unmerged), so the page quotes them with a pointer to the committed artifact.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YYZRXegrALuXd3NHH5evqx

lmeyerov and others added 2 commits July 28, 2026 00:43
The page read "q9 is 14.2x". Reconciled the same way q8 was, and the answer is
the same: our own number was right and the competitor was measured running a
DIFFERENT query.

The published 14.2x is 65.03 ms / 920.69 ms, and both halves are in pyg-bench's
0.58.0 tag sweep. The Kuzu artifact stores its query text verbatim, and it is
the old harness's OPTIONAL MATCH degree-product rewrite -- not the canonical
two-hop path enumeration GFQL ran.

Re-run on dgx-spark under the host perf lock (pygraphistry master 1537e46,
Kuzu 0.11.3, 30 timed runs after 5 warmups, slots interleaved K G G K, one
session, host load 0.96 -> 4.53), every slot value-identical at 45,562,239:

  GFQL engine='polars', canonical q9   66.26 / 64.22 ms   (65.03 published)
  Kuzu 0.11.3, canonical q9            83.93 / 83.13 ms
  Kuzu 0.11.3, q9_degprod rewrite     878.51 / 859.49 ms  (920.69 published)

So our side reproduces, the rewrite reproduces, and the handicap applied to the
competitor is 10.3-10.5x. The honest same-query ratio is 1.27-1.29x this
session, consistent with the 1.26x the matched lane already publishes with
provenance (pyg-bench published/docs-numbers.json,
graphbench.100k.q9.polars_vs_kuzu) -- which is the figure the page now quotes.

The 20k cell behaves identically: published 14.17 vs 60.00 ms = 4.2x, where
60.00 is again the rewrite (reproduced at 56.76-62.54) and the canonical query
makes that cell a loss, not a win.

Raw artifacts and the runner: pyg-bench
results/graphbench-q9-reconciliation-20260728/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YYZRXegrALuXd3NHH5evqx
…eral

bench_ladybug_cypher.py carried

  LADYBUG = {"full_scan": 3789, "range": 7.5, "point": 0.3, "count": 3.3,
             "scan_rel_props": 15722, "scan_rel_rowid": 14562}

with no URL, no version, no hardware and no citation, and it was the only
competitor input to the published "~65x" and "~3.5-3.7x" ratios. So LadybugDB
was actually run.

MEASURED (dgx-spark, host perf lock held and verified released, load 1.53 ->
2.54, 2026-07-28): LadybugDB 0.18.1 embedded in a host venv against GFQL
engine='polars' on pygraphistry master 1537e46 in
graphistry/test-rapids-official:26.02-gfql-polars (polars 1.35.2), both arms in
ONE session on ONE generated 5M-node / 20M-edge graph (nodes.parquet sha256
22c8b239..., edges.parquet 80ededc5...), 2 warmups + 5 timed runs, slots
interleaved L G G L. Every op's result values are digest-identical across the
two engines on all four slots -- rows AND values, not rows alone.

Ladybug is timed at its FASTEST result-producing scope of four measured (Arrow,
polars, pandas, Python row iterator). Choosing a slower scope for a competitor
is exactly the defect being corrected here.

  full_scan       GFQL   58.95 ms   Ladybug  364.28 ms   GFQL 6.18x
  range           GFQL    5.06 ms   Ladybug    7.57 ms   GFQL 1.50x
  scan_rel_props  GFQL 3256.91 ms   Ladybug 1925.61 ms   -> no longer claimed
  scan_rel_rowid  GFQL 3157.46 ms   Ladybug 1449.13 ms   -> no longer claimed

So "~65x" becomes 6.2x, and the "~3.5-3.7x" relationship-scan claim is
withdrawn rather than restated -- measured fairly on one box those are not GFQL
wins.

The literal is RECONCILED, not merely contradicted: it is a drain-scope
measurement (rows pulled through the Python iterator, the protocol the upstream
LadybugDB/kuzu-ladybug-benchmark runner uses) on hardware ~1.5x slower. literal
/ drain-measured-here is 1.52 / 1.56 / 1.52 on the three scans and 0.98-1.06 on
the three ops where the scope is irrelevant. And our own side was right again:
"~65x" implies a 58.3 ms GFQL full scan; measured 58.95 ms.

engines.rst also loses "query a 1.8B-edge graph in <8 GB RAM", attributed to
LadybugDB. It is not on ladybugdb.com, blog.ladybugdb.com, docs.ladybugdb.com
or their GitHub README; LadybugDB documents only "on-disk databases allow you
to process larger-than-memory workloads", which is what the row now says. The
1.8B traces to our own commit e676d7c -- "GFQL is in-memory; single-node
ceiling at ~1.8B" -- a statement about GFQL and Friendster, transposed onto the
competitor.

Runner, raw JSON and full provenance: pyg-bench benchmarks/ladybug_scan/ and
results/ladybug-scan-20260728/.

Docs build on dgx: docs/ci.sh exit 0, build succeeded, 19 pre-existing warnings,
none from these pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YYZRXegrALuXd3NHH5evqx
@lmeyerov
lmeyerov force-pushed the docs/reconcile-q9-and-remeasure-ladybug branch from 58bbc27 to 9d778fe Compare July 28, 2026 07:46
@lmeyerov
lmeyerov merged commit 02041a4 into master Jul 28, 2026
69 checks passed
@lmeyerov
lmeyerov deleted the docs/reconcile-q9-and-remeasure-ladybug branch July 28, 2026 08:43
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