Learning Platform
Глоссарий Troubleshooting
Урок 07.06 · 22 мин
Средний
storage-formatversioningcompatibility

Версионирование storage-формата

В первом уроке модуля мы прочитали из заголовка файла поле storage version — целое число вроде 68. Тогда мы отложили разговор о том, что это число значит и какие гарантии за ним стоят. Пришло время разобрать. Этот урок про то, как формат файла DuckDB эволюционирует от версии к версии, почему новый DuckDB читает старые файлы, почему старый DuckDB обычно не читает новые, и как параметром STORAGE_VERSION намеренно записать файл в более старом, совместимом формате.

Storage version — это не версия DuckDB

Первое, что нужно твёрдо развести: версия DuckDB и storage version — это разные числа про разные вещи.

Версия DuckDB — это 1.5.2, 1.4.4, 1.3.0. Она меняется с каждым релизом движка, включая патчи и фиксы багов, которые формата файла вообще не касаются.

Storage version — это целое число: 68, 67, 66. Оно описывает не движок, а структуру файла на диске. Оно меняется только тогда, когда меняется сам бинарный формат — добавляется новая схема сжатия, новый тип данных, новая структура метаданных. Несколько релизов DuckDB подряд могут писать один и тот же storage version, если в этих релизах раскладку файла не трогали.

Соответствие версий формата и линеек DuckDB:

Storage versionЛинейка DuckDB
v68DuckDB 1.5.x
v67DuckDB 1.4.x
v66DuckDB 1.3.x
v65DuckDB 1.2.x
v64DuckDB 0.9.x - 1.1.x

Обратите внимание на последнюю строку: один storage version 64 покрывает целый диапазон релизов — от 0.9.x до 1.1.x включительно. Это и есть иллюстрация принципа: на отрезке от 0.9 до 1.1 формат файла не менялся, поэтому все эти релизы пишут и читают версию 64. А вот с 1.2 формат сдвинулся — версия стала 65, дальше 66 в 1.3, 67 в 1.4, 68 в 1.5.

Эволюция storage version по релизам
v64DuckDB 0.9.x - 1.1.x. Один формат на несколько релизов: на этом отрезке раскладка файла не менялась. Начиная с v0.10 включена гарантия backward-совместимости.
v65DuckDB 1.2.x. Формат сдвинулся относительно v64.
v66DuckDB 1.3.x.
v67DuckDB 1.4.x LTS.
v68DuckDB 1.5.x, текущая стабильная линейка.

Backward-совместимость: новый движок читает старые файлы

Backward-совместимость — это способность нового DuckDB открыть файл, записанный старым DuckDB. Версия 1.5 открывает файл формата 64, записанный когда-то DuckDB 1.0. Это гарантировано.

Точнее: backward-совместимость storage-формата гарантируется начиная с DuckDB v0.10, то есть начиная со storage version 64. Любой файл формата 64 и новее гарантированно читается любым DuckDB версии 0.10 и новее. Файл, созданный сегодня в DuckDB 1.0, без проблем откроется в DuckDB 1.5 и в будущих версиях.

Почему это можно гарантировать. Новый движок знает всё про старые форматы — он содержит код чтения версий 64, 65, 66, 67, 68. Когда он открывает файл, он читает storage version из заголовка и выбирает подходящий путь чтения. Старый формат — это подмножество того, что движок умеет: добавить в новый движок код для разбора старого формата технически несложно, его просто нужно не выбрасывать. Поэтому обещание «новый читает старое» поддерживается долго и устойчиво.

Практический смысл огромен. Файл .duckdb — это надёжный долговременный контейнер данных. Записали данные сегодня — через годы, на новой мажорной версии DuckDB, вы их откроете. Не нужно бояться обновлять движок: апгрейд DuckDB не превращает накопленные файлы в нечитаемые. Это та же гарантия, что делает SQLite основой для долговременного хранения, — и DuckDB её для своей ниши воспроизводит.

Forward-совместимость: best-effort, без гарантий

