Skip to content
Field Notes
Go back

DuckDB: The Embedded Analytical Database

A look into DuckDB — the “SQLite for analytics” — covering architecture, use cases, APIs, performance, and how it compares to SQLite, ClickHouse, and Polars.


TL;DR

DuckDB is an embedded, serverless OLAP database that runs inside your application process. It uses columnar storage and vectorized execution to deliver 10-50x faster analytical queries than SQLite, while being able to query CSV, Parquet, and JSON files directly without importing them. It’s ideal for local data analysis, embedded analytics, and data science workflows. SQLite remains the better choice for transactional workloads (many small reads/writes).


Context

Several services are emerging that use DuckDB to gather text data, parse it, and make it queryable for local users via SQL-like queries. This investigation explores what DuckDB actually is, where it fits in the database landscape, and when it’s the right tool.


What Is DuckDB

DuckDB is an in-process analytical database created at CWI Amsterdam (the same research lab behind MonetDB). First released in 2019, it has grown rapidly in adoption among data engineers and analysts.

Core design principles:

File Querying

One of DuckDB’s standout features is querying external files directly:

-- Query a CSV without importing
SELECT department, AVG(salary)
FROM 'employees.csv'
GROUP BY department;

-- Query Parquet files with glob patterns
SELECT * FROM 'logs/**/*.parquet'
WHERE timestamp > '2026-01-01';

-- Query JSON
SELECT json_extract(data, '$.user.name') as name
FROM 'events.json';

No schema definition, no import step — just point and query.


Use Cases

Use CaseDescription
Local data analysisSQL on CSV/Parquet/JSON files without any setup or import
Data science workflowsReplaces Pandas for SQL-heavy analysis; 3-5x faster joins, lower memory
Embedded analyticsDashboards and reporting inside applications, no DB server needed
ETL and data wranglingTransform data locally before pushing to a data warehouse
Log and text parsingSQL queries over semi-structured text and log files
Edge and offline analyticsRuns anywhere with zero infrastructure requirements
Text data pipelinesGather text data, parse it, and expose it for SQL-based analysis

API and Language Bindings

DuckDB provides official bindings for: Python, R, Java (JDBC), Node.js, C/C++, WebAssembly, Julia, Go, ODBC, and a CLI.

import duckdb

# Query files directly
result = duckdb.sql("SELECT * FROM 'data.csv' WHERE status = 'active'")

# Get results as Pandas DataFrame
df = result.df()

# Or as Polars DataFrame
polar_df = result.pl()

# Query an existing Pandas DataFrame with SQL
import pandas as pd
users = pd.read_csv('users.csv')
duckdb.sql("SELECT city, COUNT(*) FROM users GROUP BY city ORDER BY 2 DESC")

Results can be fetched as Pandas DataFrames, Polars DataFrames, NumPy arrays, or Apache Arrow tables.

Node.js

Follows the familiar sqlite3 async API pattern:

const duckdb = require('duckdb');
const db = new duckdb.Database(':memory:');
db.all("SELECT * FROM 'data.parquet' LIMIT 10", (err, rows) => {
  console.log(rows);
});

Java

Standard JDBC interface — drop-in compatible with existing Java database tooling.


Performance Characteristics

DuckDB vs Row-Based Databases (SQLite)

Why the Speed Difference

  1. Columnar storage — only reads columns needed for the query
  2. Vectorized execution — processes thousands of values per CPU operation
  3. Parallel execution — uses all available CPU cores
  4. Late materialization — delays constructing result rows until necessary
  5. Morsel-driven parallelism — adaptive work distribution across cores

Comparison with Alternatives

DuckDB vs SQLite

DimensionDuckDBSQLite
Optimized forAnalytical queries (OLAP)Transactional operations (OLTP)
Storage modelColumnarRow-based
Best workloadAggregations, scans, complex joinsMany small reads/writes
ConcurrencySingle writer, few readersMany concurrent readers/writers
File queryingNative (CSV, Parquet, JSON)Not supported
Scale sweet spotMB to ~100GBKB to ~10GB
MaturityYoung but rapidly maturingBattle-tested for decades

Bottom line: They solve different problems. SQLite for transactional apps (mobile, web, config). DuckDB for analytical workloads.

DuckDB vs ClickHouse

DimensionDuckDBClickHouse
DeploymentEmbedded, no serverServer-based (or clickhouse-local)
ScaleSingle machine, up to ~100GBDistributed, TB to PB
Concurrent usersSingle-user or fewMany concurrent users
Real-time ingestionNot designed for itExcellent
Setup complexityZeroModerate to high
Normalized dataHandles wellPrefers denormalized

Bottom line: ClickHouse for production analytics at scale with many users. DuckDB for local/embedded analytics without infrastructure.

DuckDB vs Polars

DimensionDuckDBPolars
InterfaceSQLMethod chaining (DataFrame API)
TypeDatabase engineDataFrame library
File queryingNative SQL over filesNative via scan functions
Complex SQL joinsExcellentGood, but SQL is more natural
DataFrame transformsPossible but less ergonomicExcellent with lazy execution
InteropQueries Polars DataFramesCan use DuckDB as backend

Bottom line: Use DuckDB if you think in SQL. Use Polars if you prefer method chaining. They complement each other well.


Conclusion

DuckDB fills a clear gap: embedded, zero-setup analytical queries on local data. For the specific pattern of “gather text data, parse it, make it SQL-queryable for local users,” DuckDB is a strong fit — its ability to query files directly, combined with rich Python/Node bindings, makes it the natural choice over SQLite (which is built for transactional workloads) or ClickHouse (which requires server infrastructure).

Key takeaway: DuckDB is not a replacement for SQLite — they’re complementary. The recommended pattern for applications that need both is SQLite for transactional data and DuckDB for analytical queries.


References


Share this post on:

Previous Post
Docker Volumes and Bind Mounts
Next Post
Choosing a Container Orchestrator: A Decision Framework