Миграции для ScyllaDB в Go: scyllamigrate, gocqlx/migrate, golang-migrate и goose

Практический разбор миграций для ScyllaDB в Go: scyllamigrate, gocqlx/migrate, golang-migrate и goose. Что лучше подходит для CQL, где важна зрелость экосистемы и какие риски есть у каждого подхода

Когда в Go-проекте появляется задача миграций, многие по инерции смотрят на goose, golang-migrate или похожие инструменты. Это нормальная реакция: экосистема реляционных БД давно выработала привычный сценарий с up, down, таблицей истории и запуском миграций в CI/CD.

Но со ScyllaDB ситуация чуть другая. Это не PostgreSQL и не MySQL. Здесь другой язык запросов, другая модель распределения данных и свои эксплуатационные особенности. Поэтому вопрос обычно звучит так: можно ли взять привычный goose, или для ScyllaDB лучше использовать специализированный инструмент?

Один из таких инструментов — scyllamigrate. Но если смотреть практично, реальный выбор обычно шире. Для ScyllaDB и Cassandra-совместимого мира имеет смысл держать в голове как минимум четыре варианта:

  • scyllamigrate — узкий инструмент именно под ScyllaDB;
  • gocqlx/migrate — подход к миграциям из экосистемы scylladb/gocqlx;
  • golang-migrate/migrate — зрелый универсальный инструмент, у которого есть драйвер для Cassandra / ScyllaDB;
  • goose — популярный инструмент из SQL-мира, который часто вспоминают по привычке.

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

В статье

Почему миграции для ScyllaDB отличаются от SQL-мира

Если упростить, goose решает задачу управления миграциями для SQL-диалектов. У него сильная и зрелая модель для PostgreSQL, MySQL, SQLite, ClickHouse, MSSQL и других реляционных или SQL-подобных систем. Он поддерживает SQL-миграции, Go-функции, встроенные миграции, сценарии с миграциями не по порядку и даже подстановку env-переменных внутри SQL-файлов.

ScyllaDB работает иначе:

  • вместо SQL здесь обычно используется CQL;
  • операции DDL распространяются по кластеру не мгновенно;
  • после изменения схемы важна проверка schema agreement между узлами;
  • настройки consistency и datacenter-aware routing влияют не только на чтение и запись данных, но и на безопасное выполнение миграций.

Из-за этого универсальный SQL-мигратор не всегда хорошо подходит под реальную эксплуатацию. Формально он может быть знакомым и удобным, но у него нет знания о специфике ScyllaDB “из коробки”. Для реальной эксплуатации это важнее, чем кажется на старте.

Что умеет scyllamigrate

scyllamigrate позиционируется как лёгкая, но функциональная библиотека миграций именно для ScyllaDB. Самое важное в ней не просто наличие команд up и down, а то, что поведение инструмента подстроено под Scylla-окружение.

Ключевые возможности:

  • загрузка миграций из директории или из fs.FS, включая go:embed;
  • поддержка up и down-миграций;
  • последовательное versioning в формате вроде 000001_create_users.up.cql;
  • поддержка файлов .cql и .sql;
  • несколько CQL-операторов в одном migration-файле;
  • хранение checksum применённых миграций;
  • консольный интерфейс и программный API;
  • ожидание schema agreement после DDL-операций;
  • поддержка datacenter-aware routing и consistency settings.

Для ScyllaDB это очень практичный набор. Особенно важны две вещи:

  1. scyllamigrate работает через Go-драйвер для Scylla/Cassandra-совместимого протокола, а не через database/sql.
  2. Инструмент явно учитывает распределённую природу изменения схемы, а не просто “отправляет SQL и считает задачу завершённой”.

Ниже типичный формат миграции:

-- 000001_create_users.up.cql
CREATE TABLE IF NOT EXISTS users (
    id UUID PRIMARY KEY,
    email TEXT,
    name TEXT,
    created_at TIMESTAMP
);

CREATE INDEX IF NOT EXISTS users_email_idx ON users (email);
-- 000001_create_users.down.cql
DROP INDEX IF EXISTS users_email_idx;
DROP TABLE IF EXISTS users;

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

Где у scyllamigrate риски и ограничения

Именно здесь и проходит граница между “интересный инструмент” и “надёжный выбор для реальной эксплуатации”. У scyllamigrate сильная идея, но есть несколько практических рисков, о которых лучше сказать прямо.

