Назад к блогу

DuckDB 2.0 LTS: асинхронный ввод-вывод, Quack-протокол и режим для агентов

DuckDB 2.0 LTS: асинхронный ввод-вывод, Quack-протокол и режим для агентов

DuckDB 2.0 выходит с долгосрочной поддержкой и становится заметно ближе к полноценной СУБД: асинхронный ввод-вывод ускоряет работу с данными в объектных хранилищах, а новый сетевой протокол позволяет подключаться к одной базе из нескольких процессов. Кроме того, в движке появились полноценный тип для полуструктурированных данных и триггеры уровня инструкции — это открывает дорогу к инкрементальному поддержанию агрегатов.

Вышла DuckDB 2.0.0 — релиз с долгосрочной поддержкой (LTS) как минимум до октября 2027 года. Кодовое имя — «Cinnamon Teal», в честь утки Spatula cyanoptera. С момента версии 1.5 в него вошло почти 20 000 коммитов; это крупнейший функциональный релиз проекта.

Ниже — что именно изменилось в движке и CLI, как это работает и что из этого следует для тех, кто гоняет на DuckDB аналитические запросы.

Асинхронный ввод-вывод при чтении файлов

Главное изменение для аналитических нагрузок — асинхронный ввод-вывод при чтении Parquet, CSV и собственного формата хранения DuckDB. Раньше чтение и обработка запроса были связаны; теперь слой ввода-вывода масштабируется независимо от слоя обработки запросов: чтения выдаются в фоне, и одновременно в полёте держится несколько запросов.

Практический эффект — больше параллелизма при удалённом чтении и заметно более быстрые запросы к объектным хранилищам вроде s3://. Для локальных файлов выигрыш меньше, но именно сценарий «данные лежат в объектном хранилище» здесь основной.

Quack-протокол и оператор CONNECT

Протокол Quack — это сетевой протокол, который позволяет запускать DuckDB в традиционной схеме клиент-сервер: один процесс держит базу и обслуживает подключения, а другие подключаются к ней по сети. В 2.0.0 он доработан — заявлены существенные улучшения производительности и стабильности. Конкретные проблемы, которые мотивировали переработку, в анонсе не названы.

Сервер поднимается вызовом quack_serve с токеном, клиент подключается через ATTACH с тем же токеном, после чего CONNECT переключает сессию на удалённую базу:

CALL quack_serve(token = 'my_token');
ATTACH 'quack:localhost' AS qk (TOKEN 'my_token');
CONNECT qk;

После CONNECT запросы выполняются уже к таблицам удалённой базы. Оператор CONNECT не привязан к Quack: он указывает сессию на любую удалённую базу, которая это поддерживает, включая PostgreSQL и MySQL. Для них новый оптимизатор remote pushdown отправляет запросы прямо на удалённый сервер вместо перетаскивания таблиц по сети — так по сети передаётся результат запроса, а не все исходные строки.

Для эксплуатации важно поведение по умолчанию: сервер слушает только localhost. Чтобы принимать подключения с других машин, его оставляют на localhost и ставят за TLS-терминирующий обратный прокси. Опция allow_other_hostname, открывающая сервер напрямую, предназначена только для тестирования. В CLI появились ярлыки -serve и -connect, запускающие сервер или подключающиеся к нему из командной строки.

Тип VARIANT

VARIANT стал полноценным типом колонки. DuckDB определяет общую структуру данных и «shred» её — раскладывает по колонкам, — за счёт чего данные хорошо сжимаются и быстро обрабатываются. Работать с ними можно семейством функций variant_*.

CREATE TABLE events (payload VARIANT);

JSON-значения вставляются приведением текста к JSON, затем к VARIANT. Для значения можно узнать его тип строкой (для объектов — OBJECT) и получить массив ключей верхнего уровня типа varchar[].

Триггеры и путь к материализованным представлениям

