perf: AST-free preBaseQuery generation (node + browser, skip DuckDB round-trip)#294
Open
zaidjan-devrev wants to merge 5 commits into
Open
perf: AST-free preBaseQuery generation (node + browser, skip DuckDB round-trip)#294zaidjan-devrev wants to merge 5 commits into
zaidjan-devrev wants to merge 5 commits into
Conversation
… preBaseQuery Optimizes the cube-to-SQL generation pipeline by cutting DuckDB round-trips that exist purely to convert AST <-> SQL text. Changes: - Batch filter-param deserialization: getFilterParamsSQL issued one DuckDB round-trip per FILTER_PARAMS placeholder; now packs all ASTs into one SELECT with N aliased json_deserialize_sql columns (N->1 round-trips). Early-returns when no filter params. ~3x on filter-heavy schemas. - AST-free preBaseQuery builder: for queries without a projection-filter WHERE clause, build the outer skeleton (GROUP BY / ORDER BY / LIMIT) directly in TS, skipping the cubeToDuckdbAST -> json_deserialize_sql round-trip entirely. Falls back to the AST round-trip when a real WHERE clause is present. ~39% faster on wide-schema/no-filter inputs (dim_issue: 0.379ms -> 0.232ms). - Parallelize independent round-trips in cube-to-sql (node + browser) via Promise.all: preBaseQuery build and PROJECTION_FILTER param resolution. - Tighten getFilterParamsSQL getQueryOutput type (was any). Output is byte-identical: verified by a differential parity test against the live DuckDB round-trip across order/group-by/limit/alias-quoting/nested-empty cases plus the dim_issue production shape. Tests: meerkat-core 550 pass, meerkat-node 913 pass. No regressions. work-item: TBD
The sample input this PR targets (wide dim_issue schema, empty filters) has no FILTER_PARAMS placeholders, so the batching optimization never fires for it. Revert getFilterParamsSQL and ast-deserializer to main and remove the batch-focused perf spec, keeping this PR scoped to the AST-free preBaseQuery fast-path (and the Promise.all parallelization) that actually helps the target input. Batching can return as its own PR for filter-heavy schemas. work-item: TBD
…chmark - Wire the AST-free preBaseQuery fast-path into meerkat-browser cubeQueryToSQL, mirroring meerkat-node. In the browser each cubeToDuckdbAST -> json_deserialize_sql round-trip is a postMessage hop to the duckdb-wasm Web Worker, so skipping it matters more than in node. - Add a browser generation benchmark route (benchmarking/gen-benchmark) that runs the real cubeQueryToSQL against a live duckdb-wasm worker connection, driven by puppeteer. Measures worker hits, sequential latency, parallel contention, and head-of-line blocking behind a heavy data query. - Fix type-only re-exports of `Graph` (export type) so the joins barrel resolves under Vite/esbuild's per-file transpile (isolatedModules). Pre-existing latent issue surfaced by importing meerkat-core into the Vite dev app. Measured (duckdb-wasm worker, dim_issue shape, empty filters): - Fast path issues 0 worker round-trips for generation vs 1 on baseline (generation now fully main-thread). - Sequential full gen: ~0.42ms -> ~0.21ms (~1.9x). - Generation latency while the worker is busy with a heavy data query: ~12ms (baseline, queued behind the query) -> ~0.22ms (fast path, main thread) — ~55x. work-item: TBD
…ention Rework the browser generation benchmark to sweep N concurrent generations (1..100) fired WHILE a heavy data query occupies the single duckdb-wasm worker, median of 5 per point. This is the realistic contention scenario: baseline generations queue behind the data query (and each other) on the worker; the fast path runs on the main thread. work-item: TBD
work-item: TBD
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Speeds up cube-to-SQL generation by removing a DuckDB round-trip that exists
purely to convert an AST into a SQL string, in both meerkat-node and
meerkat-browser.
The
cubeToDuckdbAST → json_deserialize_sqlround-trip only produces the outerquery skeleton (
SELECT * FROM REPLACE_BASE_TABLE [WHERE] [GROUP BY] [ORDER BY] [LIMIT]); the SELECT-list / FROM parts are string-replaced downstream anyway.For queries whose filters contribute no WHERE clause, the skeleton is now
built directly in TypeScript (
buildPreBaseQuerySync), skipping the round-trip.Queries with a real WHERE clause fall back to the AST path, so the filter
operator grammar is untouched.
Also parallelizes the two independent steps in cube-to-sql (node + browser) via
Promise.all: the preBaseQuery build and thePROJECTION_FILTERparamresolution.
Impact — node (in-memory DuckDB)
Isolated preBaseQuery step, same machine, back to back: 0.140ms → 0.004ms
(~31x). End-to-end
cubeQueryToSQLon thedim_issueshape (375 dims, 14projected, empty filters): ~0.38ms → ~0.24ms (~37%).
Impact — browser (duckdb-wasm Web Worker)
Measured with the real
cubeQueryToSQLagainst a live duckdb-wasm worker,driven by puppeteer via a new
benchmarking/gen-benchmarkroute. In the browsereach round-trip is a postMessage hop to the single-threaded worker.
Structural change: the fast path issues 0 worker round-trips for
generation (vs 1 on baseline) — generation is now entirely main-thread.
Sequential (idle worker): ~0.42ms → ~0.21ms (~1.7x). This is the win when
the worker is free; local wasm round-trips are cheap.
Under worker contention — the real win. Fire N generations concurrently
while a heavy data query (~10ms, 200k-row self-join) occupies the worker.
Median of 5, stable across runs:
How to read it: the fast path scales linearly with N (pure main-thread CPU, runs
concurrently with the busy worker). Baseline is pinned to the worker's ~10ms
queue floor — every generation's round-trip waits behind the in-flight data
query on the single-threaded worker (head-of-line blocking), then behind each
other. The multiplier depends on how heavy the concurrent query is and how often
generation overlaps a busy worker; the durable fact is that the fast path is
immune to worker contention (0 hits).
Correctness
Output is byte-identical. A differential parity test asserts
buildPreBaseQuerySyncmatches the live DuckDB round-trip across: order-only,group-by + order, limit/offset, custom-alias quoting (
"Weird Name"),multi-order-key, nested-empty filter groups, and the
dim_issueproductionshape. The gate returns
falsewhen a real filter is present.Also in this PR
Graph(export type) in the joins barrel so itresolves under Vite/esbuild per-file transpile. Pre-existing latent issue
surfaced by importing meerkat-core into the Vite dev app.
Scope note
Batch filter-param deserialization was removed from this PR — the target
input has no FILTER_PARAMS placeholders, so it never fired. It can return as its
own PR for filter-heavy schemas.
Test plan
nx test meerkat-core— 550 passnx test meerkat-node— 909 pass (incl. POC parity + perf specs)nx test meerkat-browser— passnx build meerkat-core/meerkat-browser/meerkat-nodeDevRev
work-item: TBD — no issue ID in branch and the DevRev MCP was unavailable in
this session; please link/create the tracking issue before merge.
Follow-ups (not in this PR)
(also removes the worker hop for filter-heavy browser queries)
🤖 Generated with Claude Code