Перейти к содержанию
Learning Platform
Глоссарий
Troubleshooting

Troubleshooting — DuckDB

База знаний типичных ошибок курса DuckDB.

Показано 32 из 32 ошибок

Причина

Перенос ментальной модели PostgreSQL/MySQL на DuckDB. На самом деле DuckDB — in-process (embedded) движок: он исполняется внутри вашего процесса как библиотека.

Решение

  1. Не ищите сервер, демон, host/port, connection string по сети. Просто pip install duckdb и duckdb.connect('my.db') — база живёт в файле или в памяти прямо внутри процесса. Это аналитический аналог SQLite. Сетевого протокола у DuckDB нет вовсе.

Причина

Распространённое заблуждение. DuckDB умеет out-of-core исполнение: при нехватке памяти он спиллит данные на диск.

Решение

  1. DuckDB поддерживает external hash aggregation, external hash join и external sort. Задайте SET memory_limit='4GB' и SET temp_directory='/path/to/tmp' — тяжёлый GROUP BY/JOIN по датасету в десятки ГБ завершится на ноутбуке. Документированный кейс: все запросы на 50 ГБ датасете выполнены на 16 ГБ RAM. Spark для одной машины здесь не нужен.

Причина

DuckDB — single-process база: файл держит ровно один процесс. Внутри процесса возможны многопоточность и конкурентные читатели, но не несколько процессов-писателей.

Решение

  1. Архитектурно: один процесс-писатель на файл. Если нужна параллельная запись от многих процессов — это не профиль DuckDB. Варианты: писать в отдельные файлы и читать их вместе через glob; перейти на DuckLake с PostgreSQL-каталогом (настоящий multi-writer); или использовать MotherDuck. Для конкурентных читателей при одном писателе достаточно одного процесса.

Причина

DuckDB — OLAP-движок (колоночное хранение, векторизация). Частые мелкие INSERT/UPDATE отдельных строк от многих клиентов — это профиль OLTP.

Решение

  1. Не подменяйте PostgreSQL/SQLite DuckDB'ом для транзакционных приложений с высокой конкурентностью записи. DuckDB силён в аналитике: сканирования, агрегации, JOIN, отчёты. Для OLTP оставьте OLTP-СУБД; DuckDB подключайте как аналитический слой поверх (federated-запросы через postgres/sqlite-сканеры).

Причина

Расширение spatial не установлено и не загружено. В отличие от httpfs/parquet, spatial НЕ автозагружаемое.

Решение

  1. Выполните явно: INSTALL spatial; LOAD spatial; — и только потом пространственные функции. Это надо делать в каждой новой сессии (LOAD), установка же разовая. Примечание: с DuckDB 1.5 тип GEOMETRY стал core-типом, но многие ST_-функции по-прежнему живут в расширении spatial.

Причина

Не все промежуточные структуры умеют спиллиться. Некоторые сложные агрегатные состояния (например, list-агрегаты, distinct внутри агрегатов, оконные функции с большими партициями) пока не выгружаются на диск.

Решение

  1. Снизьте threads (каждый поток держит свой бюджет памяти), увеличьте memory_limit, переформулируйте запрос — разбейте тяжёлый GROUP BY на этапы, замените list-агрегаты, отфильтруйте данные раньше. Проверьте через EXPLAIN ANALYZE, какой оператор раздувается. Это известное ограничение, а не баг.

Причина

CSV-sniffer анализирует только выборку (по умолчанию 20 480 строк). Если аномальные значения (пустые строки, текст в числовой колонке) встречаются дальше выборки, типы выводятся неверно или поздно вскрывается ошибка.

Решение

  1. Увеличьте выборку: read_csv('f.csv', sample_size=200000) или sample_size=-1 для анализа всего файла (медленнее, но надёжно). Лучше — задайте типы явно: read_csv('f.csv', columns={'id':'BIGINT','price':'DECIMAL(10,2)'}). Явная схема убирает и угадывание, и риск ошибки.

Причина

Параметр threads выставлен в 1, либо данные лежат в одном маленьком файле/одном row group и нечего параллелить, либо в плане доминирует pipeline breaker без параллельной реализации.

