SPARQL crossover — post-preload benchmarks (May 2026) #69
Replies: 5 comments
Large-scale crossover (100k–1M, post-preload)Closes #76. Deno 2.8.0 (Windows x86_64), Execute avg (
|
| Quads | Query shape | Backend | Avg |
|---|---|---|---|
| 100000 | selective | hydrate+N3 | 534 µs |
| 100000 | selective | libsqlStore | 29.1 ms |
| 100000 | fullScan | hydrate+N3 | 68.7 ms |
| 100000 | fullScan | libsqlStore | 181 ms |
| 250000 | selective | hydrate+N3 | 292 µs |
| 250000 | selective | libsqlStore | 74.7 ms |
| 250000 | fullScan | hydrate+N3 | 443 ms |
| 250000 | fullScan | libsqlStore | 438 ms |
| 500000 | selective | hydrate+N3 | 284 µs |
| 500000 | selective | libsqlStore | 142 ms |
| 500000 | fullScan | hydrate+N3 | 210 ms |
| 500000 | fullScan | libsqlStore | 840 ms |
| 1000000 | selective | hydrate+N3 | 366 µs |
| 1000000 | selective | libsqlStore | 286 ms |
| 1000000 | fullScan | hydrate+N3 | 424 ms |
| 1000000 | fullScan | libsqlStore | 1.8 s |
Module preload (not in Deno.bench)
Per backend after shared generateSyntheticQuads: 100k ~33–39 s; 250k ~91 s; 500k ~193–196 s; 1M ~466–509 s.
Takeaways
- Production hot path: selective +
libsqlStorescales roughly linearly to ~286 ms at 1M (vs ~39 ms at 50k in the earlier table). - Avoid at scale: fullScan +
libsqlStorereaches ~1.8 s at 1M; hydrate+N3 fullScan ~424 ms but full N3 mirror at 1M is not a production pattern (#68). - Harness: opt-in
benchmarks/sparql-hexastore-crossover-large.bench.ts; defaultdeno task benchunchanged (50k max).
Also fixed bulk-import stack overflow in commitPatchToLibsql (spread on 250k+ statements) so large preload succeeds.
Large crossover update (merged via PR #85)Methodology (100k–1M):
Module preload ( Execute ( Dev opt-in: API: |
BENCH_REUSE_DB smoke (post-merge, Deno 2.8.0 Windows)Verified after PR #85 merge: Preload (
|
| Scale | Run 1 (build cache) | Run 2 (cache hit) |
|---|---|---|
| 100k | ~22 s | ~1.4 s |
| 250k | ~61 s | ~2.9 s |
| 500k | ~128 s | ~5.7 s |
| 1M | ~256 s | ~9.4 s |
Run 1 logs (imported to file cache); run 2 logs (cache hit) for all scales. Cache dir: benchmarks/.cache/crossover-large/ (manifest over SYNTHETIC_CORPUS_VERSION, BENCH_LIBSQL_SCHEMA_VERSION, quad count, searchIndexOnImport: false).
Execute on file-backed cache (run 2, not comparable to :memory: README table)
| Quads | selective | fullScan |
|---|---|---|
| 100k | ~113 ms | ~852 ms |
| 250k | ~280 ms | ~2.9 s |
| 500k | ~694 ms | ~6.3 s |
| 1M | ~1.4 s | ~12.2 s |
Published baselines in benchmarks/README.md remain :memory: preload + execute. Use file cache only for local iteration.
Canonical large-crossover row (memory, quads-only, libsqlStore only)
From the PR #85 comment above: preload ~20 / ~63 / ~130 / ~279 s; execute 38 ms / 232 ms @ 100k through 373 ms / 2.2 s @ 1M.
Earlier table in this thread (hydrate+N3 @ 1M, inline FTS, both backends) is historical — do not compare to current harness.
|
#�#� �P�o�s�t�-�m�e�r�g�e� �p�r�e�l�o�a�d� �c�h�e�c�k�:� �b�a�t�c�h�e�d� �q�u�a�d� �I�N�S�E�R�T�s� �(�P�R� �#�8�7�)� |
SPARQL hexastore performance (libsql vs denokv) — selective onlyCaptured 2026-05-27 on Windows x86_64, Deno 2.8.0. Standard scales Methodology
Unbound Preload (import + engine wiring)
Time to first useful SPARQL queryThe execute table below is post-preload only ( For end-to-end time to first useful SPARQL query, LibSQL wins unless Execute (selective SPARQL avg)
Commandsdeno task bench:hexastore-perf-libsql
deno task bench:hexastore-perf-denokv |
Uh oh!
There was an error while loading. Please reload this page.
Follow-up to Discussion #45 with fair-timing SPARQL crossover numbers after PR #67 (benchmark preload + chunked hydration). Scale-out product work continues in #68.
Methodology
sparqlEngine.execute()only (no per-iteration import).:memory:LibSQL, synthetic quads (benchmarks/synthetic-data.ts).Results (avg, post-preload)
Source:
benchmarks/README.md(post-preload baseline section).Interpretation
<urn:entity:0> ?p ?o): hydrate+N3 stays faster at these scales when the graph is already in memory (typical warm server / container with a reusedStore).createLibsqlClient): use when you must not hydrate the full corpus into N3 (memory bounds, millions of quads) — see #68.?s ?p ?o+LIMIT 100): hydrate+N3 still wins here; libsqlStore pays wide SQL + row materialization. Avoid unbound triple patterns at production scale.createLibsqlClientvscreateLibsqlN3Client(and optionalstore?: Storefor warm boots) at integration time instead of runtime heuristics.Reproduce
If you maintain crossover docs, consider linking this discussion as the canonical post-preload write-up (README cross-link welcome).
All reactions