Обратное направление — forward-совместимость, способность старого DuckDB открыть файл, записанный новым DuckDB. Версия 1.3 пытается открыть файл формата 68, записанный DuckDB 1.5. Вот это уже не гарантировано — это best-effort, «по возможности».

Почему гарантировать нельзя, понятно из логики. Новый формат может содержать структуры, которых на момент выпуска старого движка просто не существовало: новую схему сжатия, новый физический тип, новую раскладку метаданных. Старый движок физически не содержит кода для их разбора — этот код написали позже. Заставить программу прошлого понимать формат будущего невозможно: нельзя добавить в уже выпущенный бинарник поддержку того, что ещё не придумано.

Что произойдёт на практике, когда старый DuckDB встретит файл с более новым storage version. Сценарий зависит от того, насколько форматы разошлись. Часто старый движок прочитает storage version из заголовка, увидит незнакомо большое число и откажется открывать файл с явным сообщением — мол, файл записан более новой версией, обновите DuckDB. Это аккуратный отказ, и именно для него storage version лежит в заголовке: чтобы несовместимость обнаружилась сразу, на третьем поле файла, а не глубоко внутри разбора структуры. Иногда, если изменения формата невелики, старый движок может файл и прочитать — но рассчитывать на это нельзя.

WARNING

Backward-совместимость — твёрдая гарантия: новый DuckDB читает старые файлы. Forward-совместимость — лишь best-effort: старый DuckDB может и не открыть файл нового формата. Практический вывод для команд: если файлом базы обмениваются несколько человек или сервисов, ориентируйтесь на самую старую версию DuckDB в этом наборе. Тот, кто записывает файл, должен писать в формате не новее, чем понимает самый старый читатель.

Два направления совместимости
Старый файл (v64)Файл записан старой версией DuckDB, формат 64.
backward: ГАРАНТИРОВАНО
Новый DuckDB 1.5Новый движок содержит код чтения всех старых форматов и открывает старый файл надёжно.
Новый файл (v68)Файл записан новой версией DuckDB, формат 68.
forward: BEST-EFFORT
Старый DuckDB 1.3Старый движок не содержит кода для нового формата — обычно аккуратно откажется открывать файл и попросит обновиться.

STORAGE_VERSION: намеренно писать в старом формате

Раз forward-совместимость не гарантирована, возникает практическая задача: я работаю на свежем DuckDB 1.5, но файл нужно отдать коллеге, у которого ещё 1.2. Если я запишу файл в формате 68, его 1.2 файл не откроет. Решение — записать файл в более старом формате намеренно.

Для этого служит параметр STORAGE_VERSION, задаваемый при ATTACH:

-- Создать/присоединить базу в формате, совместимом с DuckDB 1.2.0
ATTACH 'shared.duckdb' (STORAGE_VERSION 'v1.2.0');

-- Все таблицы, созданные в этой базе, лягут в формат 65,
-- и файл откроется любым DuckDB начиная с 1.2.0
CREATE TABLE report AS SELECT range AS id FROM range(1000) USING shared;

STORAGE_VERSION 'v1.2.0' говорит движку: пиши этот файл в формате той версии. DuckDB 1.5 умеет записывать не только свой родной формат 68, но и более старые — он сознательно ограничивает себя, не использует структуры, которых в формате 65 не было, и файл получается читаемым на DuckDB 1.2 и новее. Можно указать и 'latest' — это явно затребовать самый свежий формат, который движок умеет.

Есть и эквивалент на уровне CLI — флаг -storage-version при запуске (доступен начиная с DuckDB 1.2.0):

# Записать базу в формате, совместимом с заданной версией
duckdb -storage-version v1.2.0 shared.duckdb \
  "CREATE TABLE report AS SELECT range AS id FROM range(1000);"

Компромисс очевиден: записывая файл в старом формате, вы отказываетесь от возможностей, появившихся в новых версиях формата, — новых типов, новых схем сжатия, более компактных метаданных. Файл может оказаться чуть менее эффективным. Но взамен он гарантированно открывается на старом движке. Это осознанный обмен «новые фичи формата» на «совместимость с конкретной старой версией», и STORAGE_VERSION делает этот обмен явным и управляемым.

