ESPNHow a mass Man City player exodus would warp the transfer marketPunchNGF mourns victims of Ondo aircraft crashDaily MaverickSHARE WITH US: Online schools in South Africa: what should parents know?The Jerusalem PostFormer German spy chief detained on suspicion of espionage and treason, Bild reportsZDF heuteEntdecken Sie das ZDF-NachrichtenstudioSouth China Morning PostGreenpeace catches Hong Kong geopark ‘golden week’ visitors damaging marine lifeCapital FMGovt fast-tracks passport decentralisation as Malindi, Nyeri offices near completionNHK 社会JR東日本 大雨災害を受け運転規制のあり方を検証へNPRVietnamese police arrest 12 suspected of prowling city streets at night, snatching cats for meatکیهان لندنرئیس پیشین سرویس اطلاعات خارجی آلمان به اتهام جاسوسی بازداشت شدABC NewsTrump says his super PAC will now pay for controversial taxpayer-funded promo adsAntara NewsIndonesia targets Rp3,839 tln in downstreaming investment through 2029
The Daily Newsstand · Free, Always
Tuesday, October 6, 2026

Поддержка YDB в Ptah 0.13.0: описание схемы в HCL и управление миграциями

Translate

Привет, Хабр! Меня зовут Денис, я разрабатываю Ptah — открытый инструмент для управления схемами баз данных и миграциями. В версии 0.13.0 появилась нативная поддержка YDB. В этой статье я рассмотрю, как описать таблицы и индексы в HCL, получить из этого описания миграции и проверить результат их применения. Я уже писал о Ptah на Хабре ранее, но это была общая статья. Здесь же я сфокусируюсь именно на YDB.

В блоге YDB уже выходили материалы о Goose, Liquibase и Flyway. Продолжим эту тему, но начнём не с написания очередного SQL-файла, а с описания состояния, к которому нужно привести базу. Для практического примера возьмём таблицу эпизодов сериалов, использованную в данных публикациях.

После основной части разберём особенности интеграции: работу с версиями YDB, чтение схемы, выполнение DDL и распределённую блокировку. Отдельно рассмотрим объекты для потоковой обработки и федеративных запросов, которым посвящена ещё одна статья команды YDB.

Введение

Предположим, мы разрабатываем сервис с каталогом сериалов. Сначала ему достаточно хранить названия эпизодов и даты выхода. Затем появляется поиск, мягкое удаление, журнал просмотров и обработка изменений в других сервисах. Вместе с приложением меняется схема базы данных.

Для управления такими изменениями обычно используется история миграций. Разработчик записывает необходимые операции, а инструмент выполняет их в заданном порядке и сохраняет сведения о применённых версиях. Так можно обновлять окружения постепенно, не создавая базу заново при каждом релизе.

Ptah поддерживает этот процесс, но позволяет автоматизировать подготовку миграций. Вместо списка действий разработчик описывает нужные таблицы, колонки и индексы. Инструмент читает текущее состояние базы, сравнивает его с описанием и рассчитывает переход.

Полученный план можно использовать двумя способами:

HCL ───────────────┐
                   ├─ Сравнение ─ План ─┬─ Прямое применение
Схема из YDB ──────┘                    └─ Файлы миграций → ревью → применение

Например, добавление блока index означает, что у таблицы должен появиться индекс. Если таблицы ещё нет, Ptah создаст её вместе с индексом. Если таблица уже существует, подготовит операцию добавления индекса. Решение зависит от исходного состояния, а не от того, в какой момент разработчик написал блок HCL.

При этом декларативное описание не заменяет историю миграций. В одном файле удобно видеть текущую структуру базы, а в миграциях — переходы, через которые она к ней пришла. Оба варианта используют один механизм сравнения и планирования. Подробнее об их устройстве — в описании рабочих процессов Ptah.

Что вошло в поддержку YDB

Интеграция в 0.13.0 охватывает не только обычные таблицы. Ptah читает объекты YDB, сравнивает их определения, формирует изменения и выполняет миграции с учётом возможностей сервера.

Объекты

Что учитывается при управлении схемой

Строковые и колоночные таблицы

Колонки, ключи, типы, допустимые defaults и настройки хранения

Индексы

Глобальные, уникальные, асинхронные, покрывающие, векторные, полнотекстовые и локальные индексы колоночных таблиц

Хранение данных

TTL, многоуровневый TTL колоночных таблиц, column families, партиционирование и read replicas

Представления

Текст запроса, зависимости и комментарии

Топики и changefeeds

Параметры потока, срок хранения, потребители и связанные с ними изменения

Внешние источники, таблицы и секреты

Определения доступа к другим системам, форматы данных и ссылки на значения секретов

Репликации и transfers

Конфигурация и зависимости от исходных и целевых объектов

Объекты доступа и управления

Пользователи, группы, права, coordination nodes, resource pools, classifiers и streaming queries

Полный справочник находится в документации YDB для Ptah 0.13.0. Доступность конкретной операции зависит от версии YDB и настроек кластера.

Важное различие для практической части: возможности адаптера и возможности формата описания — не одно и то же. В HCL 0.13.0 можно описать таблицы, обычные и векторные индексы, параметры партиционирования индексов, views и coordination nodes. Для ряда других объектов, например топиков, TTL и внешних источников, HCL-представления пока нет. Их поддержку рассмотрим отдельно, со ссылками на соответствующие разделы документации.

Установка и подключение

Для примеров нужен Ptah 0.13.0. Готовые архивы доступны на странице релиза, остальные способы установки описаны в инструкции. Драйвер YDB уже входит в состав утилиты: отдельно устанавливать JDBC-драйвер или собирать приложение на Go не требуется.

