DuckDB: In-Process OLAP Analytics on Parquet, CSV, and S3

Photo by Carlos Muza on Unsplash
DuckDB is an in-process OLAP database built for analytical queries like scans, aggregates, and joins over large datasets. It is often called SQLite for analytics because it runs inside your application with no server. It excels at querying Parquet, CSV, and JSON files directly and at powering single-node analytics in notebooks, backends, or the browser.
Both are embedded, serverless databases, but they target opposite workloads. SQLite is row-oriented and optimized for transactional app-local reads and writes. DuckDB is columnar and vectorized, optimized for analytical scans and aggregates over many rows. DuckDB can also query Parquet, CSV, JSON, and S3 files directly, which SQLite cannot do.
Yes. You can put a .parquet file straight into a SQL FROM clause, and DuckDB reads it in place with no import step. It applies projection pushdown to read only the columns you select and filter pushdown to skip row groups outside your WHERE clause, so a filtered query often touches only a fraction of the file.
DuckDB released its stable 1.0.0 version, codenamed Snow Duck, on June 3, 2024. The release focused on stability rather than new features, backed by a storage format that is backward compatible since v0.10.0. This means databases created with 1.0.0 remain readable by future versions, making it safe to build on.
Use DuckDB for analytical queries on a single machine when standing up infrastructure would be overkill. Keep Postgres when you need many concurrent writers and transactional integrity behind an app. Choose a cloud warehouse like BigQuery or Snowflake when analytics must scale across many nodes or be shared org-wide. DuckDB fills the gap in between.

Photo by Carlos Muza on Unsplash
Key Takeaway
DuckDB is an in-process OLAP database, often called SQLite for analytics. Its columnar vectorized engine runs SQL directly over Parquet, CSV, and JSON files, local or on S3, with zero ETL. It embeds in Python, Node, or the browser via WebAssembly, reached a stable 1.0 in June 2024, and shines for single-node analytics where a full warehouse is overkill.
Most analytics questions I get asked are one-off and awkward: a stakeholder drops a folder of Parquet exports and wants a grouped total by Friday. The traditional answer is to spin up a warehouse, define a schema, write an ingestion job, and load the data before you can run a single aggregate. For a question that takes ten seconds of SQL to answer, that overhead is absurd.
DuckDB is the tool that removed that overhead for me. It is an in-process analytical database released under the permissive MIT License, and the easiest way to describe it is SQLite for analytics: no server, no daemon, no network hop. You add one library to your process and you have a full SQL engine that reads columnar data at speed. It hit a stable 1.0.0 on June 3, 2024, so its storage format and API are now safe to build on.
Two design choices make DuckDB fast for the queries analysts actually run. First, it is columnar: it stores and reads data one column at a time, so a query touching three columns of a fifty-column table only pays for those three. Row-store databases like SQLite or a default Postgres table read the whole row off disk even when you asked for a fraction of it. For scans and aggregates over big tables, that difference is enormous.
Second, DuckDB uses a vectorized execution engine. Instead of pushing rows through the query operators one at a time, it processes them in batches of a few thousand values that fit in CPU cache, which slashes the per-row interpreter overhead that dominates traditional engines. Being in-process matters too: the query runs inside your Python or Node process, so results never cross a socket. There is no server to install, update, or maintain.
The feature that changed how I work is that DuckDB queries files where they sit. A file ending in .parquet can go straight into the FROM clause, and read_csv or read_json_auto do the same for other formats, inferring types automatically. Glob patterns treat a whole folder of files as one table. There is no CREATE TABLE, no COPY, no loading step at all — the file is the table.
-- No load step, no schema, no server. Just point SQL at the file.
SELECT country, COUNT(*) AS orders, SUM(amount) AS revenue
FROM 'sales/2026-*.parquet'
WHERE status = 'paid'
GROUP BY country
ORDER BY revenue DESC
LIMIT 10;
-- CSV and JSON work the same way, with types inferred automatically.
SELECT * FROM read_csv('events.csv');
SELECT * FROM read_json_auto('logs.json');When you scan a Parquet file, DuckDB applies projection pushdown to read only the columns your query names, and filter pushdown to skip row groups whose min/max statistics fall outside your WHERE clause. On a well-partitioned dataset this means a filtered query reads a small fraction of the file off disk, which is why querying Parquet directly is often faster than loading it first.
The httpfs extension extends this model to remote storage. After a one-line INSTALL and LOAD, you can point read_parquet at an https:// URL or an s3:// path and DuckDB streams only the byte ranges it needs rather than downloading the whole object. Credentials live in a secret created with CREATE SECRET, or you can lean on the standard AWS credential chain. This turns an S3 data lake into something you can query from a laptop with plain SQL.
INSTALL httpfs;
LOAD httpfs;
-- Store credentials once as a secret (or use the AWS credential chain).
CREATE SECRET (
TYPE s3,
KEY_ID 'AKIAIOSFODNN7EXAMPLE',
SECRET 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
REGION 'us-east-1'
);
-- Query a remote Parquet file directly; only the needed bytes are fetched.
SELECT device, AVG(latency_ms) AS p_latency
FROM read_parquet('s3://my-bucket/metrics/*.parquet')
GROUP BY device;Because DuckDB compiles down to a header and one implementation file with no external dependencies, it drops into almost any runtime. Python and R clients make it the default for notebook analytics; the official Node.js client ships as the @duckdb/node-api package with native Promise support, letting a backend run analytical SQL without a separate database service. The same engine powers command-line and JVM clients too.
The client that surprises people most is DuckDB-Wasm. DuckDB has been compiled to WebAssembly, so the full engine runs inside any browser on any device — no backend required. A dashboard can fetch a Parquet file and let the user pivot, filter, and aggregate entirely client-side, which is how tools like interactive data explorers ship analytics with zero server cost. The trade-offs are a single thread and a memory ceiling around four gigabytes.
DuckDB is built for one writer at a time. A single process can open a database file read-write, and while other processes can attach read-only, it is not designed for many concurrent applications writing to the same file the way Postgres handles hundreds of connections. Treat DuckDB as an embedded analytics engine, not a shared transactional backend for a multi-user web app.
It is easy to mistake DuckDB for a drop-in replacement for the databases you already run. It is not — it is aimed at a different workload. This table lays out where each one fits so you pick by the shape of the query, not by habit.
| Aspect | DuckDB | SQLite | Postgres |
|---|---|---|---|
| Storage model | Columnar | Row-oriented | Row-oriented |
| Built for | OLAP analytics | Embedded OLTP | General OLTP |
| Query files directly | Yes — Parquet, CSV, JSON, S3 | No | Via extensions only |
| Concurrent writers | Single writer | Single writer | Many concurrent writers |
| Best fit | Large scans and aggregates | App-local key lookups | Multi-user transactional apps |
My rule of thumb is to match the tool to the workload rather than the data size. DuckDB earns its place when the query is analytical and the deployment is single-node. Reach for it when:
Stick with Postgres when you need many concurrent writers, transactional integrity, and a long-lived shared database behind an application. Reach for a cloud warehouse like BigQuery or Snowflake when analytics must scale across many nodes or be shared by a whole organization at once. DuckDB fills the wide gap in between, and for the everyday analytics I run, that gap is where most of the work actually lives.