sparrowflight · demo · 100 charts
100 charts. One Flight call.
One hundred weekly petroleum series — ~140,000 rows — in a single Arrow Flight SQL call. Stream it (charts draw as batches arrive) or draw at the end. The third button is the fairest fight we can offer JSON: one REST call, gzipped, same server, same lake — the best the conventional way gets. The numbers are measured in your browser, nothing faked.
GizmoSQL C++ Sparrow Python engine
the page loads no data until you click — the call happens in your browser
the scoreboard — same 140,709 rows every row · measured 2026-07-18 · the buttons above rerun it in your browser
approachfirst chartall 100on the wirethe story
Arrow Flight — Sparrow, 1-RTT + lz40.16 s0.27 s1.0 MB Arrow (lz4)fastest of all, both metrics — the ticket skips a round trip and the stream ships compressed
Arrow Flight — stream (GizmoSQL, 2-RTT)0.23 s0.39 s3.8 MB ArrowC++ engine, standard 2-RTT, uncompressed — still quick, but a round trip and 3.8× the wire behind
REST + JSON — one gzipped call~0.6 s0.90 s0.8 MB gz → 9.9 MB JSONnow the bigger wire once Arrow is compressed — and half its time is one main-thread JSON.parse stall; under load the factory caps at 3.1 req/s vs Arrow's 13.8
ROAPI — Utf8View (benched)21 s81 s290.3 MBuncompacted View buffers: 76× the wire for the same data
What the 16-user button shows, explained. Run it and watch the medians: JSON's median user is often served faster than Arrow's — and its slowest user waits far longer. That's the signature of a serializing factory. A JSON API spends real CPU manufacturing each response, so concurrent requests form a queue: the first customers are served quickly, and every later user inherits the whole queue in front of them. Medians look fine; tails grow with every added user. Arrow's columnar serialization is cheap enough that the same box runs all sixteen nearly in parallel — everyone waits about the same, and the slowest user is barely worse than the median. Nobody's dashboard is judged by its median load; it's judged by the person staring at a spinner. That's why the number to read is the slowest user — and why the gap between the two columns widens with every user you add.
JSON used to have two things going for it here: one round trip and a small wire. Sparrow now has both. Flight SQL reads are two RPCs by design — submit the query, get a ticket, then stream it — because the ticket can point at several endpoints on several nodes: that indirection is what lets a big result scatter across a cluster. REST carries no such concept, so it wins the tiny pulls against the standard flow. But this server accepts client-constructed tickets, so query() skips the ask-step and streams in one round trip — and it now ships that stream lz4-compressed (sparrowJS decodes it in the browser via arrow-js 21.2 + lz4js). The result: the 100-chart pull dropped from ~0.93 s to 0.27 s and its wire from 3.8 MB to 1.0 MB — now in the same ballpark as JSON's 0.8 MB gzip, but with no JSON.parse stall on the other end (Arrow hands typed arrays, not two million object properties). One round trip and a small wire were JSON's last two advantages here; compression takes the wire.
ROAPI (the third engine on the 10-chart demo) sits this one out: DataFusion currently serializes this query's Utf8View string columns with uncompacted shared buffers — ~290 MB on the wire for these 3.8 MB of data, measured. The charts decode correctly (sparrowJS transcodes View types client-side), but a 76× wire bill is a stall by any name. Details in the Flight reference.
the code this page runs: npm install @sparrowflight/js · source · the 10-chart demo with the REST race · connect to this server yourself · demo:demo is yours to use