Когда в production-ETL нужно обработать сотни миллионов строк, а вся память уходит на дублирование данных между pandas и SQLite, DuckDB с zero-copy через Arrow-интерфейс перестаёт быть экспериментом. Многие кидаются тащить данные через CSV или JSON, забывая, что in-process движок может работать без сериализации и wire latency.
Zero-copy memory sharing
DuckDB напрямую читает Arrow-таблицы из polars без копирования. Пример:
import duckdb
import polars as pl
df = pl.DataFrame({"x": range(10_000_000)})
con = duckdb.connect()
con.execute("CREATE TABLE data AS SELECT * FROM df WHERE x % 2 = 0")
result = con.execute("SELECT * FROM data").pl()
С pandas тоже работает через relation, но только с
pyarrow dtype:import pandas as pd
df_pd = pd.DataFrame({"id": range(1_000_000), "value": range(1_000_000)})
rel = duckdb.sql("SELECT * FROM df_pd WHERE value % 100 = 0")
result_pd = rel.df()
Бенчмарк на 100M строк
Проверил на real ETL-пайплайне (8GB RAM, 8 vCPU): фильтрация + агрегация + join на пяти колонках.
* Pandas native: 47 сек, пик RAM 14GB
* Pandas + DuckDB: 12 сек, 4.2GB
* Polars native: 8 сек, 5.1GB
* Polars + DuckDB: 5 сек, 3.8GB
DuckDB выигрывает за счёт push-down фильтров и vectorized execution — он не тащит все данные в Python-модель, а выполняет логику внутри движка.
Типичная ошибка в production
Использовать одно DuckDB-соединение для многопоточного ETL. DuckDB не thread-safe для записи, только для чтения. Для мультитрединга — выделяйте
duckdb.connect(":memory:") на каждый поток. Записывать данные лучше через один процесс.Практический совет
Для полного zero-copy все таблицы должны быть в Arrow-формате. Polars работает из коробки, pandas — только с
pyarrow dtype. Иначе DuckDB копирует данные, теряется смысл оптимизации.Вывод: DuckDB не заменяет polars или pandas, а выступает как вычислитель data-flow — связка DuckDB + polars решает главный bottleneck ETL: копирование через Python object model.