Решение

  1. Проверьте SELECT current_setting('threads') — по умолчанию это число ядер. Запустите EXPLAIN ANALYZE и посмотрите, какой оператор занимает время. Для Parquet — несколько файлов или row groups дают параллельное сканирование. Один CSV без row groups параллелится хуже, чем Parquet.

Причина

По умолчанию DuckDB берёт значительную долю системной RAM (около 80%). В контейнере или на shared-машине это приводит к вытеснению других процессов или OOM-kill.

Решение

  1. Всегда задавайте лимит явно под своё окружение: SET memory_limit='6GB'. В контейнере с cgroup-лимитом это критично — DuckDB может не видеть лимит контейнера. Заодно задайте SET temp_directory='/tmp/duckdb', чтобы спилл шёл в известное место с достаточным объёмом диска.

Причина

Сжатие в DuckDB применяется только к персистентным базам. In-memory база (`:memory:`) хранит данные без колоночной компрессии.

Решение

  1. Если важен объём памяти — работайте с персистентным файлом (duckdb.connect('data.db')), даже если он временный: данные лягут сжатыми сегментами. In-memory режим удобен для мелких сессий и тестов, но для больших датасетов берите файл. Грубая оценка: ~100 ГБ несжатого CSV -> ~25 ГБ файла DuckDB.

Причина

DuckDB гарантирует backward-совместимость (новая версия читает старые файлы), но НЕ forward-совместимость. Старый движок не обязан читать файл нового storage version.

Решение

  1. Обновите DuckDB до версии не ниже той, что создала файл. Если файл нужно отдать в окружение со старым DuckDB — пересоздайте его с нужной версией формата: ATTACH 'old.db' (STORAGE_VERSION 'v1.2.0') и скопируйте данные, либо используйте флаг CLI -storage-version. Универсальный путь — EXPORT DATABASE в Parquet+SQL и IMPORT на другой стороне.

Причина

Расширение httpfs не загружено, либо нет доступа в интернет для автозагрузки, либо не настроены учётные данные (secret).

Решение

  1. httpfs автозагружаемо, но в air-gapped окружении установите заранее: INSTALL httpfs. Для S3 задайте secret: CREATE SECRET (TYPE s3, KEY_ID '...', SECRET '...', REGION '...'). Без секрета приватные бакеты вернут 403. Проверьте также endpoint/region — для S3-совместимых хранилищ (MinIO, R2) их надо указывать явно.

Причина

Перенос привычки серверных СУБД, где данные сперва грузят в таблицы.

Решение

  1. DuckDB запрашивает Parquet/CSV/JSON/Arrow прямо на месте: SELECT * FROM 'data/*.parquet' WHERE .... Pandas/Polars/PyArrow DataFrame тоже доступны по имени переменной через replacement scan — без CREATE TABLE и без копирования. Импорт в таблицу нужен, только когда вы хотите персистентность, сжатие или ускорение повторных запросов.

Причина

DuckDB часто сравнивают с Pandas из-за работы с DataFrame'ами в Python.

Решение

  1. DuckDB — полноценный SQL OLAP-движок: ACID-транзакции, настоящий cost-based оптимизатор, larger-than-memory исполнение, персистентное хранилище, параллелизм. Pandas — однопоточная in-memory библиотека без всего этого. DuckDB может запрашивать сам Pandas DataFrame, но возможностями он принципиально шире.

Причина

Файл .duckdb уже открыт другим процессом с эксклюзивной блокировкой: открытый DBeaver/IDE, другой Python-процесс, незавершённый предыдущий запуск, оставшийся .wal-lock после краха.

Решение

  1. Закройте все клиенты, держащие файл. Найдите процессы: lsof file.duckdb (Mac/Linux). Для конкурентного ЧТЕНИЯ из второго процесса можно открыть базу в read-only: duckdb.connect('f.db', read_only=True) — read-only коннекты не конфликтуют. Писатель при этом должен быть один.

Причина

Переменная DataFrame недоступна в области видимости коннекта: запрос идёт через явно созданное соединение, DataFrame создан в другой функции/области, либо это не connection-метод, а другой объект.

Решение

  1. Replacement scan находит объект в локальной/глобальной области Python. Если переменная не видна — зарегистрируйте явно: con.register('df', my_dataframe), затем con.execute('SELECT * FROM df'). Явная регистрация надёжнее автоматического scan внутри функций и модулей.