Запустим отдельную локальную YDB:

docker run -d --rm \
  --name ptah-ydb-demo \
  --hostname localhost \
  -p 127.0.0.1:2136:2136 \
  -p 127.0.0.1:8765:8765 \
  -e GRPC_PORT=2136 \
  -e MON_PORT=8765 \
  -e YDB_USE_IN_MEMORY_PDISKS=true \
  ydbplatform/local-ydb:26.2.1.14

Версия образа зафиксирована, чтобы обновление latest не изменило условия примера. Порты доступны только с локальной машины. Постоянный том здесь не подключён: после удаления контейнера данные не сохранятся. На ARM для этого образа может потребоваться эмуляция linux/amd64. Параметры запуска перечислены в документации контейнера YDB.

Создадим рабочий каталог и зададим строку подключения:

mkdir ptah-ydb-example
cd ptah-ydb-example
mkdir migrations

export YDB_URL='ydb://localhost:2136/local'

В URL указаны адрес сервера и путь базы /local. Схема ydb:// использует обычный gRPC, а ydbs:// — соединение с TLS. После готовности контейнера проверим подключение:

ptah db capabilities --db-url "$YDB_URL"

Команда покажет возможности подключённой YDB. Для защищённого сервера используются учётные данные пользователя, токен либо стандартное окружение YDB SDK. Способы аутентификации описаны в разделе Connecting.

Дальше работаем именно с этой отдельной базой. Описание одной демонстрационной таблицы не следует применять как полную схему общей базы приложения.

Практическая часть проверена на Ptah 0.13.0 и YDB 26.2.1.14. Основные команды выполнялись бинарным файлом для macOS arm64, сервер работал в отдельном Linux-контейнере. Команды CI с временной YDB выполнялись на Linux amd64. Ниже приведены фрагменты полученного вывода.

Создание первой таблицы

В примере Liquibase создаётся таблица episodes с составным первичным ключом и индексом по названию. Опишем ту же предметную модель в HCL.

Создадим файл schema.hcl:

schema "media" {}

table "episodes" {
  schema = schema.media

  column "series_id" {
    type = sql("Uint64")
    null = false
  }

  column "season_id" {
    type = sql("Uint64")
    null = false
  }

  column "episode_id" {
    type = sql("Uint64")
    null = false
  }

  column "title" {
    type = text
    null = true
  }

  column "air_date" {
    type = sql("Timestamp64")
    null = true
  }

  primary_key {
    columns = [column.series_id, column.season_id, column.episode_id]
  }

  index "episodes_title_idx" {
    columns = [column.title]
  }
}

Блок table описывает таблицу, column — колонку. В primary_key перечислены колонки первичного ключа, а в index — ключ вторичного индекса. Ссылки вида column.title указывают на колонки этой таблицы.

В YDB нет SQL-схем в привычном для PostgreSQL смысле. Блок schema "media" соответствует каталогу, поэтому таблица получит путь media/episodes внутри базы /local. Имена в YDB чувствительны к регистру.

Тип text будет преобразован в Utf8. Через sql("Uint64") и sql("Timestamp64") мы указали собственные типы YDB. Здесь sql(...) задаёт имя типа в HCL, а не выполняет запрос. Синтаксис этого формата описан в справочнике HCL.

Сначала проверим файл без подключения к базе:

ptah schema validate \
  --schema-file schema.hcl \
  --dialect ydb \
  --server-version 26.2.1.14

Затем посмотрим, какой YQL получается из описания:

ptah schema render \
  --schema-file schema.hcl \
  --dialect ydb \
  --server-version 26.2.1.14

render показывает создание описанной схемы. Эта команда полезна для проверки типов и синтаксиса, но она не определяет, что уже существует в базе. Для этого нужен план миграции.

Получение плана

ptah migrations plan \
  --schema-file schema.hcl \
  --db-url "$YDB_URL"

Ptah прочитает схему из YDB и сравнит её с файлом. Поскольку база пустая, план содержит создание таблицы и её индекса. Команда ничего не выполняет и не записывает файлы миграций.

У YDB нет отдельной инструкции CREATE INDEX: индекс новой таблицы задаётся внутри CREATE TABLE, а индекс существующей — через ALTER TABLE ... ADD INDEX. Для HCL это не меняет способ описания. Нужную форму выбирает планировщик.

Генерация файлов

Сохраним план в виде версионной миграции:

ptah migrations generate \
  --schema-file schema.hcl \
  --db-url "$YDB_URL" \
  --migrations-dir ./migrations \
  --name create_episodes

В каталоге появятся два файла с общей версией: *.up.sql для применения и *.down.sql для обратного перехода. Их содержимое можно проверить на ревью вместе с изменением schema.hcl.

После проверки зафиксируем контрольные суммы:

ptah migrations hash --dir ./migrations

В результате создаётся или обновляется ptah.sum. Генерация и подсчёт контрольных сумм разделены, чтобы в сумму попали именно те файлы, которые прошли ревью. В Git сохраняются описание схемы, обе миграции и ptah.sum.

Подробности форматов файлов и генерации приведены в Generate migrations.

Применение и проверка результата

ptah migrations up \
  --db-url "$YDB_URL" \
  --migrations-dir ./migrations \
  --verify-sum \
  --migration-lock-timeout 30s

--verify-sum включает проверку файлов перед применением. --migration-lock-timeout ограничивает ожидание другого мигратора, который работает с той же базой.

