DEV Community

Mohammed Arshad Ansari
Mohammed Arshad Ansari

Posted on Originally published at hikmahtechnologies.com

Local-first analytics in practice: DuckDB, Parquet, and killing the round-trip

In the previous post I argued that a lot of analytics infrastructure exists to solve laptop-sized problems. Here's what the alternative actually looks like in code.

The setup: data as files, not endpoints

The local-first pattern starts by treating your dataset as a file — ideally Parquet, a columnar format that's compact, typed, and splittable. You can sit it in object storage, ship it in a release, or cache it on disk. No connection string, no credentials rotation, no warehouse to keep warm.

Then you point an in-process engine at it. DuckDB reads Parquet natively and runs the query where you are.

import duckdb

# No server. No connection pool. Just a query against a file.
con = duckdb.connect()

result = con.execute("""
    SELECT
        symbol,
        date_trunc('month', trade_date) AS month,
        avg(close) AS avg_close,
        count(*) AS days
    FROM 'prices/*.parquet'      -- glob straight over partitioned files
    WHERE trade_date >= '2024-01-01'
    GROUP BY 1, 2
    ORDER BY 1, 2
""").df()                         # straight into a pandas DataFrame

print(result.head())
Enter fullscreen mode Exit fullscreen mode

That query reads only the columns it needs (columnar), only the row groups that match the predicate (pushdown), and never leaves the machine. On a few gigabytes of price data it returns in milliseconds — on the same laptop you're reading this on.

It runs in the browser too

The part that surprises people: the same engine compiles to WebAssembly. With duckdb-wasm you can ship a Parquet file and a query to the browser and render an interactive analytics view with no backend at all.

import * as duckdb from '@duckdb/duckdb-wasm'

const db = await initDuckDB()            // wasm engine in the tab
const con = await db.connect()
await con.query(`
  SELECT regime, count(*) AS n
  FROM 'https://cdn.example.com/regimes.parquet'
  GROUP BY regime
`)
Enter fullscreen mode Exit fullscreen mode

The user's machine does the work. Your "API" is a static file on a CDN. Your hosting bill is whatever a CDN charges to serve a few megabytes — and there's no query endpoint to attack, rate-limit, or scale.


This is the first part. The full post — including the rest of the working details — is on my site: Local-first analytics in practice: DuckDB, Parquet, and killing the round-trip

Top comments (0)