1. Молодость проекта

Судя по активности репозитория на момент написания — это небольшой и довольно молодой проект. Для команды это означает не “использовать нельзя”, а более высокий риск:

  • API и поведение могут меняться заметнее, чем у зрелых инструментов миграций;
  • меньше примеров из реальных рабочих сценариев;
  • выше вероятность, что часть пограничных случаев вы будете проверять на себе.

2. Небольшая экосистема

Узкоспециализированный инструмент почти всегда проигрывает массовым решениям по объёму документации, количеству обсуждений, готовых примеров и накопленной практики эксплуатации.

Если у вас уже есть принятый стандарт миграций на golang-migrate или goose, переход на отдельный инструмент, заточенный под Scylla, увеличивает стоимость поддержки.

3. Зависимость от Scylla/Cassandra-драйверного стека

Для Scylla это нормально, но эксплуатационная цена есть. Важно заранее проверить:

  • какой именно драйвер нужен в приложении и в миграциях;
  • не появится ли трение из-за replace в go.mod, если проект использует Scylla fork gocql;
  • насколько воспроизводимо собирается консольный инструмент и пайплайн вокруг него.

Это не повод отказываться от инструмента, но повод заранее снять вопросы совместимости.

Для scyllamigrate это особенно заметно: в README прямо сказано, что go install для CLI не поддерживается из-за replace на ScyllaDB fork gocql, поэтому консольный инструмент предлагается брать из GitHub Releases, Docker-образа или собирать из исходников.

4. down не означает безопасный rollback

Наличие up и down выглядит привычно, но в distributed database это легко переоценить. Для ScyllaDB rollback далеко не всегда так же безопасен и предсказуем, как в реляционном мире.

Опасные зоны:

  • удаление колонок, индексов и materialized views;
  • несовместимость схемы со старой версией приложения;
  • частично применённые разрушающие изменения схемы;
  • ложное ощущение надёжности вида “раз есть down, значит откат всегда реалистичен”.

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

5. Хорошая идея под Scylla не заменяет проверки на стенде

Даже если инструмент ждёт schema agreement, это решает только часть проблемы. Он не отменяет:

  • тестирование миграций на стенде, похожем на боевое окружение;
  • проверку совместимости старой и новой версии приложения со схемой;
  • контроль длительных DDL-операций;
  • аккуратную стратегию выката на multi-node и multi-DC окружениях.

Отдельная практическая деталь из README scyllamigrate: целевой keyspace должен существовать заранее. Это мелочь, но на старте она легко ломает ожидания, если команда привыкла, что мигратор сам “доведёт систему до нужного состояния”.

Как выглядит рабочий процесс

С точки зрения разработчика, сценарий работы у scyllamigrate знакомый:

  1. Создаём директорию migrations/.
  2. Добавляем пары файлов *.up.cql и *.down.cql.
  3. Запускаем up, status, down, version.
  4. Храним историю применённых миграций в таблице schema_migrations.

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

scyllamigrate up -hosts=node1:9042,node2:9042 -keyspace=myapp -dir=./migrations
scyllamigrate status -keyspace=myapp
scyllamigrate down -keyspace=myapp

Если миграции нужно возить вместе с приложением, можно использовать go:embed и запускать их программно:

package main

import (
    "context"
    "embed"
    "log"

    "github.com/heartwilltell/scyllamigrate"
)

//go:embed migrations/*.cql
var migrations embed.FS

func main() {
    // Здесь намеренно опущен код инициализации драйвера.
    // Его стоит сверять с актуальным README scyllamigrate
    // и используемым в проекте вариантом gocql / scylladb-gocql.
    session := initScyllaSession()
    defer session.Close()

    migrator, err := scyllamigrate.New(
        session,
        scyllamigrate.WithFS(migrations),
        scyllamigrate.WithKeyspace("myapp"),
    )
    if err != nil {
        log.Fatal(err)
    }
    defer migrator.Close()

    if _, err := migrator.Up(context.Background()); err != nil {
        log.Fatal(err)
    }
}

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

Отдельно полезно, что в scyllamigrate есть программные операции для управления keyspace — создание и удаление. Для локальной разработки и integration-тестов это удобно: можно поднимать временное окружение, создавать keyspace, прогонять миграции и потом очищать стенд.