Появились триггеры уровня инструкции: FOR EACH STATEMENT с REFERENCING OLD TABLE AS o / NEW TABLE AS n даёт телу доступ к таблицам-переходам — строкам до и после изменения. В отличие от обычного построчного триггера, который срабатывает на каждую затронутую строку, такой триггер выполняется один раз на всю инструкцию и видит сразу весь набор изменённых строк. Это важно, когда изменение нужно обработать как единое множество: например, аудит соединяет старые и новые строки по идентификатору и записывает прежнее и новое значения одной операцией.

Отдельный сценарий — инкрементальное поддержание агрегатов. Чтобы хранить средний возраст по национальностям, таблица age_statistics держит не только average_age, но и sum_age и num_people: среднее пересчитывается без полного пересчёта таблицы. Триггер на INSERT группирует новые строки по национальности и через MERGE прибавляет сумму и количество, а при отсутствии строки вставляет новую. Триггер на DELETE работает симметрично: если удалены все люди данной национальности, запись удаляется, иначе сумма и количество вычитаются.

Авторы прямо называют это шагом к материализованным представлениям — давно числившемуся пункту в разделе «future work» roadmap. То есть триггеры приближают к ним, но не заменяют: материализованные представления остаются будущей работой.

Пользовательские репозитории расширений

Репозиторий расширений — это имя, URL-префикс и один или несколько RSA-публичных ключей, которым доверяется подпись расширений из него. Префикс может указывать на локальный путь, https, s3 — на всё, что DuckDB умеет читать.

Использование таких репозиториев сначала разрешается настройкой allow_extension_repositories: чтобы включить пользовательские репозитории, ей присваивается значение 'allowed' командой SET allow_extension_repositories = 'allowed';. Затем репозиторий создаётся командой CREATE EXTENSION REPOSITORY ... WITH PREFIX ....

При создании DuckDB загружает публичные ключи репозитория и закрепляет их в определении, печатая SHA-256 отпечаток каждого ключа — его можно сверить с опубликованным отдельно. Установка идёт через INSTALL AND LOAD ... FROM .... Функциональность ещё в разработке, и её синтаксис может измениться в будущих релизах.

Agent Mode в CLI

Coding agents вроде Claude Code, Codex и Cursor всё чаще управляют DuckDB CLI от имени пользователей. CLI 2.0 определяет, что запущен агентом, и переключается в режим агента: печатает компактные Markdown-таблицы вместо таблиц с отступами и выдаёт ошибки в JSON. Долгие запросы перед выполнением сообщают ожидаемую стоимость, чтобы агент решил, ждать ли.

Проблема, которую это решает, — объём вывода, который модели приходится читать: в экспериментах на TPC-H режим агента сократил его на 59%. Включается вручную флагом -agent, агенту можно указать отключить его флагом -no-agent.

Лямбда-синтаксис: ломающее изменение

Старый синтаксис с одиночной стрелкой (x -> x + 1) отключён по умолчанию. Вместо него используется синтаксис в стиле Python, где перед параметром ставится ключевое слово lambda:

SELECT list_transform([1, 2, 3], lambda x: x + 1);

Временно вернуть старое поведение можно настройкой SET lambda_syntax = 'enable_single_arrow';. И флаг, и синтаксис с одиночной стрелкой будут полностью удалены в v2.1 — это переходный период, а не альтернатива.

ICU: библиотека убрана

Метки времени с учётом часового пояса, календари и collations всегда обеспечивались библиотекой ICU. В 2.0 она удалена полностью: расширение icu теперь само реализует часовые пояса, календари и collations. Данные о часовых поясах строятся напрямую из базы IANA и сжимаются примерно до 45 kB. Поведение остаётся прежним, а нативная реализация быстрее.

Ограничения и открытые вопросы

Помимо временного характера флага lambda_syntax, авторы объявили устаревшей сборку amalgamation — она «стала слишком большой, чтобы быть реалистично полезной в большинстве случаев». Платформа osx_amd64 постепенно выводится из поддержки: DuckDB 2.1 перестанет её поддерживать. Материализованные представления, к которым приближают триггеры, остаются пунктом будущих работ.

Источники

Похожее