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
docs(gfql): reconcile the OLAP q9 figure and re-measure LadybugDB (#1826)
* docs(gfql): replace the OLAP q9 "14.2x" with the re-measured 1.26x
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
* docs(gfql): re-run LadybugDB and replace the hardcoded competitor literal
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
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+4-2Lines changed: 4 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -42,8 +42,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
42
42
43
43
### Documentation
44
44
-**GFQL pay-as-you-go resident indexing user guide**: New :doc:`Pay-As-You-Go Resident Indexing <gfql/indexing>` page — the lifecycle guide to resident indexes (`gfql_index_all()` / `gfql_index_edges()` / `create_index()` / `show_indexes()` / `drop_index()`): what the node-id + CSR in/out adjacency sidecars are, what engages them on 0.58.0 (seeded typed-hop fast paths incl. property RETURNs and property-seeded lookups per #1768/#1770, direct `g.hop()`; the general polars chain traversal honestly noted as not yet covered), the staleness/validity contract (identity + fingerprint; rebind invalidates; declines are safe — identical results either way), engine notes (polars needs `gfql_index_all(engine='polars')` until #1767), 0.58.0-tag measured numbers, and a runnable end-to-end example. Wired into the GFQL toctree + recommended paths alongside :doc:`Seeded Traversal Indexes <gfql/index_adjacency>`.
45
-
- **GFQL performance docs: 0.58.0 release-tag-verified numbers, siloed in one page**: `gfql/performance.rst` is now the canonical benchmark-numbers page (alongside `gfql/index_adjacency.rst` for the index benchmarks) — a benchmark rerun updates it alone. It carries the 0.58.0 tag sweep (DGX Spark GB10, warm medians N=30; four-engine numbers cross-engine parity-verified, competitor pairs validated against expected result rows): seeded typed-hop fast path across all four engines (e.g. pandas 29.9→2.46ms, 12.1×), native chain form, resident-index covered-shape lookups (with the `gfql_index_all(engine='polars')` caveat / PR #1767), flat seeded-hop scaling on pandas (0.159–0.164ms from 0.25M to 32M edges), the one-keyword `engine='polars'` LDBC SNB SF1 seed-lookup win (1,299.6→106.1ms, 12.3×), LDBC SNB interactive SF1 vs Neo4j 5.26 same-box pairs (GFQL 4 of 5; Neo4j wins recent-replies — reported as-is), OLAP multi-join vs embedded Kuzu (q9 14.2×) with the honest inverse (Kuzu wins single-table aggregates 2–4×, seeded property-projection lookups 2.4–64×), plus the prior Orkut/LiveJournal bulk sweep (moved from `engines.rst`, dated once) and its methodology. All other pages — `engines.rst`, `quick.rst`, `about.rst`, `overview.rst`, `index.rst` — now carry stable qualitative claims (e.g. "often an order of magnitude faster on query-heavy workloads") that link into `performance.rst` instead of inline figures, replacing the stale "up to ~38×" headlines and avoiding scattered per-claim version labels.
46
-
-**Withdrew the OLAP q8 magnitude claim from `gfql/performance.rst`** (no replacement figure published): the page read "**q8 runs in 5.0 ms vs 1,004 ms for embedded Kuzu (200×)**". Reconciled rather than merely re-measured — our own side held up (5.0 ms published vs 5.06 ms re-measured), but the 1,004 ms was not canonical Kuzu: the old harness replaced q8/q9 path enumeration with an `OPTIONAL MATCH` degree-product rewrite and fed Kuzu pre-lowercased columns built *outside* the timing loop, while GFQL paid `toLower()` in-query. Re-running that same handicapped configuration reproduces **964.60 ms on the same box**, i.e. a 98.9× handicap applied to the competitor; canonical Kuzu 0.11.3 on a position-balanced, row-validated run at 100k persons / 2.4M FOLLOWS is **9.76 ms vs 5.06 ms = 1.93×**. The directional claim (`engine='polars'` wins q8) survives and is kept; the magnitude is withdrawn. The companion q9 figure is unchanged here — it is being researched separately.
45
+
- **GFQL performance docs: 0.58.0 release-tag-verified numbers, siloed in one page**: `gfql/performance.rst` is now the canonical benchmark-numbers page (alongside `gfql/index_adjacency.rst` for the index benchmarks) — a benchmark rerun updates it alone. It carries the 0.58.0 tag sweep (DGX Spark GB10, warm medians N=30; four-engine numbers cross-engine parity-verified, competitor pairs validated against expected result rows): seeded typed-hop fast path across all four engines (e.g. pandas 29.9→2.46ms, 12.1×), native chain form, resident-index covered-shape lookups (with the `gfql_index_all(engine='polars')` caveat / PR #1767), flat seeded-hop scaling on pandas (0.159–0.164ms from 0.25M to 32M edges), the one-keyword `engine='polars'` LDBC SNB SF1 seed-lookup win (1,299.6→106.1ms, 12.3×), LDBC SNB interactive SF1 vs Neo4j 5.26 same-box pairs (GFQL 4 of 5; Neo4j wins recent-replies — reported as-is), OLAP multi-join vs embedded Kuzu (q9 1.26×) with the honest inverse (Kuzu wins single-table aggregates 2–4×, seeded property-projection lookups 2.4–64×), plus the prior Orkut/LiveJournal bulk sweep (moved from `engines.rst`, dated once) and its methodology. All other pages — `engines.rst`, `quick.rst`, `about.rst`, `overview.rst`, `index.rst` — now carry stable qualitative claims (e.g. "often an order of magnitude faster on query-heavy workloads") that link into `performance.rst` instead of inline figures, replacing the stale "up to ~38×" headlines and avoiding scattered per-claim version labels.
46
+
-**Withdrew the OLAP q8 magnitude claim from `gfql/performance.rst`** (no replacement figure published): the page read "**q8 runs in 5.0 ms vs 1,004 ms for embedded Kuzu (200×)**". Reconciled rather than merely re-measured — our own side held up (5.0 ms published vs 5.06 ms re-measured), but the 1,004 ms was not canonical Kuzu: the old harness replaced q8/q9 path enumeration with an `OPTIONAL MATCH` degree-product rewrite and fed Kuzu pre-lowercased columns built *outside* the timing loop, while GFQL paid `toLower()` in-query. Re-running that same handicapped configuration reproduces **964.60 ms on the same box**, i.e. a 98.9× handicap applied to the competitor; canonical Kuzu 0.11.3 on a position-balanced, row-validated run at 100k persons / 2.4M FOLLOWS is **9.76 ms vs 5.06 ms = 1.93×**. The directional claim (`engine='polars'` wins q8) survives and is kept; the magnitude is withdrawn.
47
+
- **Replaced the OLAP q9 figure in `gfql/performance.rst` with a re-measured value**: the page read "q9 is **14.2×**"; it now reads **66.6 ms vs 84.1 ms for embedded Kuzu 0.11.3 (1.26×)** at 100k persons / 2.4M FOLLOWS with `engine='polars'`. Reconciled, not merely contradicted, and the same way q8 was: the published 14.2× was **65.03 ms / 920.69 ms**, and the raw artifact behind it records a Kuzu query text that is *not the query GFQL ran* — Kuzu got the old harness's `OPTIONAL MATCH` degree-product rewrite while GFQL ran the canonical two-hop path enumeration. Re-run on dgx-spark under the perf lock (30 timed runs after 5 warmups, position-balanced K G G K, both arms in one session, values identical at 45,562,239 on every slot): our own side reproduces (**64.2–66.3 ms** vs 65.03 published), the rewrite reproduces (**859.5–878.5 ms** vs 920.69 published), and canonical Kuzu answers the *same* query in **83.1–83.9 ms** — a **10.3–10.5× handicap** applied to the competitor. The honest same-query ratio is **1.27–1.29×** this session, consistent with the 1.26× published by the provenance-carrying matched lane (`pyg-bench published/docs-numbers.json`, key `graphbench.100k.q9.polars_vs_kuzu`), which is the figure the page now quotes.
48
+
- **LadybugDB was actually run, and the hardcoded competitor literal is now a measurement**: `benchmarks/gfql/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}` — a bare dict with no URL, version, hardware or citation, and the only competitor input to the published `~65×` / `~3.5–3.7×` ratios. LadybugDB **0.18.1** was installed and run on dgx-spark under the perf lock, in one session, against GFQL `engine='polars'` (master `1537e467`, RAPIDS image, 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 interleaved L G G L, host load 1.53 → 2.54. Every op's result values are **digest-identical** across the two engines on all four slots. Ladybug is timed at its fastest result-producing scope of four measured (Arrow / polars / pandas / row iterator), because picking a slower scope for a competitor is the defect being corrected. Measured: full node scan **59.0 ms vs 364.3 ms = 6.2×** (published `~65×`) and the 1,001-row range scan **5.1 ms vs 7.6 ms = 1.5×**. **The `~3.5–3.7×` relationship-scan claim is withdrawn — measured fairly on one box those ops are not GFQL wins** (`scan_rel_props` 3256.9 vs 1925.6 ms; `scan_rel_rowid` 3157.5 vs 1449.1 ms), so the page no longer claims them. The old literal is **reconciled**: it is a Python-row-iterator ("drain") measurement on hardware ~1.5× slower — literal / drain-here is 1.52 / 1.56 / 1.52 on the three scans and 0.98–1.06 on the three ops where scope is irrelevant — and our own side was right again (`~65×` implies a 58.3 ms GFQL scan; measured 58.95 ms). Also removed the `1.8B-edge graph in <8 GB RAM` capacity line attributed to LadybugDB in `engines.rst`: it is not on ladybugdb.com, blog.ladybugdb.com, docs.ladybugdb.com or their GitHub README — LadybugDB documents only the qualitative *"on-disk databases allow you to process larger-than-memory workloads"*, which is what the page now says — and the `1.8B` traces to our own commit `e676d7cbe` ("GFQL is in-memory; single-node ceiling at ~1.8B"), i.e. a statement about GFQL and Friendster that was transposed onto the competitor. Runner, raw JSON and provenance: pyg-bench `benchmarks/ladybug_scan/` and `results/ladybug-scan-20260728/`.
0 commit comments