Причина

Датасет не Hive-партиционирован, либо partition pruning/filter pushdown не сработал: фильтр по обычной колонке внутри файла, а не по партиционной колонке из пути.

Решение

  1. Организуйте данные в Hive-layout (year=2026/month=05/) — тогда фильтр по партиционной колонке отсечёт целые каталоги. Для фильтра внутри файлов помогает Parquet с zonemap по row groups (filter pushdown пропустит группы). Проверьте через EXPLAIN ANALYZE число прочитанных байт с фильтром и без — так видно, работает ли pruning.

Причина

REAL/DOUBLE — типы конечной точности (IEEE 754). Это не потеря данных при сжатии (Chimp/Patas/ALP — lossless), а обычная природа float-арифметики.

Решение

  1. Для денег и точных значений используйте DECIMAL(p,s) — фиксированная точность без ошибок округления. DOUBLE оставьте для научных/приближённых расчётов. Сравнивайте float не на равенство, а с допуском. Сжатие чисел в DuckDB lossless — дело именно в самом типе float.

Причина

DuckDB — колоночный OLAP-движок: он оптимизирован под пакетную загрузку, а не под тысячи одиночных INSERT. Каждый отдельный INSERT — это транзакция и запись в WAL.

Решение

  1. Грузите данные пакетно: INSERT INTO t SELECT * FROM read_csv('f.csv'), COPY t FROM 'f.parquet', либо CREATE TABLE t AS SELECT .... Из Python — con.execute('INSERT INTO t SELECT * FROM df') через replacement scan одним запросом. Множество одиночных INSERT — антипаттерн для OLAP.

Причина

Friendly SQL — это расширения именно диалекта DuckDB. FROM-first, EXCLUDE/REPLACE, COLUMNS, GROUP BY ALL, QUALIFY, list comprehensions не входят в стандарт SQL.

Решение

  1. Это ожидаемо: friendly SQL непереносим на PostgreSQL, Snowflake и др. Если запрос должен работать в нескольких СУБД — пишите стандартный SQL: явный список колонок, GROUP BY с перечислением, оконные функции в подзапросе вместо QUALIFY. Friendly SQL — для кода, привязанного к DuckDB.

Причина

Идентификаторы DuckDB регистронезависимы, но сохраняют исходный регистр. При этом строковые сравнения и поиск по имени могут вести себя не так, как ожидается, если колонка была создана в кавычках.

Решение

  1. Обращайтесь к колонке без кавычек — OrderID, orderid, ORDERID эквивалентны. Кавычки "OrderID" нужны, только если в имени есть пробелы/спецсимволы. По соглашению приводите имена к snake_case на этапе staging: "OrderID" AS order_id.

Причина

Коннект открыт с `read_only=True`, либо файл оказался read-only из-за прав ФС, либо процесс получил read-only fallback, потому что писатель уже держит файл.

Решение

  1. Откройте коннект на запись: duckdb.connect('f.db') без read_only. Проверьте права файла и каталога. Убедитесь, что другой процесс не держит эксклюзивную блокировку (lsof). Read-only коннект нужен для конкурентного чтения рядом с писателем — но писать через него нельзя по определению.

Причина

Фильтр не протолкнулся к источнику: предикат стоит после JOIN/агрегации, или применён к вычисляемому выражению, по которому zonemap не работает, или у источника нет статистики.

Решение

  1. Фильтруйте как можно раньше — до JOIN. Не оборачивайте колонку фильтра в функцию (WHERE date_col >= '2026-01-01', а не WHERE year(date_col) = 2026) — иначе zonemap и filter pushdown не сработают. Для таблиц убедитесь, что данные дают полезную min/max-статистику по сегментам.

Причина

CSV — текстовый строчный формат: его нужно каждый раз парсить, в нём нет колоночного хранения, сжатия, row groups и zonemap. Projection и filter pushdown по CSV ограничены.

Решение

  1. Если данные читаются многократно — один раз сконвертируйте: COPY (SELECT * FROM read_csv('f.csv')) TO 'f.parquet' (FORMAT parquet). Дальше работайте с Parquet: колоночное чтение, сжатие, пропуск row groups по статистике, параллельное сканирование. CSV держите только как формат обмена.

