Представьте: у вас есть паркет на 10 гигов, и надо всего лишь посчитать по нему пару агрегаций... А что если можно не тащить его целиком в память и не городить пайплайны, а просто написать SQL прямо по файлу - без всякой базы и серверов? 🦆
Есть такой инструмент - DuckDB, и за последние пару лет он потихоньку прописался в арсенале у многих DS-команд.
🦆 Что это такое?
Если в двух словах - это "SQLite для аналитики". Появился он ещё в 2019 году (кстати, вырос из амстердамского института CWI - того самого, откуда родом Python), но по-настоящему зрелым стал не так давно. Технически это встраиваемая колоночная база данных, заточенная под аналитические запросы.
Что это значит на практике:
• Работает прямо внутри вашего Python-скрипта - никаких серверов, подключений и конфигов.
• Читает CSV, Parquet, JSON, а также pandas- и polars-датафреймы напрямую, без предварительной загрузки в базу.
• Это полноценный SQL - с оконными функциями, подзапросами, джойнами и всем, что вы любите.
• Многопоточность из коробки + отлично работает с данными, которые больше оперативной памяти!
По сути это способ писать SQL по чему угодно: по файлам, датафреймам, бакетам в облаке, без построения пайплайнов и поднятия инфраструктуры.
🦆 Почему он такой быстрый?
• Хранение колоночное: если вам нужны две колонки из тридцати, DuckDB прочитает с диска только их (это называют projection pushdown).
• Фильтр из WHERE он старается применить как можно раньше, ещё на этапе чтения (это predicate pushdown) - то есть лишние строки даже не поднимаются в память.
• Плюс векторизованное исполнение: данные обрабатываются не по одной строчке, а пачками - отсюда и скорость на больших файлах.
🦆 Установка и использование
Ставится одной строчкой, без серверов и докера:
pip install duckdb
Самый частый сценарий - SQL прямо по паркет-файлу, без загрузки куда-либо:
import duckdb
result = duckdb.sql("""
SELECT category,
AVG(price) AS avg_price,
COUNT(*) AS orders
FROM 'sales.parquet'
WHERE price > 100
GROUP BY category
ORDER BY orders DESC
""").df()
Обратите внимание: файл указан прямо в FROM, никакой предварительной загрузки в таблицу. В конце .df() возвращает результат обычным pandas-датафреймом.
Можно так же легко пройтись сразу по нескольким файлам по маске:
duckdb.sql("SELECT * FROM 'data/2024_*.parquet'").df()
И самая приятная часть - дружба с pandas:
import pandas as pd
import duckdb
df = pd.read_csv("orders.csv")
# обращаемся к df по имени переменной прямо в SQL
result = duckdb.sql("""
SELECT user_id, SUM(amount) AS total
FROM df
GROUP BY user_id
HAVING total > 1000
""").df()
DuckDB видит переменную df в вашем Python-коде и работает с ней как с таблицей, причём без лишнего копирования данных туда-сюда - обмен идёт напрямую.
А ещё можно смешивать источники в одном запросе: сджойнить pandas-датафрейм, паркет с диска и CSV из интернета в одном SELECT 😏
🦆 Что почитать:
• Официальная документация: тык
• Раздел Guides с готовыми рецептами кода: тык
• Блог с разборами внутренностей и бенчмарками: тык
Пусть ваши запросы летают, а память не кончается! 💨
#аналитика@data_easy
#classic_ml@data_easy