DuckDB is an in-process analytical database optimized for OLAP workloads. Install it via the [duckdb] extra and it is ready for local development, testing, and small-to-medium scale benchmarks.
- Zero configuration - No server setup required
- In-process execution - Embedded in Python process
- Columnar vectorized - Optimized for analytics
- SQLite-compatible - File-based persistence
- Parallel execution - Multi-threaded queries
DuckDB is available via the [duckdb] extra and recommended for most local workloads:
- No server setup - Works with no external dependencies after install
- Native file format support - Parquet and CSV support
- Local execution - No network or service variability
- Free and open source - No licensing costs
- Suitable for development - Quick iteration on benchmark workflows
Choose the platform that best fits your specific requirements. See the Platform Selection Guide for help selecting the right platform for your use case.
uv add benchbox --extra duckdb
pip install "benchbox[duckdb]"The first command installs BenchBox with the DuckDB extra using uv, and the second does the same with pip.
No configuration needed - DuckDB works immediately:
benchbox run --platform duckdb --benchmark tpch --scale 0.01The CLI stores the database file under the runs root, in databases/<benchmark>_sf<scale>/, and names it from the benchmark, scale factor, and tuning settings. Set BENCHBOX_OUTPUT_DIR to move the runs root:
BENCHBOX_OUTPUT_DIR=/data/benchbox \
benchbox run --platform duckdb --benchmark tpch --scale 1.0To choose the exact file, or to use an in-memory database, pass database_path to DuckDBAdapter in the Python API.
benchbox run --platform duckdb --benchmark tpch --scale 1.0 \
--platform-option threads=8 \
--platform-option memory_limit=4GB| Option | Default | Description |
|---|---|---|
threads |
(auto) | Number of threads |
memory_limit |
(auto) | Maximum memory usage |
temp_directory |
(auto) | Temp file location |
driver_version |
(latest) | Pin the DuckDB Python package version (e.g. 1.2.0) |
driver_auto_install |
false | Auto-install the requested driver version via uv if missing |
max_temp_directory_size and progress_bar are Python/config-file settings, not --platform-option names: pass them to DuckDBAdapter (or from_config) directly, e.g. DuckDBAdapter(database_path=":memory:", progress_bar=True).
benchbox run --platform duckdb --benchmark tpch \
--platform-option driver_version=1.4.3 \
--platform-option driver_auto_install=trueSee {ref}driver-version-management for the full guide, including why uv run may
revert a manually-installed version and how to work around it.
benchbox run --platform duckdb --benchmark tpch --scale 0.01
benchbox run --platform duckdb --benchmark tpch --scale 0.1 \
--queries Q1,Q6,Q17| Scale Factor | Data Size | Use Case |
|---|---|---|
| 0.01 | ~10 MB | Unit testing, CI/CD |
| 0.1 | ~100 MB | Integration testing |
| 1.0 | ~1 GB | Standard benchmarking |
| 10.0 | ~10 GB | Performance testing |
Note: Execution times vary based on hardware, query complexity, and configuration. Run benchmarks to establish baselines for your environment.
benchbox run --platform duckdb --benchmark tpch --scale 1.0 \
--tuning tunedThe tuned mode applies optimizations such as indexes.
from benchbox import TPCH
from benchbox.platforms.duckdb import DuckDBAdapter
adapter = DuckDBAdapter()
adapter = DuckDBAdapter(database_path="./benchmarks.duckdb")
benchmark = TPCH(scale_factor=0.1)
benchmark.generate_data()
adapter.load_benchmark(benchmark)
results = adapter.run_benchmark(benchmark)
print(f"Total runtime: {results.total_time:.2f}s")The first DuckDBAdapter() call creates an in-memory database. The second creates a persistent one at the given path. Use one or the other.
from benchbox import TPCH
from benchbox.platforms.duckdb import DuckDBAdapter
scales = [0.01, 0.1, 1.0]
for sf in scales:
adapter = DuckDBAdapter()
benchmark = TPCH(scale_factor=sf)
benchmark.generate_data()
adapter.load_benchmark(benchmark)
results = adapter.run_benchmark(benchmark)
print(f"SF {sf}: {results.total_time:.2f}s")benchbox run --platform duckdb --benchmark tpch \
--platform-option threads=4This controls parallelism.
benchbox run --platform duckdb --benchmark tpch --scale 10.0 \
--platform-option memory_limit=8GBThis limits memory usage.
For large datasets that exceed memory:
benchbox run --platform duckdb --benchmark tpch --scale 10.0 \
--platform-option temp_directory=/fast/ssd/tmpDuckDB supports fast data loading from multiple formats:
benchbox run --platform duckdb --benchmark tpch --scale 1.0Data is generated as .tbl flat files (zstd-compressed by default) and loaded automatically.
benchbox datagen --benchmark tpch --scale 1.0 --output ./data/tpch
benchbox run --platform duckdb --benchmark tpch --scale 1.0 --output ./data/tpchGenerate the data separately, then run the benchmark with the pre-generated data.
For testing queries without loading:
import duckdb
conn = duckdb.connect()
result = conn.execute("""
SELECT count(*) FROM read_parquet('lineitem/*.parquet')
""").fetchone()The query reads the Parquet files directly.
Begin with SF 0.01 to validate your workflow:
benchbox run --platform duckdb --benchmark tpch --scale 0.01An in-memory database avoids disk I/O. The CLI always writes a database file, so use the Python API for in-memory runs:
from benchbox.platforms.duckdb import DuckDBAdapter
adapter = DuckDBAdapter(database_path=":memory:")When iterating on queries, load the data once and reuse the database file:
benchbox run --platform duckdb --benchmark tpch --scale 1.0 \
--phases generate,load
benchbox run --platform duckdb --benchmark tpch --scale 1.0 \
--phases powerThe first command loads the data once. The second runs the queries as many times as you need. Both use the same database file because the CLI names it from the benchmark, scale factor, and tuning settings.
Test at similar scale to production platforms:
benchbox run --platform duckdb --benchmark tpch --scale 10.0For example, if you plan to run SF 100 on Snowflake, test at SF 1-10 on DuckDB.
benchbox run --platform duckdb --benchmark tpch --scale 10.0 \
--platform-option memory_limit=16GB \
--platform-option temp_directory=/tmp/duckdbIncrease the memory limit or set a temp directory so DuckDB can spill to disk.
import os
from benchbox.platforms.duckdb import DuckDBAdapter
adapter = DuckDBAdapter(thread_limit=os.cpu_count())This sets the thread count to the number of available cores.
The progress bar is not a --platform-option; enable it through the Python API:
from benchbox.platforms.duckdb import DuckDBAdapter
adapter = DuckDBAdapter(database_path=":memory:", progress_bar=True)BENCHBOX_OUTPUT_DIR=./benchbox_runs_2 \
benchbox run --platform duckdb --benchmark tpchDuckDB allows only one connection for write operations. Close other DuckDB connections, or point BENCHBOX_OUTPUT_DIR at a different runs root so the run uses a new database file. The new root also holds its own generated data and results.
df -h /tmp
benchbox run --platform duckdb --benchmark tpch --scale 10.0 \
--platform-option temp_directory=/data/tmpCheck the space in the temp directory first, then point temp_directory at a location with more room.
| Feature | DuckDB | SQLite | PostgreSQL |
|---|---|---|---|
| Setup | None | None | Server |
| Best for | OLAP | OLTP | General |
| Parallelism | Multi-thread | Single-thread | Multi-process |
| Memory | In-process | In-process | Separate |
| Scale | ~100 GB | ~10 GB | ~TB |
- SQLite - Alternative embedded database
- DataFusion - Rust-based alternative
- Quick Start - Getting started tutorial
- Platform Selection