Но именно для реальной эксплуатации здесь стоит сделать дополнительную проверку: какой драйвер вы используете, как фиксируете его версию и не появляется ли лишняя сложность вокруг форка под ScyllaDB.

Сравнение: scyllamigrate, gocqlx/migrate, golang-migrate и goose

Главная ошибка при сравнении этих инструментов — считать, что один “лучше” другого вообще. На практике они оптимизированы под разный мир и под разный приоритет: близость к ScyllaDB, зрелость экосистемы, простоту эксплуатации или универсальность.

Критерий scyllamigrate gocqlx/migrate golang-migrate/migrate goose
Основной фокус ScyllaDB / CQL ScyllaDB / CQL Универсальный инструмент миграций SQL-миграции
Насколько подходит для ScyllaDB Высокий Высокий Средний Низкий
Зрелость проекта Низкая/средняя Средняя Высокая Высокая
Экосистема и распространённость Небольшие Средние Большие Большие
Удобство для CLI / CI Среднее Среднее Высокое Высокое
Драйверная модель Драйвер, совместимый со Scylla/Cassandra gocqlx + экосистема Scylla Драйвер для Cassandra / ScyllaDB database/sql и SQL-диалекты
Учёт особенностей ScyllaDB Сильный акцент Естественное соответствие экосистеме Scylla Не главный акцент Не главный акцент
Универсальность для разных БД Низкая Низкая/средняя Высокая Высокая
Риск лишней зависимости от стека Средний/высокий Средний Низкий/средний Низкий
Лучший сценарий Отдельный инструмент под ScyllaDB Уже живёте в gocqlx Нужна зрелость и большая экосистема SQL-мир без Scylla

Как читать эту таблицу

  • scyllamigrate выигрывает по идейной близости к ScyllaDB, но проигрывает по зрелости.
  • gocqlx/migrate выглядит естественно, если проект уже стоит на gocqlx.
  • golang-migrate выглядит самым сильным компромиссом между зрелостью и поддержкой драйвера для Scylla/Cassandra.
  • goose остаётся хорошим инструментом, но не выглядит лучшей базовой ставкой именно для ScyllaDB.

Где силён golang-migrate

У golang-migrate/migrate есть важное преимущество: это зрелый и массово используемый инструмент миграций, у которого при этом есть драйвер для Cassandra / ScyllaDB. Это делает его сильным кандидатом там, где важнее доверие к экосистеме, стабильный консольный интерфейс, понятный сценарий для CI/CD и предсказуемость поддержки.

Если сформулировать коротко: это не инструмент, придуманный специально для ScyllaDB, но это уже и не просто “SQL-мир, случайно применённый к Scylla”.

Где силён gocqlx/migrate

gocqlx/migrate интересен тем, что это путь из экосистемы scylladb. Если приложение уже использует gocqlx, этот вариант часто оказывается самым естественным: меньше технологических скачков и лучшее совпадение с уже принятым драйверным стеком.

Его главный плюс не в максимуме возможностей, а в экосистемной близости.

Но здесь есть важная оговорка: начиная с gocqlx v3, проект официально требует replace github.com/gocql/gocql => github.com/scylladb/gocql .... То есть технологическая близость к ScyllaDB достигается ценой более жёсткой привязки к её драйверному стеку.

Где силён goose

goose хорош там, где миграции живут в SQL-мире:

  • schema changes в PostgreSQL;
  • seed-данные как SQL или Go-функции;
  • смешанный сценарий “часть миграций в SQL, часть в Go”;
  • единый инструмент на несколько реляционных проектов.

Где силён scyllamigrate

scyllamigrate хорош там, где важно, что база именно ScyllaDB:

  • CQL вместо SQL;
  • понимание специфики кластера;
  • безопасное ожидание schema agreement;
  • работа через Scylla/Cassandra-драйвер и его настройки.

Когда выбирать какой инструмент

Выбирайте scyllamigrate, если:

  • вы реально используете ScyllaDB как основную рабочую БД;
  • миграции пишутся в CQL и должны учитывать кластерную природу схемы;
  • хотите запускать миграции через gocql и управлять consistency/datacenter-настройками;
  • нужен понятный сценарий под ScyllaDB без SQL-абстракции сверху;
  • важно встроить миграции в Go-сервис или в отдельный deployment-step для Scylla;
  • вас устраивает риск более молодого проекта.

