Vectorized execution and tuning

DuckDB · Expert · 15 min

Knowing the engine explains performance surprises and lets you process datasets larger than RAM reliably.

DuckDB executes queries with a vectorized, push-based engine. Operators process vectors of up to 2048 values of one column at a time, which keeps data in CPU caches and lets tight loops run without per-row interpretation overhead. Parallelism is morsel-driven: the input is split into chunks (for Parquet, roughly by row group) that worker threads grab dynamically, so all cores stay busy without fixed partitioning.

EXPLAIN shows the physical plan; EXPLAIN ANALYZE runs the query and annotates each operator with time and row counts. Look for where rows explode (a bad join), which scan dominates, and whether filters and projections were pushed into the scan.

Key settings:

  • SET threads = 8; defaults to the core count
  • SET memory_limit = '4GB'; defaults to 80% of RAM
  • SET temp_directory = '/fast/disk'; where spilled data goes

Hash joins, aggregations, sorts and window functions can spill to disk when they exceed the memory limit, so queries larger than RAM usually complete, just slower. preserve_insertion_order = false lets DuckDB skip ordering work and use less memory on big exports.

Performance tips: prefer Parquet over CSV, keep row groups large, avoid many tiny files, and remember that some operators, such as very large list() aggregates, cannot spill.

Example

import duckdb

con = duckdb.connect()
con.sql("SET threads = 4")
con.sql("SET memory_limit = '1GB'")
con.sql("CREATE TABLE t AS SELECT i, i % 100 AS g FROM range(2_000_000) r(i)")

plan = con.sql("""
    EXPLAIN ANALYZE SELECT g, count(*) FROM t WHERE i % 2 = 0 GROUP BY g
""").fetchall()[0][1]
print("HASH_GROUP_BY" in plan, "Filters:" in plan)  # True True
# the WHERE clause was pushed down into the table scan
print(con.sql("SELECT current_setting('threads')").fetchone())   # (4,)

Remember

  • Operators work on column vectors of up to 2048 values
  • Morsel-driven parallelism spreads work over all threads
  • EXPLAIN ANALYZE shows per-operator time and row counts
  • memory_limit and temp_directory control spilling for larger-than-RAM queries

Watch out for

  • Setting memory_limit close to total RAM on a machine that also runs other processes, causing OS out-of-memory kills.

Check yourself

  1. A GROUP BY needs more memory than memory_limit allows. What does DuckDB typically do?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: duckdb.org/docs/current/guides/performance/overview