После выполнения в YDB появится таблица media/episodes, а мигратор сохранит запись о применённой версии. Повторный запуск migrations up не выполняет эту версию заново.

Посмотреть историю можно отдельной командой:

ptah migrations status \
  --db-url "$YDB_URL" \
  --migrations-dir ./migrations

Теперь сравним фактическую схему с HCL:

ptah schema compare \
  --schema-file schema.hcl \
  --db-url "$YDB_URL"

После успешного применения различий быть не должно. Это уже проверка структуры базы, а не только записи в истории миграций.

В нашем запуске migrations status показал:

Total Migrations: 1
Applied Migrations: 1
Pending Migrations: 0

Повторный migrations up завершился с сообщением Database is already up to date!, а schema compare — с No schema differences detected. Все три команды вернули код 0.

Эволюция таблицы episodes

Добавим мягкое удаление эпизодов и уберём индекс по названию. В декларативном варианте для этого меняется сам файл схемы: добавляется колонка, а блок ненужного индекса удаляется.

Полный обновлённый schema.hcl:

schema "media" {}

table "episodes" {
  schema = schema.media

  column "series_id" {
    type = sql("Uint64")
    null = false
  }

  column "season_id" {
    type = sql("Uint64")
    null = false
  }

  column "episode_id" {
    type = sql("Uint64")
    null = false
  }

  column "title" {
    type = text
    null = true
  }

  column "air_date" {
    type = sql("Timestamp64")
    null = true
  }

  column "is_deleted" {
    type    = bool
    null    = false
    default = false
  }

  primary_key {
    columns = [column.series_id, column.season_id, column.episode_id]
  }
}

Для новой обязательной колонки указан default = false. Он задаёт значение и для уже существующих строк. Возможность добавления колонки с default проверяется для конкретного сервера. В примере используется YDB 26.2.

Получим следующую миграцию:

ptah migrations plan \
  --schema-file schema.hcl \
  --db-url "$YDB_URL"

ptah migrations generate \
  --schema-file schema.hcl \
  --db-url "$YDB_URL" \
  --migrations-dir ./migrations \
  --name add_episode_soft_delete

Теперь в план попадёт только разница: добавление is_deleted и удаление episodes_title_idx. Остальная таблица уже соответствует описанию.

В полученном файле *.up.sql оказались именно эти операции:

ALTER TABLE `media/episodes` DROP INDEX `episodes_title_idx`;
ALTER TABLE `media/episodes` ADD COLUMN `is_deleted` Bool NOT NULL DEFAULT false;

Перед применением мы записали в таблицу два эпизода — Pilot и Second episode. Так проверка затрагивает и существующие строки, а не только пустую таблицу.

После ревью повторим применение:

ptah migrations hash --dir ./migrations

ptah migrations up \
  --db-url "$YDB_URL" \
  --migrations-dir ./migrations \
  --verify-sum \
  --migration-lock-timeout 30s

При таком процессе HCL хранит актуальное устройство таблицы, а каталог миграций — последовательность её изменений. Чтобы узнать, какие поля нужны приложению сейчас, достаточно открыть описание схемы.

После применения прочитали строки через YDB CLI. Названия, ключи и даты выхода сохранились. Новая колонка получила значение по умолчанию:

episode_id

title

is_deleted

1

Pilot

false

2

Second episode

false

В истории теперь две применённые миграции. Повторное применение не добавило новую запись, а сравнение с обновлённым HCL снова не нашло различий.

Для преобразований данных, которые нельзя получить из сравнения схем, остаются обычные миграции. Например, решить, какие эпизоды следует скрыть по условиям лицензионного договора, должен код приложения или специально подготовленная миграция. Из появления колонки is_deleted это правило не следует.

Типы данных и переносимость

В HCL можно использовать как общие SQL-типы, так и собственные имена YDB. Несколько примеров сопоставления:

Тип в HCL

Представление в YDB

text, varchar(255)

Utf8

sql("String"), sql("BYTEA")

String, то есть байты

bool

Bool

smallint, integer, bigint

Int16, Int32, Int64

sql("Uint64")

Uint64

decimal(22, 9)

Decimal(22,9)

timestamp

Timestamp64 либо Timestamp, в зависимости от линии YDB

json, jsonb

Json, JsonDocument

Общее имя удобно для схем, которые используются с несколькими СУБД. Собственный тип делает намерение явным: например, Uint64 сразу указывает, что значение беззнаковое.

При этом одинаковое описание не всегда означает одинаковые ограничения. В YDB нет строки с ограничением длины на уровне типа: varchar(255) станет Utf8, но сервер не начнёт отклонять строки длиннее 255 символов. Проверка с --no-skipped сообщает о таких свойствах. Для проверки скопировали описание в schema-length.hcl и заменили тип колонки title с text на varchar(255):

ptah schema validate \
  --schema-file schema-length.hcl \
  --dialect ydb \
  --server-version 26.2.1.14 \
  --no-skipped

Команда завершилась с кодом 1 и указала конкретное свойство:

ydb: column "media.episodes.title": type modifier=varchar(255): length 255 would be skipped

Для типов, у которых нет подходящего представления, возвращается ошибка. Аналогично проверяются ограничения и операции, которых нет у выбранной YDB. Это позволяет обнаружить проблему до применения миграции. Полная таблица находится в разделе Tables and types.

Работа с индексами

Асинхронные и покрывающие индексы

Предположим, сервису нужна выдача эпизодов по дате выхода. Внутрь таблицы episodes можно добавить такой блок:

index "episodes_air_date_idx" {
  columns = [column.air_date]
  include = [column.title]
  type    = "async"

  auto_partitioning_by_load              = "ENABLED"
  auto_partitioning_min_partitions_count = 4
  auto_partitioning_max_partitions_count = 32
}

columns задаёт ключ индекса. В include перечисляются дополнительные данные, которые должны храниться в нём (для YDB это COVER). Подходящий запрос сможет получить название эпизода из индекса без дополнительного чтения этой колонки из основной таблицы.

type = "async" выбирает асинхронное обновление. Оно допускает задержку относительно основной таблицы, что нужно учитывать в требованиях к свежести выдачи. Для обычного синхронного индекса этот атрибут можно убрать.

Остальные параметры относятся к партиционированию самого индекса. В YDB глобальный индекс имеет собственное хранение, поэтому его настройки не обязаны совпадать с настройками основной таблицы.

При изменении параметров Ptah сохраняет согласованный набор связанных значений. Это важно, поскольку изменение одного переключателя автоматического разбиения может сбросить другой параметр, например минимальное число партиций. Если настройка индекса пропущена в HCL, её действующее значение сохраняется.

Перечень атрибутов и правила изменения описаны в Index partitioning.

Уникальность и переименование

Для уникального индекса используется unique = true. В YDB такой индекс создаётся как глобальный уникальный синхронный индекс. Добавление его к уже существующей таблице может требовать включённого EnableAddUniqueIndex, поэтому создание вместе с таблицей и последующее добавление проверяются отдельно.

Если изменилось только имя индекса, а ключ, тип, покрывающие колонки и уникальность остались прежними, Ptah может использовать переименование. Индекс не нужно удалять и строить заново. Возможность такого перехода также зависит от настроек сервера.

План изменения индекса получается теми же командами migrations plan и migrations generate, которые использовались для колонок. Дополнительные правила приведены в документации индексов.

Векторный индекс

Теперь добавим поиск по смыслу описания эпизода. Для этого понадобятся документы с текстом и заранее рассчитанными векторами. Следующий блок таблицы можно добавить в тот же schema.hcl, где уже объявлена схема media:

table "search_documents" {
  schema = schema.media

  column "id" {
    type = sql("Uint64")
    null = false
  }

  column "body" {
    type = text
    null = false
  }

  column "embedding" {
    type = sql("String")
    null = true
  }

  primary_key {
    columns = [column.id]
  }

  index "search_documents_embedding_idx" {
    columns = [column.embedding]
    type    = "vector_kmeans_tree"

    distance         = "cosine"
    vector_type      = "float"
    vector_dimension = 1536
    levels           = 2
    clusters         = 128
  }
}

Вектор хранится в бинарной колонке String. Параметры индекса задают размерность, тип элементов, метрику расстояния и устройство дерева. В примере используется косинусное расстояние и векторы из 1536 элементов типа float.

Размерность относится к индексу: колонка String сама по себе не проверяет длину записанного вектора. Поэтому приложение должно формировать данные в формате, который ожидает YDB. Вектор другой размерности может не попасть в индекс.

При изменении метрики или размерности меняется определение индекса, и это учитывается при сравнении. Отдельно проверяются возможности версии: наличие команды создания ещё не означает одинакового поведения индекса на всех линиях YDB.