Выбирайте gocqlx/migrate, если:

  • проект уже использует gocqlx;
  • хотите остаться ближе к экосистеме scylladb;
  • не нужен максимально универсальный стандарт миграций для разных БД;
  • важнее экосистемная совместимость, чем отдельный специализированный консольный инструмент.

Выбирайте golang-migrate, если:

  • вам нужна зрелость, большая экосистема и предсказуемый рабочий сценарий;
  • вам важнее доверие к инструменту, чем максимальная близость к ScyllaDB;
  • в компании уже есть практика использования golang-migrate;
  • нужен один понятный инструмент миграций для нескольких типов хранилищ;
  • вы готовы принять, что это не самый нативный вариант для ScyllaDB.

Выбирайте goose, если:

  • у вас PostgreSQL, MySQL, SQLite или другой SQL-движок из поддерживаемых;
  • нужен зрелый универсальный инструмент миграций для нескольких сервисов;
  • вы хотите использовать не только SQL-файлы, но и Go-миграции;
  • нужен сценарий с миграциями не по порядку или подстановка env-переменных в SQL-миграциях;
  • ScyllaDB в проекте нет, и CQL-специфика вам не нужна.

Не делайте автоматический перенос привычек

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

Если база распределённая и schema changes проходят через согласование между узлами, инструмент должен уважать эту модель. В противном случае вы переносите опыт из реляционного мира в систему, где часть старых привычек уже не работает так же надёжно.

Типовые сценарии выбора

1. Сервис целиком на ScyllaDB, уже используется gocqlx

Это самый простой случай. Я бы сначала смотрел на gocqlx/migrate, потому что он ближе всего к уже принятому стеку и не требует лишней смены инструментов.

2. ScyllaDB, но важнее зрелость и спокойный рабочий процесс

Здесь логично первым кандидатом сделать golang-migrate. Он менее узко заточен под ScyllaDB, зато выигрывает по зрелости, распространённости и предсказуемости сопровождения.

3. ScyllaDB и нужен отдельный инструмент именно под её особенности

В таком случае есть смысл смотреть на scyllamigrate. Но выбирать его лучше осознанно: с проверкой драйверного стека, прогоном миграций на стенде и без завышенных ожиданий от rollback-модели.

4. У вас PostgreSQL, MySQL или другая реляционная БД

Здесь goose — сильный и зрелый выбор. Богатая экосистема, поддержка Go-миграций, сценарии с миграциями не по порядку, подстановка env-переменных в SQL. ScyllaDB-специфика не нужна, и незачем её тянуть.

Практический вывод

scyllamigrate не заменяет goose или golang-migrate вообще. Он решает более узкую, но очень конкретную задачу: делать миграции для ScyllaDB так, чтобы сценарий работы соответствовал самой базе, а не привычкам из SQL-мира.

Если у вас ScyllaDB, scyllamigrate выглядит интересным кандидатом, но не автоматическим победителем. У него сильная близость к ScyllaDB, однако есть и плата за эту специализацию:

  • инструмент понимает CQL;
  • работает через подходящий драйвер;
  • поддерживает встроенные миграции;
  • умеет checksum tracking;
  • учитывает schema agreement и настройки кластера.

Но при этом остаётся более молодым и менее обкатанным, чем массовые инструменты миграций.

Если нужен наиболее зрелый и экосистемно устойчивый вариант, сильным кандидатом выглядит golang-migrate. Если проект уже живёт на gocqlx, то естественно сначала смотреть на gocqlx/migrate. Если у вас реляционная БД, goose остаётся сильным и зрелым выбором, но для ScyllaDB он уже выглядит скорее побочной опцией, чем главным кандидатом.

Хороший практический вопрос звучит не “что лучше”, а “какая рабочая модель у нашей базы и какой инструмент ей соответствует”. На практике для ScyllaDB я бы начинал проверку с такого порядка:

  1. gocqlx/migrate, если проект уже в экосистеме gocqlx;
  2. golang-migrate, если важнее зрелость и большая экосистема;
  3. scyllamigrate, если нужен инструмент, заточенный именно под ScyllaDB, и устраивают риски молодого проекта;
  4. goose, если ScyllaDB в проекте нет или она не является основной целевой базой.

Документация и первоисточники

Обсуждение в Telegram

Присоединиться →

Комментарии