Когда в 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-мира
- Что умеет scyllamigrate
- Где у scyllamigrate риски и ограничения
- Как выглядит рабочий процесс
- Сравнение: scyllamigrate, gocqlx/migrate, golang-migrate и goose
- Когда выбирать какой инструмент
- Типовые сценарии выбора
- Практический вывод
- Документация и первоисточники
Почему миграции для 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 это очень практичный набор. Особенно важны две вещи:
scyllamigrateработает через Go-драйвер для Scylla/Cassandra-совместимого протокола, а не черезdatabase/sql.- Инструмент явно учитывает распределённую природу изменения схемы, а не просто “отправляет 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 forkgocql; - насколько воспроизводимо собирается консольный инструмент и пайплайн вокруг него.
Это не повод отказываться от инструмента, но повод заранее снять вопросы совместимости.
Для 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 знакомый:
- Создаём директорию
migrations/. - Добавляем пары файлов
*.up.cqlи*.down.cql. - Запускаем
up,status,down,version. - Храним историю применённых миграций в таблице
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 я бы начинал проверку с такого порядка:
gocqlx/migrate, если проект уже в экосистемеgocqlx;golang-migrate, если важнее зрелость и большая экосистема;scyllamigrate, если нужен инструмент, заточенный именно под ScyllaDB, и устраивают риски молодого проекта;goose, если ScyllaDB в проекте нет или она не является основной целевой базой.
Комментарии