Причина

Похожие названия. На деле это разные вещи: DuckDB — движок, DuckLake — формат lakehouse-таблиц.

Решение

  1. DuckLake — открытый формат lakehouse: данные в Parquet на object storage плюс ВСЕ метаданные в реляционной SQL-базе-каталоге (SQLite/PostgreSQL/DuckDB). DuckDB — движок исполнения и эталонная реализация DuckLake через расширение ducklake. Можно использовать DuckDB без DuckLake и читать DuckLake другими движками (DataFusion, Spark, Trino).

Причина

Каждая запись по умолчанию могла бы порождать отдельный Parquet-файл — классическая проблема small files и write amplification в lakehouse-форматах.

Решение

  1. DuckLake решает это через data inlining: мелкие изменения (по умолчанию до 10 строк, параметр DATA_INLINING_ROW_LIMIT) хранятся прямо в метаданных каталога, а не в файлах. Когда данных накопилось — выполните CHECKPOINT, чтобы выгрузить инлайненные данные в Parquet. Не делайте тысячи микро-коммитов без последующего checkpoint.

Причина

По умолчанию база DuckDB-WASM живёт в памяти вкладки. Без настройки персистентности состояние не сохраняется между перезагрузками.

Решение

  1. Используйте OPFS: открывайте базу по пути opfs://my.db. Для автосохранения каждого изменения задайте SET checkpoint_threshold='0KB'. Тогда состояние переживёт перезагрузку вкладки и даже перезапуск браузера. Учтите ограничения OPFS по объёму и то, что доступ к OPFS требует защищённого контекста (HTTPS).

Причина

Старая репутация ранних 0.x версий, когда формат менялся часто.

Решение

  1. Backward-совместимость формата гарантируется с версии v0.10 (storage version 64): новые версии DuckDB читают старые файлы. Forward-совместимость — best-effort, но управляема: параметр STORAGE_VERSION позволяет писать файлы, совместимые со старыми версиями. Для долговременной переносимости используйте EXPORT DATABASE.

Причина

Слишком много потоков при ограниченной памяти: каждый поток держит свой бюджет памяти, и общий расход растёт — начинается спилл или OOM. Либо запрос упирается в pipeline breaker, либо данных мало для параллелизма.

Решение

  1. Подбирайте threads под объём RAM: грубо ~1-2 ГБ/поток для агрегаций, ~3-4 ГБ/поток для тяжёлых JOIN. Если памяти мало — уменьшите threads, а не увеличивайте. Смотрите EXPLAIN ANALYZE: если время в одном breaker-операторе, рост числа потоков не поможет.

Причина

DuckDB 1.0 вышел в июне 2024 — он давно устарел. VARIANT, GEOMETRY как core-тип, MERGE INTO, encryption, DuckLake и friendly CLI появились позже.

Решение

  1. Ориентируйтесь на актуальные версии: stable — 1.5.2 (апрель 2026), LTS — линия 1.4.x «Andium» (поддержка до 16.09.2026). Для продакшена со строгими требованиями к стабильности берите LTS 1.4.x. MERGE INTO и encryption требуют минимум 1.4; VARIANT и GEOMETRY-core — минимум 1.5.

Причина

MERGE INTO появился только в DuckDB 1.4. На более старой версии этой команды нет.

Решение

  1. Обновитесь минимум до 1.4 (лучше — текущий stable 1.5.2 или LTS 1.4.x). До 1.4 для upsert используйте INSERT ... ON CONFLICT DO UPDATE — но это требует ограничения уникальности/первичного ключа. Преимущество MERGE INTO как раз в том, что ключ не обязателен и условие слияния произвольное.

Причина

DuckDB оптимизирован под аналитику и пакетные изменения. Большие UPDATE/DELETE по колоночному хранилищу затрагивают целые row groups; накопление изменений в WAL без checkpoint тоже замедляет.

Решение

  1. Группируйте изменения в крупные операции вместо множества мелких. После больших серий записи выполняйте CHECKPOINT, чтобы слить WAL в основной файл. Для массовой замены данных часто быстрее CREATE OR REPLACE TABLE t AS SELECT ..., чем точечные UPDATE/DELETE по строкам.