Когда совместимости storage-формата мало

Storage version решает совместимость на уровне байтового формата файла. Но иногда нужна переносимость более радикальная — поверх версий, поверх движков, даже на другие СУБД. Storage version тут не поможет: даже файл формата 64 — это всё равно специфичный для DuckDB бинарный формат, и Postgres его не прочитает.

Для такой переносимости есть другой инструмент — логический дамп через EXPORT DATABASE и IMPORT DATABASE. Он выгружает базу не как бинарный файл, а как набор Parquet/CSV-файлов с данными плюс SQL-скрипт со схемой. Это переносимо несравнимо шире, чем любой бинарный формат, — и этому посвящён следующий, последний урок модуля. Здесь же зафиксируем границу: storage version — про совместимость бинарного формата между версиями DuckDB; логический экспорт — про переносимость данных за пределы этой бинарной совместимости вообще.

Попробуй сам

Поэкспериментируйте с версиями формата.

  1. Узнайте свою версию DuckDB: duckdb --version. По таблице из урока определите, какой storage version она пишет по умолчанию.
  2. Создайте обычную базу: duckdb v_default.duckdb "CREATE TABLE t AS SELECT range AS n FROM range(1000);". Прочитайте storage version прямо из файла: hexdump -C v_default.duckdb | head -1 и переведите байт по смещению 0x0C в десятичную систему. Совпало с ожидаемым по таблице?
  3. Создайте базу в намеренно старом формате: duckdb -storage-version v1.2.0 v_old.duckdb "CREATE TABLE t AS SELECT range AS n FROM range(1000);". Снова прочитайте storage version из заголовка через hexdump. Какое теперь число? Должно быть 65.
  4. Откройте обе базы своим текущим DuckDB — обе должны открыться без проблем (backward-совместимость: новый движок читает оба формата).
  5. Поразмышляйте над сценарием: у вас 1.5, у коллеги 1.3. В каком формате нужно записать файл, чтобы коллега его открыл? А если бы у коллеги было 1.5, а у вас 1.3 — смогли бы вы открыть его файл, и почему это уже не гарантировано?

Этот эксперимент показывает, что storage version — это видимое в байтах файла число, которым можно управлять, и что выбор формата — это выбор между новыми возможностями и совместимостью со старым движком.

Эволюция ORC-формата: схема и добавление новых колонок
Проверка знанийKnowledge check
Чем backward-совместимость storage-формата DuckDB отличается от forward-совместимости, почему одна гарантирована, а другая нет, и зачем нужен параметр STORAGE_VERSION?
ОтветAnswer
Backward-совместимость — это способность нового DuckDB открыть файл, записанный старым DuckDB; она гарантирована начиная с версии v0.10, то есть для всех файлов storage version 64 и новее. Гарантировать её можно потому, что новый движок просто содержит код чтения всех старых форматов (64, 65, 66, 67, 68) — старый формат для него подмножество умеемого, нужно лишь не выбрасывать этот код. Forward-совместимость — обратное направление, способность старого DuckDB открыть файл нового формата — лишь best-effort, без гарантий. Гарантировать её невозможно по логике: новый формат может содержать структуры (новую схему сжатия, новый тип, новую раскладку метаданных), которых на момент выпуска старого движка не существовало, и кода для их разбора в уже выпущенном бинарнике быть не может. Обычно старый DuckDB, увидев в заголовке незнакомо большой storage version, аккуратно откажется открывать файл и попросит обновиться — для этого storage version и лежит в заголовке, чтобы несовместимость обнаружилась сразу. Параметр STORAGE_VERSION (через ATTACH или CLI-флаг -storage-version) нужен, чтобы намеренно записать файл в более старом формате: новый DuckDB умеет писать не только родной формат, но и старые, сознательно не используя новые структуры. Это решает задачу обмена файлами со старым движком — ценой отказа от возможностей новых версий формата, осознанный обмен фич на совместимость.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Какой storage version соответствует DuckDB 1.4.x?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 7