Skip to content

Performance and Benchmarks

Karthik P edited this page Oct 25, 2026 · 11 revisions

Performance & Benchmarks

Performance is a core feature of Blinkql, so it's treated like one: budgeted, measured, tested in CI and documented in public.

Philosophy

  • Measure, don't guess. No optimization is merged without before/after numbers.
  • Baseline first. Milestone 1 deliberately ships a straightforward implementation so every later improvement has an honest "before."
  • Reproducible or it didn't happen. Datasets are generated deterministically, the method is fixed, and raw results are committed to the repository.
  • Report trade-offs. Every write-up states what got worse (memory, complexity, bundle size), not only what got better.

Performance budgets

Initial targets, to be revised once the baseline is measured.

Metric Target Measured with
Largest Contentful Paint (cold load) ≤ 2.5 s Lighthouse CI
Interaction to Next Paint ≤ 200 ms web-vitals during end-to-end runs
Cumulative Layout Shift ≤ 0.1 Lighthouse CI
Engine ready, cold (nothing cached) ≤ 4 s User Timing marks
Engine ready, warm (cached) ≤ 1 s User Timing marks
Main-thread long tasks while a query runs none over 50 ms Long Tasks API (PerformanceObserver)
First rows rendered after the engine returns ≤ 100 ms User Timing marks
Scrolling a 1M-row result sustained 60 fps requestAnimationFrame frame-time sampling
Initial JavaScript, excluding WebAssembly ≤ 250 KB gzipped size-limit

The LCP, INP and CLS targets match Google's "good" Core Web Vitals thresholds.

Reference environments

Results are always reported with the environment they came from.

Profile Hardware OS Browser
Reference laptop Lenovo ThinkPad (20LS): Intel Core i5-8250U (4 cores / 8 threads), 24 GB Windows 11 Pro Chromium (Playwright's pinned build)
Throttled (low-end simulation) Reference laptop with 4× CPU slowdown via the Chrome DevTools Protocol
Community runs Submitted by contributors; see below

Datasets

A deterministic generator (bench/datasets) creates synthetic manufacturing telemetry from a fixed seed, so everyone benchmarks identical data.

Size Rows Formats Shapes
S 100,000 CSV, Parquet narrow (8 columns), wide (50 columns)
M 1,000,000 CSV, Parquet narrow, wide
L 10,000,000 Parquet narrow

Public datasets such as the NYC TLC trip records (Parquet) may be added for comparison with other tools.

What gets measured

Stage Metric
Cold start Time to app shell, time to engine ready, bytes transferred
Ingest Time to register and first-query a file, by size and format
Query Engine execution time for a fixed scenario set (scan, filter, group-by, join, window)
Transfer Time from engine result to Arrow table available on the main thread
Render Time to first visible rows; frame times while scrolling
Memory Peak JS heap and WebAssembly memory (performance.measureUserAgentSpecificMemory in Chromium under cross-origin isolation, otherwise DevTools heap snapshots)
Bundle Gzipped size of each entry chunk

Method

  1. Each run states whether it is cold (fresh browser profile) or warm (engine cached).
  2. 3 warm-up iterations, then 10 measured iterations per scenario.
  3. Report median and p95; raw samples are stored alongside.
  4. Browser versions are pinned through Playwright's bundled browsers.
  5. Results are saved as JSON in bench/results/ with the commit SHA, environment and timestamp.
  6. CI runs a small benchmark smoke test on every pull request and fails if a budget regresses beyond a tolerance.

Optimization log

Each experiment gets an ID, a hypothesis and a published result.

ID Experiment Hypothesis Status
P-01 Baseline: results converted to row objects, stored in React state, rendered as a plain table Establishes the "before" ✅ Published
P-02 Keep results as Arrow; read cell values lazily from columnar vectors Removes conversion time and cuts memory ✅ Published
P-03 Move results out of React state; components subscribe to slices Eliminates large re-renders ✅ Published
P-04 Virtualized grid (TanStack Virtual) Constant render cost regardless of row count ✅ Published
P-05 Stream record batches; render the first rows before the full result arrives Faster time to first rows on large results ✅ Published
P-06 Query cancellation and superseded-query handling No wasted work when users re-run quickly ✅ Published
P-07 Lazy-load and cache the engine (self-hosted, immutable caching, service worker) Faster cold and warm start 🔜 Planned
P-08 Single-threaded vs multithreaded engine bundle Faster large scans and aggregations on multi-core machines 🔜 Planned
P-09 Convert CSV to Parquet on import (stored in OPFS) Much faster repeat queries ✅ Published
P-10 SQL-side downsampling for charts vs client-side Smooth charts on million-point series ✅ Published
P-11 Canvas-rendered grid vs DOM virtualization Higher scroll frame rate on wide tables 🔜 Planned
P-12 Code-split the editor and chart libraries Smaller initial bundle, faster first paint 🔜 Planned

Write-up template

Every completed experiment gets its own wiki page using this structure:

# P-XX: <title>

**Context:** What was slow, and how we noticed.
**Hypothesis:** What we expected to improve, and why.
**Change:** What was implemented (link to the PR).
**Method:** Dataset, scenario, environment, cold or warm.

| Metric | Before (median / p95) | After (median / p95) | Change |
|---|---|---|---|

**Trade-offs:** What got worse or more complex.
**Evidence:** Links to traces, profiler screenshots, raw results.
**Conclusion:** Keep, revert or iterate?

Run the benchmarks yourself

pnpm bench:data   # generate the deterministic datasets
pnpm bench        # run the benchmark suite (results in bench/results/)

(Scripts become available with Milestone 0.)

Community results are very welcome. Different CPUs, browsers and operating systems tell us far more than one machine can. Open a pull request adding your JSON results to bench/community-results/, along with your hardware details.

Clone this wiki locally