Поддержка векторных индексов входит в релиз 0.13.0. Отдельный механизм ptah inference для YDB в этот релиз не включён (его развитие ведётся в задаче #4181). Устройство индекса описано в Vector indexes.

Представления и coordination nodes

Для чтения каталога без удалённых эпизодов добавим view:

view "published_episodes" {
  schema = schema.media

  as = <<-YQL
    SELECT
        series_id,
        season_id,
        episode_id,
        title,
        air_date
    FROM `media/episodes`
    WHERE NOT is_deleted
  YQL
}

Описание остаётся в HCL, а тело запроса записывается на YQL. При создании Ptah добавляет требуемый YDB параметр security_invoker = TRUE.

Изменение текста представления в YDB выполняется через пересоздание: обычного ALTER VIEW для этого нет. Планировщик учитывает зависимость от исходной таблицы, чтобы создать объекты в подходящем порядке. Подробнее — в Views.

Другой поддерживаемый HCL-объект — coordination node. Он может понадобиться приложению, например для координации фоновых обработчиков:

coordination_node "media" "workers" {
  self_check_period     = "PT2S"
  read_consistency_mode = "strict"
}

Два имени в заголовке задают каталог media и узел workers. Ptah управляет конфигурацией узла. Сессии и семафоры, которые приложение создаёт в процессе работы, остаются рабочим состоянием приложения, а не частью декларации схемы.

Для собственных миграционных блокировок Ptah использует отдельный служебный узел ptah_locks. Его не нужно добавлять в файл. HCL-форма пользовательского узла описана в справочнике.

Все дополнения из этих разделов собраны в одном полном файле. Применили его следующей миграцией и прочитали схему обратно из YDB:

Объект

Что вернулось при чтении

episodes_air_date_idx

GLOBAL ASYNC, покрывающая колонка title, разбиение по нагрузке и границы от 4 до 32 партиций

search_documents_embedding_idx

vector_kmeans_tree, cosine, float, размерность 1536, 2 уровня и 128 кластеров

media/published_episodes

Запрос к media/episodes с условием NOT is_deleted

media/workers

self_check_period = "PT2S", read_consistency_mode = "strict"

После применения schema compare не нашёл различий, а повторный schema apply ответил Schema is synced, no changes to be made. Для view проверили и результат запроса: сначала оно вернуло оба эпизода, после установки is_deleted = true у второго — только Pilot. Сама строка второго эпизода осталась в таблице. Векторный индекс в этом опыте проверялся на создание и сохранение параметров, без оценки качества поиска.

Прямое применение и поиск расхождений

В локальной разработке бывает удобнее обновлять базу прямо из HCL, не создавая отдельную миграцию для каждого промежуточного изменения:

ptah schema apply \
  --schema-file schema.hcl \
  --db-url "$YDB_URL"

Команда сравнивает описание с базой, показывает план и применяет его после подтверждения словом YES. Если состояние уже совпадает, выполнять нечего. В проекте с версионной доставкой это альтернативный процесс: прямое применение не добавляет новую версию в каталог миграций.

Сравнение полезно и независимо от способа применения. Допустим, все миграции выполнены, но затем администратор вручную удалил индекс. Запись о выполнении миграции осталась, контрольные суммы файлов тоже не изменились. Однако фактическая схема больше не соответствует ожидаемой.

Для проверки такого расхождения, или drift, используется:

ptah schema drift \
  --schema-file schema.hcl \
  --db-url "$YDB_URL"

Команда возвращает 0, когда расхождений нет, и 1, когда они обнаружены. Её можно включить в проверку окружения. В версионном процессе базу следует сравнивать с HCL установленного релиза, а не с ещё не выпущенными изменениями основной ветки.

Проверим это на созданной базе: удалим через YDB CLI индекс episodes_air_date_idx, не меняя HCL и файлы миграций. В нашем прогоне migrations status по-прежнему показал три применённые миграции и ни одной ожидающей. Но schema drift вернул код 1:

Schema drift detected (highest severity: warning).
Failure threshold: all. Failing: true.
Database: ydb://localhost:2136/local

Findings:
- indexes_added: 1 (warning)

indexes_added здесь означает индекс, который нужно добавить, чтобы вернуть базу к желаемому состоянию. После подтверждения плана schema apply восстановил индекс и его настройки. Следующий schema drift вернул код 0 и сообщение No schema drift detected.

Таким образом, migrations status показывает состояние истории, а schema compare и schema drift — соответствие самой схемы. Эти проверки решают разные задачи. Подробности — в Work with a desired schema.

Подготовка миграций в CI

До сих пор для генерации следующего изменения мы читали схему из запущенной базы. В CI можно обойтись без подключения к рабочему окружению: воспроизвести историю из Git на временной YDB и использовать её как исходное состояние.

После следующего изменения schema.hcl выполним:

ptah migrations generate \
  --replay \
  --dev-url 'docker://ydb/26.2.1.14/local' \
  --schema-file schema.hcl \
  --migrations-dir ./migrations \
  --name next_change

Ptah поднимет временный сервер, применит существующие миграции, прочитает полученную схему и сравнит её с HCL. Если разницы нет, новый файл не нужен. --db-url в этом режиме не используется: исходным состоянием служит история миграций.

Для этого запуска добавили в episodes необязательную колонку editor_note типа text. Исходным состоянием была история из трёх предыдущих миграций. После её воспроизведения генератор записал в новую миграцию один оператор:

ALTER TABLE `media/episodes` ADD COLUMN `editor_note` Utf8;

Таблицы и индексы из уже применённой истории в этот переход повторно не попали.

Миграции из Git → временная YDB → прочитанная схема
                                         ↓
Новый HCL ───────────────────────────→ сравнение → новая миграция

После ревью новых файлов обновим контрольные суммы и выполним проверки:

ptah schema fmt --check schema.hcl

ptah migrations hash --dir ./migrations

ptah migrations validate --dir ./migrations

ptah migrations validate \
  --dir ./migrations \
  --dev-url 'docker://ydb/26.2.1.14/local'

ptah migrations lint \
  --dir ./migrations \
  --dialect ydb \
  --server-version 26.2.1.14

В проверяющем CI-задании выполняются fmt, validate и lint, а не повторный hash: иначе изменение истории можно случайно закрепить новой контрольной суммой. hash относится к подготовке проверенного изменения.

Проверки дополняют друг друга. fmt следит за оформлением HCL, validate проверяет каталог и при указанной dev-базе воспроизводит миграции, а lint анализирует потенциально проблемные операции. Для YDB это, например, удаление колонки, используемой индексом или TTL, добавление обязательной колонки без подходящего default и изменение настроек разбиения.

Для ревью можно сохранить и отчёт о рисках: migrations generate --report html записывает его рядом с миграциями. Доступны также структурированные отчёты для автоматических проверок. Примеры находятся в документации генерации.

Временная YDB позволяет проверить исполнение на своей версии и конфигурации. Проверки преобразования реальных данных и совместимости приложения добавляются к этому процессу отдельно.

Проверка полученной истории на временной YDB завершилась с кодом 0:

OK: migrations directory matches ptah.sum
OK: migration SQL validated on dev database

Затем повторили генерацию с тем же HCL и обновлённой историей. Новых файлов не появилось. fmt --check и lint также завершились с кодом 0. Вывод линтера — No lint findings. После команд временных контейнеров YDB не осталось.

Управление специальными объектами YDB

Кроме рассмотренных таблиц и индексов, интеграция поддерживает специальные объекты YDB. Разберём, где они могут понадобиться нашему сервису и какие зависимости появляются при их изменении.

Партиционирование, TTL и column families

Для журнала просмотров можно задать автоматическое разбиение таблицы по размеру и нагрузке, минимальное и максимальное число партиций, а также дополнительные реплики для чтения. Параметры таблицы и параметры её индексов сравниваются отдельно.

TTL подходит для событий, которые нужны ограниченное время. Например, журнал может хранить просмотры за последние 30 дней. В описании задаются колонка времени и интервал. Для целочисленного времени также указывается единица измерения. Ptah нормализует интервалы, поэтому P30D и PT720H не создают различие сами по себе.

При изменении таблицы учитываются зависимости. Сначала нужно убрать TTL, который использует колонку, и только затем удалять саму колонку. А сокращение срока хранения требует внимания к данным: оно меняет не только параметры объекта, но и набор записей, которые останутся в базе.

Column families позволяют раздельно настраивать хранение групп колонок строковой таблицы. Например, для объёмного содержимого события можно выбрать сжатие и подходящий storage pool. В модели сохраняются и настройки семьи, и принадлежность колонок.

В HCL 0.13.0 эти свойства таблицы не объявляются. При работе с существующей базой их отсутствие в HCL не означает команду удалить TTL или column families: они сохраняются. Экспорт сообщает, какие свойства не вошли в файл.

Проверили это для TTL на отдельной таблице media/view_events: создали её с политикой удаления через 30 дней по колонке created_at, затем экспортировали схему в HCL. На стандартный поток ошибок пришло предупреждение:

warning: table.media.view_events: row deletion policy (TTL P30D on created_at) is not represented in HCL

В полное HCL-описание добавили колонки и ключ этой таблицы, без TTL. Применение ответило Schema is synced, no changes to be made. Результат SHOW CREATE TABLE до и после совпал, включая TTL = INTERVAL('P30D') DELETE ON created_at.

Подробнее: TTL, column families и партиционирование таблиц.

Колоночные таблицы и полнотекстовые индексы

Для аналитики по истории просмотров можно использовать колоночное хранение. Ptah поддерживает колоночные таблицы, хеш-партиционирование, локальные индексы и многоуровневый TTL. В последнем случае данные могут сначала перемещаться во внешнее хранилище, а затем удаляться по следующему правилу.

Локальные индексы min_max, bloom_filter и bloom_ngram_filter отличаются от глобальных индексов строковой таблицы. Они помогают исключать ненужные блоки данных при чтении. Поэтому планировщик учитывает тип хранения и допустимые для него операции, а не применяет одинаковые правила к любым таблицам.

Для текстового поиска в строковых таблицах поддержаны fulltext_plain и fulltext_relevance. В сравнении участвуют параметры анализатора: токенизатор, язык и фильтры. Изменение этих настроек влияет на поиск и может потребовать перестроения индекса.

Форматы деклараций, настройки хранения и параметры анализатора приведены в справочнике YDB 0.13.0.

Топики и changefeeds

В статье о Liquibase специальная функциональность YDB показана на примере создания топика через YQL. Ptah также умеет выполнять такие миграции, а его модель схемы дополнительно включает определения топиков и потребителей.

Для сервиса с каталогом эпизодов топик может хранить события просмотров. В его конфигурацию входят разбиение на партиции, срок хранения, кодеки и потребители. При изменении описания Ptah определяет, какие параметры можно обновить на месте.

Другой вариант — получать события непосредственно об изменениях таблицы. Для этого используется changefeed. Например, поисковый сервис может читать изменения episodes и обновлять свой индекс. Топик такого потока принадлежит таблице и расположен по пути вида media/episodes/updates. Описывать его второй раз как независимый топик не требуется.

В планировании важен жизненный цикл. Некоторые изменения требуют пересоздания потока или потребителя. Это может привести к потере непрочитанных сообщений или позиции чтения, и соответствующие операции отражаются в оценке риска. Удаление топика также нельзя считать изменением только метаданных: вместе с ним исчезают сообщения.

Поддерживаемые определения и операции приведены в разделах о топиках и changefeeds.

Федеративные источники и внешние таблицы

Предположим, основной каталог находится в YDB, справочник правообладателей — в PostgreSQL, а архив событий — в объектном хранилище. Для анализа может понадобиться обращаться к этим данным совместно, не перенося весь архив в YDB.

Устройство такого доступа подробно разобрано в статье «Как работают федеративные системы: рассказываем на примере YDB». Здесь нас интересует конфигурация доступа: её тоже нужно воспроизводить между окружениями и изменять вместе с приложением.

Ptah управляет определениями внешних источников и внешних таблиц. В них задаются адрес системы, способ аутентификации, формат данных и другие параметры. Для файлов внешняя таблица также описывает набор колонок.

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

Например, переход архива на другой бакет становится изменением описания источника. На ревью видны новый адрес и зависящие от него таблицы, а после применения можно сравнить конфигурацию окружения с ожидаемой.

Само чтение и выполнение федеративных запросов остаются задачей YDB и её коннекторов. Ptah не устанавливает коннектор, не переносит данные из PostgreSQL и не изменяет схему удалённой базы вслед за созданием объекта доступа в YDB.

Значения секретов не хранятся в декларации открытым текстом: используются ссылки на переменные окружения PTAH_SECRET_*. Поскольку сервер не возвращает сохранённое значение, ротация задаётся явно, а не определяется обычным сравнением схем.

Возможности интеграции описаны в External data sources and external tables и Secrets.

Репликации, transfers и управление доступом

Асинхронная репликация создаёт целевые таблицы сама. Ptah учитывает их принадлежность репликации и не рассматривает как независимые таблицы, которые можно произвольно перестроить по обычному плану.

Transfer связывает поток сообщений с таблицей назначения и преобразованием на YQL. Для планировщика это набор зависимостей: необходимые таблицы и топики должны существовать до создания transfer. Изменения конфигурации рассматриваются отдельно от операций переключения работающей репликации.

Пользователи, группы и разрешения также входят в модель YDB. Это позволяет анализировать доступ приложения и технических пользователей к объектам. При этом общие объекты базы имеют свои правила владения: отсутствие пользователя или resource pool в описании одного приложения не означает автоматическое удаление.

Resource pools и classifiers задают ограничения ресурсов и правила назначения запросов в пулы. Для streaming queries сохраняется постоянная конфигурация — текст, настройка запуска и пул ресурсов. Текущее состояние обработки и содержимое checkpoints не становятся частью схемы.

Полные определения этих семейств приведены в документации диалекта, а анализ доступа — в Schema security.

Как Ptah читает схему YDB

Для генерации изменений недостаточно уметь отправить на сервер CREATE TABLE. Необходимо прочитать результат и восстановить те свойства, которые участвовали в исходном описании.

Ptah подключается через YDB Go SDK и использует нативные API. Каталоги обходятся через ListDirectory, таблицы и связанные объекты читаются через соответствующие методы описания. Если высокоуровневая модель SDK не содержит нужные поля, разбирается ответ протокола. SHOW CREATE используется с учётом возможностей версии сервера.

Полученная схема переводится в ту же внутреннюю модель, что и HCL. После этого сравниваются объекты и свойства, а не текст двух SQL-файлов. Поэтому text из декларации и Utf8 из YDB можно считать одним типом, но синхронный и асинхронный индексы — разными объектами по поведению.

При чтении важно сохранить вид расширенного индекса. Векторный индекс нельзя свести к обычному глобальному только потому, что используемый уровень SDK не знает его параметров. Неизвестные свойства либо явно отмечаются, либо приводят к отказу от операции, которая могла бы их потерять.

Из этого следует и правило для экспорта: HCL-файл может не содержать всех объектов прочитанной базы. Команда сообщает о пропусках, а дальнейшее применение учитывает, что соответствующие семейства этим форматом не описаны. Нулевой diff относится к управляемой части схемы.

Описание источников метаданных и поведения экспорта находится в Reading a live database.

Версии YDB и feature flags

Одна и та же операция может быть доступна на одном кластере и недоступна на другом. Причиной бывает как версия YDB, так и включённые функции.

В Ptah для этого используются наборы capabilities. Они определяют, например, возможность добавить колонку с default, изменить default существующей колонки, создать векторный индекс или переименовать индекс. При подключении выбирается набор для версии сервера.

Дополнительно можно указать адрес monitoring endpoint:

ptah db capabilities \
  --db-url 'ydb://localhost:2136/local?monitoring=http://localhost:8765'

В этом случае Ptah уточняет возможности по feature flags конкретного кластера. Например, EnableAddUniqueIndex меняет возможность добавления уникального индекса к уже существующей таблице, а EnableMoveIndex — переименования индекса.

Без monitoring endpoint используются значения, принятые для соответствующей линии релизов. Для защищённого подключения следует указывать защищённый endpoint, к которому у пользователя есть необходимые права.

В релизе 0.13.0 сертифицированы линии YDB 25.1 и 26.2. Для остальных описанных линий существуют измеренные presets с уровнем поддержки best-effort. Это не означает одинаковый набор функций: у каждой линии свои возможности. Таблица различий приведена в What each release line does.

Проверки интеграции включают обратное чтение: создать объект, получить его описание, сравнить и применить тот же источник повторно. После первого применения повторный план должен быть пустым. Так проверяется не только принятие команды сервером, но и сохранение нужных параметров.

Проверку версии можно увидеть и без сервера: тот же schema-v3.hcl отрендерили с --server-version 25.1.4.7. Команда завершилась с кодом 2 и сообщила, что индексу search_documents_embedding_idx требуется недоступная возможность vector_indexes. Это проверка выбора preset, сервер 25.1 в этом опыте не запускался.

Распределённые блокировки и выполнение миграций

Проблема конкурентных запусков отдельно обсуждается в статье о Goose. Если несколько экземпляров приложения одновременно начинают обновлять базу, одной истории миграций недостаточно: нужно согласовать право на выполнение.

В статье о Flyway описана блокировка через специальную строку истории с installed_rank = -100. Автоматическое истечение там намеренно не используется: после отказа лидера предлагается сначала разобраться в состоянии базы.

В Ptah блокировка построена на coordination service YDB. Мигратор удерживает эксклюзивный семафор своей сессией. Второй процесс ожидает освобождения, а параметр --migration-lock-timeout задаёт допустимое время ожидания. При завершении или истечении сессии владение освобождается.

Для проверки запустили два процесса перед третьей миграцией. Первый задержали на 12 секунд в --pre-up-hook, который выполняется уже под блокировкой. Второму задали --migration-lock-timeout 2s. Он завершился с кодом 2 и сообщением:

error: error running migrations: failed to acquire migration lock for migrate up: timed out acquiring migration lock "ptah_migrate" for ydb after 2s

Первый процесс после задержки применил миграцию. В истории остались три применённые версии: конкурентный запуск не создал повторную запись. Этот опыт проверяет ожидание и отказ по таймауту. Поведение при обрыве сессии им не измерялось.

Работающий мигратор также проверяет потерю владения и не начинает следующие шаги после её обнаружения. Это координация между миграторами, использующими данный механизм, запись приложения она не останавливает. Сам по себе семафор также не является fencing token для каждого DDL-запроса.

Механизм описан в разделе о блокировках.

Порядок выполнения DDL

YDB не выполняет DDL внутри транзакции и не позволяет объединять изменение схемы и данных в один запрос. Поэтому Ptah отправляет схемные операции последовательно, отдельными запросами. Например, сначала добавляет колонку, затем создаёт использующий её индекс.

Последовательности операций с данными выполняются в транзакциях вместе с записью контрольной точки. При конфликте ABORTED повторяется соответствующая транзакция. Разбор миграционного файла учитывает блоки и другие конструкции YQL: простого разделения текста по точке с запятой здесь недостаточно.

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

Таким образом, освобождение миграционной блокировки и возможность продолжить прерванную миграцию решаются отдельно. Новому процессу не следует повторять неопределённую операцию только потому, что он получил семафор.

Параметр --statement-timeout ограничивает время запроса, но не превращает любой DDL в гарантированно отменяемый. Для части схемных операций сервер может продолжить работу после завершения клиентского ожидания. Эти случаи описаны в Migrations on YDB.

Перестройка таблицы и откат

Некоторые изменения, например смена типа колонки или состава первичного ключа, YDB не выполняет на месте. Ptah может подготовить перестройку таблицы, когда она допустима и явно разрешена через --allow-table-rebuild.

План включает создание новой таблицы, копирование данных, замену через переименование и восстановление связанных объектов. На время такого перехода нужно остановить конкурентную запись: копирование и смена имени не образуют общую транзакцию. Для больших таблиц и обновлений без остановки приложения необходим отдельный процесс переноса и переключения.

Автоматическая перестройка также не применяется к таблицам, свойства которых нельзя корректно восстановить. Например, копирование значений Serial требует отдельно решить вопрос состояния последовательности.

Обратная миграция восстанавливает доступные обратному переходу изменения структуры, но не возвращает удалённые данные. Удалённая колонка может появиться снова пустой, а пересоздание топика не восстанавливает его старые сообщения. Поэтому down не заменяет резервную копию. Правила приведены в Table rebuilds.

Сравнение с Goose, Liquibase и Flyway

Сведём рассмотренные процессы в таблицу. Для Goose, Liquibase и Flyway сравниваются подходы из приведённых публикаций, а не все возможности каждой современной редакции.

Вопрос

Goose

Liquibase

Flyway

Ptah

Что меняет разработчик

SQL- или Go-миграцию

Changeset с операциями

Следующий SQL-файл

Описание желаемой схемы, в нашем примере HCL

Как определяются действия

Их задаёт автор миграции

Автор задаёт изменения, адаптер формирует запросы

Их задаёт автор миграции

Сравниваются желаемое и исходное состояния

Как доставляется изменение

По истории миграций

По истории changeset’ов

По истории миграций

Сгенерированными миграциями либо прямым применением

Где используются особенности YDB

В SQL или коде миграции

В поддержанных операциях и нативном SQL

В SQL миграции

В модели диалекта, чтении и планировании

Что нужно для подключения в показанном варианте

Goose с поддержкой YDB

Расширение диалекта и JDBC-драйвер

Расширение диалекта и JDBC-драйвер

Бинарный файл Ptah с включённым драйвером

Liquibase при этом не ограничивается ручным написанием changeset’ов: в платформе есть команда сравнения diff. Поэтому различие не сводится к утверждению, что сравнивать схемы умеет только Ptah. Для YDB важны конкретные объекты, которые адаптер может прочитать и включить в план.

В процессе из этой статьи HCL используется сразу для нескольких задач: показать актуальную структуру, получить миграцию, проверить результат и обнаружить ручные изменения окружения. При этом сгенерированный переход остаётся обычным артефактом ревью, а не скрытым действием при старте приложения.

Это полезно, когда команда хочет уменьшить объём ручного DDL, но сохранить версионную доставку. Если же проекту удобнее полностью управлять каждым шагом самостоятельно, такой процесс можно продолжать использовать и в Ptah: ручные миграции также поддерживаются.

При переходе существующего проекта сначала нужно согласовать исходное состояние, область управления и служебные таблицы прежнего мигратора. Совместимость описания схемы не означает автоматического переноса истории Goose, Liquibase или Flyway. Два инструмента не должны независимо изменять одни и те же объекты, считая каждый свою историю единственным источником состояния.

Другие возможности

В статье использовался HCL. Кроме него, Ptah принимает SQL, включая YQL, YAML, DBML и Go-аннотации. Источники можно объединять, учитывая возможности каждого формата.

Для Atlas-подобного процесса есть ptah-compat. Работа с YDB в нём является расширением Ptah: обычный профиль её поддерживает, строгий профиль совместимости с Atlas CE — нет.

Из того же описания можно получать документацию, исследовать зависимости, собирать статистику объектов и анализировать права доступа. Поддержка данных, MCP, редакторов и CI разобрана в документации проекта.

Заключение

Мы создали таблицу эпизодов из HCL, получили первую миграцию, изменили схему и проверили результат. Затем рассмотрели индексы, представления и возможности YDB, которые требуют более специального описания: TTL, потоки изменений, колоночное хранение и федеративные источники.

В Ptah 0.13.0 работа с YDB проходит через общий процесс чтения, сравнения, планирования и применения. Для разработчика это возможность хранить актуальное описание базы рядом с кодом и получать из него проверяемые изменения. Версионные миграции при этом сохраняются, а особенности выполнения YQL учитываются в адаптере.

Поддержка и контакты

Исходный код находится в репозитории Ptah, установочные файлы — в релизе 0.13.0. В справочнике YDB собраны поддерживаемые объекты, требования к серверу и примеры.

Об ошибках и предложениях можно написать в issues проекта. Особенно полезны примеры схем и миграций из реальных проектов: по ним проще проверить не только отдельную команду, но и весь переход между версиями.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.