Когда использовать DuckDB и когда НЕ использовать (антипаттерны)
Инструмент полезен ровно настолько, насколько ясны его границы. Молоток — отличный инструмент, но если им закручивать шурупы, виноват не молоток. С DuckDB та же история: на своих задачах он один из лучших инструментов, на чужих — плохой выбор, и проблема не в нём, а в применении. Зрелость инженера измеряется не тем, сколько инструментов он знает, а тем, насколько точно он понимает, где каждый из них уместен, а где нет.
Прошлые уроки дали теорию: in-process архитектура, OLAP против OLTP. Этот урок переводит теорию в практическое решение: даёт чёткие критерии, когда DuckDB — правильный выбор, и разбирает анти-паттерны — ситуации, где его применять не следует, с объяснением почему. Это, возможно, самый практичный урок модуля: именно здесь вы научитесь не делать дорогую архитектурную ошибку.
Важно понимать, откуда берётся неправильное применение DuckDB. Почти всегда — из его же достоинств. DuckDB настолько прост в установке и приятен в работе, что возникает соблазн применить его везде: «он отлично себя показал на аналитике — давайте и приложение на нём построим». Этот соблазн и ведёт к анти-паттернам. Поэтому знать сильные стороны инструмента недостаточно — нужно так же чётко знать его границы, чтобы привлекательность не увела за их пределы. Каждый анти-паттерн ниже разобран с причиной: понимая почему DuckDB там не работает, вы не повторите ошибку даже в незнакомой ситуации.
Когда DuckDB — правильный выбор
Decision matrix: pandas / Polars / PyArrow / DuckDB / Spark Когда Trino, когда Spark SQL, когда хранилищеDuckDB силён, когда совпадают несколько условий. Чем больше совпадает — тем увереннее выбор.
Аналитическая (OLAP) нагрузка. Запросы агрегируют, группируют, соединяют таблицы, сканируют много строк по нескольким колонкам. Это профиль, под который DuckDB создан.
Один процесс — один пользователь данных. Аналитик в ноутбуке, ETL-скрипт в пайплайне, аналитический модуль внутри приложения. Данные обрабатывает один процесс, не нужен сетевой многопользовательский доступ.
Желание избавиться от инфраструктуры. Нет кластера, нет сервера, нет демона для администрирования. pip install duckdb — и движок готов. Для локальной аналитики и встраивания это огромный плюс.
Работа с файлами данных напрямую. Есть Parquet, CSV, JSON — на диске или в object storage. DuckDB запрашивает их на месте, без шага импорта в отдельную СУБД.
Аналитика внутри Python/R. Уже есть pandas/Polars DataFrame, и нужен по ним полноценный SQL — быстрее pandas, с настоящим оптимизатором. Zero-copy интеграция здесь делает DuckDB естественным выбором.
Конкретные сценарии-фавориты: разведочный анализ датасета в ноутбуке; трансформационный (T) слой ELT-пайплайна на одной машине; быстрый аналитический модуль внутри приложения, считающий отчёты по локальным данным; обработка набора Parquet-файлов без поднятия Spark; локальная замена тяжёлым pandas-скриптам, которые упираются в память. Общее у всех этих сценариев — задача аналитическая, помещается в одну машину и обрабатывается одним процессом. Если ваша задача похожа на любой из них хотя бы по форме, DuckDB почти наверняка хороший выбор; чем больше совпадений с перечисленными условиями, тем увереннее.
Анти-паттерн 1: операционная БД приложения
Ситуация. Команда хочет взять DuckDB как основную базу веб-приложения: пользователи регистрируются, оформляют заказы, обновляют профили — всё пишется в DuckDB.
Почему это плохо. Это OLTP-нагрузка: тысячи коротких конкурентных операций, многие из них — записи. DuckDB реализует модель «один писатель, много читателей»: persistent-файл в каждый момент открыт на запись только одним процессом, и сам движок оптимизирован под аналитику, а не под высококонкурентную мелкую запись. Веб-приложение с несколькими процессами-воркерами, каждый из которых хочет писать, упрётся в эту модель сразу.
Что брать вместо. PostgreSQL — для серверного приложения. SQLite — для встроенной локальной БД приложения. Оба — OLTP-движки, рассчитанные ровно на эту нагрузку.
Анти-паттерн 2: разделяемая сетевая БД для многих сервисов
Ситуация. Несколько микросервисов на разных машинах должны одновременно читать и писать общие данные. Кто-то предлагает поставить DuckDB «как базу для всех».
Почему это плохо. DuckDB — embedded, in-process СУБД. У неё нет сервера, нет демона, нет сетевого протокола, к ней нельзя «подключиться» по сети с другой машины — подключаться не к чему (об этом был урок про in-process архитектуру). DuckDB живёт внутри одного процесса; межмашинный конкурентный доступ к общим данным — вне его модели.
Что брать вместо. Клиент-серверную СУБД с сетевым доступом: PostgreSQL для транзакционных данных. Если задача аналитическая, но нужен именно сетевой managed-доступ к DuckDB-движку в облаке — существует MotherDuck, отдельный managed-сервис (его курс разбирает в модуле 15). Но «голый» DuckDB на роль сетевой разделяемой БД не годится.
Анти-паттерн 3: высококонкурентная запись из множества процессов
Ситуация. Двадцать параллельных процессов-воркеров одновременно пишут результаты в один persistent-файл DuckDB.
Почему это плохо. Это частный, но очень распространённый случай нарушения модели «один писатель». Persistent-файл DuckDB может быть открыт на запись только одним процессом одновременно. Двадцать пишущих процессов — это конфликт: они не смогут писать в файл параллельно.
Как делать правильно. Если параллелизм нужен внутри одной задачи — используйте многопоточность внутри одного процесса DuckDB: один процесс, много потоков, движок сам распараллелит работу (morsel-driven parallelism, модуль 08). Если данные приходят из множества независимых процессов — пусть каждый пишет в свой отдельный файл (например, свой Parquet), а потом один процесс DuckDB читает их все вместе через glob. Конкурентная запись в один файл из многих процессов — не та модель.
Различайте конкурентность потоков и конкурентность процессов. DuckDB отлично параллелит работу между потоками одного процесса — это его сильная сторона. Чего он не делает — это конкурентная запись в один файл из нескольких процессов. «Один писатель» означает один пишущий процесс, а не один поток.
Анти-паттерн 4: распределённая обработка на кластере машин
Ситуация. Датасет — десятки терабайт, и хочется обрабатывать его, распределив по кластеру из десятков машин, как это делает Spark.
Почему это плохо. DuckDB — single-node движок. Он рассчитан на одну машину и прекрасно использует все её ядра и память, умеет out-of-core (данные больше RAM спиллятся на диск — модуль 13). Но он не распределённый: не умеет раскидать один запрос на кластер машин и собрать результат. Для по-настоящему распределённой обработки на кластере DuckDB не предназначен.
Когда это всё-таки не анти-паттерн. Важная оговорка: «больше RAM» — это не «нужен кластер». Современная одна машина имеет сотни гигабайт памяти, а DuckDB ещё и спиллит на диск. Датасеты в сотни гигабайт и единицы терабайт одна машина с DuckDB обрабатывает уверенно — и это часто проще и дешевле кластера. Кластер действительно нужен лишь когда данные не помещаются и в это, либо когда требуется горизонтальное масштабирование под нагрузку. Не спешите тянуть Spark: сначала проверьте, не справится ли одна машина.
Этот пункт заслуживает развёрнутого пояснения, потому что «у нас много данных, значит нужен кластер» — одно из самых дорогих заблуждений в инженерии данных. Кластер — это не бесплатное «больше мощности». Это координатор и воркеры, которые надо развернуть и поддерживать; это сеть между узлами, по которой данные приходится перегонять (а перегонка данных по сети часто оказывается узким местом распределённой обработки); это сложность отладки, когда запрос ведёт себя по-разному на разных узлах. Кластер оправдан, когда задача действительно его требует — но он добавляет постоянную операционную и денежную стоимость.
Одна машина с DuckDB во многих случаях оказывается и быстрее, и дешевле именно потому, что у неё нет сетевого слоя между узлами: все ядра работают над общей памятью, данные никуда не перегоняются. А «потолок» одной машины сегодня высок: сотни гигабайт RAM плюс способность DuckDB спиллить на диск означают, что датасеты, которые «на глаз» кажутся требующими кластера, на деле помещаются в одну ноду. Правильный инженерный порядок — снизу вверх: сначала проверить, справится ли одна машина с DuckDB, и переходить к распределённому решению только когда измерения показали, что одной ноды действительно не хватает. Преждевременный переход на кластер — это плата сложностью и деньгами за мощность, которая, возможно, не нужна.
Итоговая таблица решений
| Сценарий | DuckDB? | Почему |
|---|---|---|
| Разведка датасета в ноутбуке | Да | OLAP, один процесс, минимум инфраструктуры |
| Трансформационный (T) слой ELT на одной машине | Да | Аналитическая обработка, single-node |
| Запрос Parquet/CSV без импорта | Да | Прямое чтение файлов — профиль DuckDB |
| Аналитика по pandas/Polars DataFrame | Да | Zero-copy, полноценный SQL быстрее pandas |
| Backend веб-приложения (OLTP) | Нет | Модель «один писатель»; берите PostgreSQL/SQLite |
| Сетевая разделяемая БД для микросервисов | Нет | Embedded, нет сервера; берите PostgreSQL (или MotherDuck) |
| Конкурентная запись из 20 процессов в один файл | Нет | «Один писатель»; пишите в отдельные файлы либо потоками |
| Распределённая обработка на кластере | Нет | Single-node; для кластера — Spark/Trino |
| Датасет в сотни ГБ на одной мощной машине | Да | Single-node + out-of-core справляется; кластер не нужен |
Закономерность простая. Все «да» — это OLAP-нагрузка в пределах одной машины и одного процесса. Все «нет» — это либо OLTP, либо многопроцессная конкурентная запись, либо распределённая обработка. Запомнив эту закономерность, вы будете принимать решение быстро и правильно.
Алгоритм решения за три вопроса
Чтобы не держать в голове всю таблицу, сведём выбор к трём последовательным вопросам. Они отсеивают анти-паттерны по одному.
Вопрос первый: это аналитическая нагрузка? Запрос агрегирует, группирует, соединяет, сканирует много строк — или это короткие транзакции приложения, точечный доступ по ключу, конкурентная запись? Если нагрузка транзакционная (OLTP) — DuckDB не подходит, ответ найден, дальше не идём. Если аналитическая — переходим ко второму вопросу.
Вопрос второй: нужен ли сетевой многопользовательский доступ к общим данным? Должны ли разные сервисы с разных машин одновременно работать с одними данными по сети? Если да — «голый» DuckDB не подходит (он embedded, без сервера), нужен либо сетевой движок, либо managed-вариант. Если данные обрабатывает один процесс — переходим к третьему вопросу.
Вопрос третий: помещается ли задача в одну машину? Здесь важно не путать «много данных» с «нужен кластер»: учитывайте, что у одной машины сотни ГБ RAM, а DuckDB ещё и спиллит на диск. Если задача реально превышает возможности одной мощной ноды или требует горизонтального масштабирования — нужен распределённый движок. Если одна машина справляется — это сценарий для DuckDB.
Прошёл все три вопроса с ответами «аналитическая / один процесс / помещается в машину» — DuckDB ваш инструмент. Сорвался на любом из трёх — вопрос сам подсказал, какой класс инструмента нужен вместо. Этот короткий алгоритм покрывает практически все реальные развилки и избавляет от необходимости заучивать таблицу.
Попробуй сам
Примените критерии к реальным архитектурным развилкам. Для каждой решите «DuckDB — да или нет» и письменно обоснуйте причину:
- SaaS-стартап: нужна основная БД, в которой пользователи создают и редактируют документы, тысячи операций в секунду.
- Data-команда: ежедневный ETL читает 80 ГБ Parquet из S3, чистит, агрегирует и кладёт витрину обратно. Всё на одной виртуальной машине.
- BI-инструмент: 50 аналитиков из браузера одновременно гоняют разные SQL-запросы по общему набору данных через сеть.
- Ноутбук исследователя: датасет 30 ГБ, нужно посчитать корреляции и агрегаты; машина имеет 16 ГБ RAM.
- Лог-пайплайн: 100 параллельных воркеров обрабатывают события и должны куда-то складывать результат.
Подсказки к рассуждению: пункт 1 — какой класс нагрузки? Пункт 3 — что мешает «голому» DuckDB при сетевом multi-user доступе? Пункт 4 — мешает ли то, что датасет больше RAM? Пункт 5 — что не так с записью из 100 процессов в один файл и как это переделать. Сверьте свои ответы с итоговой таблицей решений.