Миграции баз данных — обзор библиотеки и ее использование
Как вы уже могли прочесть, недавно вышла новая версия CodeIgniter, одним из нововведений которого является библиотека Migration. Один из главных ее разработчиков, Phil Sturgeon был настолько воодушевлен удобством управления версиями баз данных для Rails, что решил создать аналог такого метода для CodeIgniter, и вот, в конце-концов вы можете видеть эту библиотеку в официальной поставке.
Из этой статьи вы получите общее представление о миграциях, а также научитесь их создавать. Во второй же части, мы с вами увидим, как легко они могут быть интегрированы в ваше приложение.
Данная статья будет полезна начинающим пользователям CodeIgniter, но я надеюсь что и более продвинутые коллеги узнают об этой чудесной библиотеке и подчерпнут для себя что-нибудь новое.
Кроме всего прочего, данная библиотека совсем не сложна, и реализовать ее при желании для любой другой платформы не составит большого труда!
Обзор библиотеки
Чтобы понять в общих чертах, как работает библиотека рассмотрим следующий примитивный сценарий:
0. Допустим, у нас есть 0 версия приложения с начальной базой (также версии 0).
1. В ходе разработки мы понимаем, что для реализации нового функционала (например системы подписки на новости) нам необходимо будет внести изменения не только в код приложения, но и в структуру базы данных.
2. Для этого мы в конфиге укажем, что версия базы данных для этой версии кода будет 1 и вместе с новым кодом создадим миграцию 001, указав для нее действия как для апгрейда, так и для даунгрейда базы (на случай если нужно будет откатить версию приложения).
3. Затем мы реализуем какой-либо механизм накатывания обновлений — это может быть и страничка в админке, и решение, использующее CLI, или даже полностью автоматическое обновление при выгрузке из системы контроля версий.
4. Выгружаем новый код проекта на сайт, запускаем миграцию до нужной версии, и вуаля!
А теперь представьте, что вам нужно обновить приложение через 3 версии вперед, или же на тестовом сервере посмотреть как была реализована версия, сделанная пару месяцев назад? Легко, просто изменив код, мигрируйте до нужной версии базы (миграция произойдет постепенно, допустим с 4-й до 1-й сначала накатятся 3-я и 2-я, а затем 1-я версии)!
Вы воодушевились такой возможностью как и я и хотите попробовать? Нет ничего проще!
Практическое использование
Собственно говоря, библиотека совершенно проста в использовании, и для ее внедрения в проект требуется добавить\поправить совсем немного строчек кода.
Весь процесс создания миграций не займет десяток минут времени, а сэкономит вам и вашим коллегам, работающим над этим же проектом прилично.
Создаем класс MY_Migration
Итак, для начала нам будет нужно немного допилить напильником расширить класс Migration.
Зачем? Все очень просто, по умолчанию этот класс не умеет сообщать о том, какие версии базы и кода являются текущими, а реализовывать эти проверки по всему коду не совсем правильно, лучше это сделать в одном месте.
Для этого воспользуемся стандартной методикой расширения классов, используемой в CodeIgniter — создадим файл %site_path%/application/libraries/MY_Migration.php со парой десятков строк кода внутри.
Этот класс добавит к реализации предка еще 2 публичных метода:
Примечание: здесь и далее могут быть приведены сокращенные листинги кода, ссылка на все файлы, использованные в туториале в конце статьи
Как видите, функции являются лишь обертками для protected метода для получения версии базы данных и опять же protected свойства $_migration_version, и вполне наглядно демонстрируют применение инкапсуляции.
Также советую обратить внимание на важный момент: в оригинальном классе Migration стоит ограничение, из-за которого его конструктор выполняется только при инициализации родительского класса.
Поэтому чтобы приложение работало правильно, автоматически инициализируя MY_Migration вместо оригинала, необходимо продублировать конструктор родительского класса, изменив в нем условие на 41 строке:
Создаем миграцию
Расширив базовый класс, дело остается за малым. Для того, чтобы библиотека могла накатывать или откатывать изменения на базу данных, необходимо создать миграционный файл, содержащий в себе класс-потомок Migration(в нашем случае MY_Migration).
Так как у нас это первая миграция, то создадим следующий файл: %site_path%/application/migrations/001_add_messages.php, содержащий в себе фунции up и down:
Обратите внимание на то, что файлы с миграциями содержат в начале 3-х здачный цифровой порядковый номер версии, на которую они переводят базу данных, а класс, содержащийся в них называется как имя файла, где вместо номера версии стоит Migration_.
Правим конфиг
Наконец, для работы с миграциями нужно немного подправить конфиг в файле %site_path%/application/config/migration.php и включить их, посмотреть правильно ли указаны к ним путь и версия базы данных, требуемой для корректной работы нашего кода (стандартные комментарии вырезаны из листинга):
Обратите внимание, что я указал 1-ю версию, что подразумевает, что мы уже сделали функционал рассылок.
Таким образом, мы немного повысили удобство пользования библиотекой, расширив ее функционал, обошли подводный камень, связанный с проверкой в конструкторе на наследование оригинального класса, а так же научились создавать миграции.
Как вы видите, ничего сложного в создании миграций нет, а, повторюсь, они могут сэкономить вам существенное количество времени. Более того, их внедрение в текущий проект так же не должно вызвать каких-либо сложностей. Кстати, как раз внедрению и будет посвящена моя вторая статья, которая появится в самое ближайшее время.
PS: Не хватает кармы для публикации в блог CodeIgniter, так что пока публикую обще-php-шный блог
UPD: Добрые анонимы налили немного кармы, перенес в блог CodeIgniter
Применение миграций
Применение миграции во время выполнения
Приложение может применять миграцию программным способом, как правило, во время запуска. При работе с локальной разработкой и тестированием миграций этот подход не подходит для управления производственными базами данных по следующим причинам.
После добавления миграций их необходимо развернуть и применить к базам данных. Для этого существуют различные стратегии, которые более подходят для рабочих сред, а другие — для жизненного цикла разработки.
Независимо от стратегии развертывания всегда следует проверять созданные миграции и тестировать их перед применением к рабочей базе данных. Миграция может привести к удалению столбца, когда намерение приходило переименовать его или может завершиться ошибкой по различным причинам при применении к базе данных.
Скрипты SQL
рекомендуемый способ развертывания миграций в рабочей базе данных — создание сценариев SQL. Ниже перечислены преимущества этой стратегии.
Основное использование
следующий сценарий создает SQL скрипт из пустой базы данных для последней миграции:
С from (в подразумеваемую миграцию)
в следующем примере создается скрипт SQL из данной миграции в последнюю миграцию.
С from и to
в следующем примере создается скрипт SQL из указанной from миграции в указанную to миграцию.
Обязательно учитывайте возможные сценарии потери данных.
Основное использование
следующий сценарий создает SQL скрипт из пустой базы данных для последней миграции:
С from (в подразумеваемую миграцию)
в следующем примере создается скрипт SQL из данной миграции в последнюю миграцию.
С from и to
в следующем примере создается скрипт SQL из указанной from миграции в указанную to миграцию.
Обязательно учитывайте возможные сценарии потери данных.
Создание скрипта принимает следующие два аргумента, указывающие, какой диапазон миграций должен быть создан:
идемпотентными сценарии SQL
Ниже приводится пример идемпотентными миграций.
Средства командной строки
Программы командной строки EF можно использовать для применения миграций к базе данных. При работе с локальной средой разработки и тестирования миграций этот подход не идеально подходит для управления производственными базами данных.
В следующем примере база данных обновляется до последней миграции:
Следующий пример обновляет базу данных на заданную миграцию:
Обратите внимание, что это можно использовать для отката к предыдущей миграции.
Обязательно учитывайте возможные сценарии потери данных.
В следующем примере база данных обновляется до последней миграции:
Следующий пример обновляет базу данных на заданную миграцию:
Обратите внимание, что это можно использовать для отката к предыдущей миграции.
Обязательно учитывайте возможные сценарии потери данных.
Дополнительные сведения о применении миграций с помощью программ командной строки см. в справочнике по инструментам EF Core.
Пакеты
Эта функция появилась в EF Core 6,0.
Пакеты миграции — это исполняемые файлы с одним файлом, которые можно использовать для применения миграций к базе данных. они устраняют некоторые недостатки SQL сценария и программ командной строки:
В следующем примере создается пакет:
В следующем примере создается автономный пакет для Linux:
В следующем примере создается пакет:
В следующем примере создается автономный пакет для Linux:
Дополнительные сведения о создании пакетов см. в справочнике по инструментам EF Core.
efbundle
| Аргумент | Описание |
|---|---|
| Целевая миграция. Если значение равно «0», все миграции будут отменены. По умолчанию используется последняя миграция. |
| Параметр | Short | Описание |
|---|---|---|
| Строка подключения к базе данных. По умолчанию используется значение, указанное в AddDbContext или onconfiguring. | ||
| —verbose | Отображение подробных выходных данных. | |
| —no-color | Не замечайте выходные данные. | |
| —prefix-output | Вывод префикса с уровнем. |
следующий пример применяет миграцию к локальному экземпляру SQL Server, используя указанные имя пользователя и пароль.
Миграция версий БД MSSQL
В данном посте я хочу поделить мыслями на тему поддержки и миграции изменений в базах данных и MSSQL в частности, а также своим решением.
Проблема
Варианты решения проблемы
Мое решение
Как начать использовать
1. Забрать исходники проекта https://csharpdatabasemigrator.codeplex.com/SourceControl/latest
2. Открыть проект в студии
3. Зайти в папку Default (папка по умолчанию для файлов миграции)
4. Создать файл миграции. Файл миграции представляет собой класс наследованный он базового класса Migration
5. В атрибутах класса прописать номер версии и его описание, а также сам код для миграции. Например создания новой таблицы
Можно выполнить SQL команды:
Подробности синтаксиса можно прочитать тут: https://github.com/schambers/fluentmigrator/wiki/Fluent-Interface
6. Скомпилировать проект и запустить приложение:
7. Подключиться к серверу баз данных
8. Выбрать в выпадающем списке нужную базу данных
9. Выбрать тип операции «Update To latest Version» и нажать кнопку «Do Job»
10. Проверить лог в правой части формы
Как работать с несколькими типами базы данных
Все файлы миграции, относящиеся к одного схеме базы данных, должны находится в одном пространстве имен (namespace). По умолчанию это «Migrations.Default» по имени папки в проекте. Чтобы добавить еще один тип миграции нужно в проекте «Migrations» создать еще одну папку, например «MySecondDatabase» и отредактировать файл конфигурации «MigrationNamespaces.config»
После этого, в главном окне приложения будет доступен тип «My Second Database».
Выполнение sql файлов из папки
1. Система может использовать в качестве миграции файлы из указанной папки. Все файлы будут отсортированы по имени и выполнены в момент применения миграции:
Как это все работает
Для выполнения миграции из c# кода надо подключить сборку «FluentMigrator.Runner» и выполнить один метод:
Для создания и восстановления бэкапов баз данных есть два метода:
Миграция схемы данных без головной боли: идемпотентность и конвергентность для DDL-скриптов
Язык SQL и реляционные базы с нами уже более сорока лет. За это время стандарт SQL прошёл через множество ревизий, и, судя по всему, процесс развития на этом не останавливается. Реляционные базы в качестве хранилищ данных десятилетиями царствовали безраздельно, царствуют и поныне, и лишь только в последнее время их немного теснят альтернативные подходы.
SQL практически всемогущ, если вопрос касается извлечения данных. (Не все знают, но одним SQL-запросом можно графически построить множество Мандельброта). Но одна проблема продолжает быть в нём концептуально не решена: проблема миграции схем данных.
Я думаю, на разных этапах своей карьеры в IT мы все сталкивались с тем, как это бывает тяжело: контролировать структуру рабочей базы данных и выполнять её обновления по мере разворачивания новых версий софта. Баги, возвращающиеся после того, как их вроде уже исправили, ошибки «поле отсутствует в таблице», жалобы «я исправил хранимку, а вы затёрли!» — знакомо ли вам это всё?
Острота этой проблемы особенно ясна по контрасту с тем, насколько кардинально решена задача контроля версий исходного кода: системы типа Git или Subversion позволяют вам держать под контролем совместную работу многих разработчиков, не терять ни одного изменения, «записывать все ходы» и собирать результаты труда команды воедино. Но эта благостная картина заканчивается там, где заканчивается область применимости систем контроля версий. Если мы говорим о структуре схемы данных, то до сих пор применение систем контроля версий для их разработки — весьма ограничено.
Так легко и соблазнительно бывает внести изменения структуры прямо на базе! Если такие изменения оказываются не задокументированы, то в будущем наступает необходимость развивать систему и приближается катастрофа. Но и осознав необходимость документировать каждый шаг по изменению структуры БД, мы приходим к тому, что делать это — очень неудобно, а язык DDL определения схемы данных нам в этом никак не помогает. Например, добавление поля в таблицу требует команды ALTER TABLE ADD. Выполнить это выражение имеет смысл ровно один раз и ровно тот самый момент, когда в таблице поле ещё отсутствует, а необходимость в нём – уже есть. Выполнение этого скрипта в другие моменты приведёт либо к ошибке выполнения самого скрипта, либо, что хуже, к неправильному состоянию структуры базы данных.
Как же агрегировать все изменения, производимые разработчиками, в единый результат и как проконтролировать его выполнение?
Подход №1: накопление change-скриптов
Практическое решение этой задачи вручную, без использования каких-либо дополнительных инструментов, заключается в создании в кодовой базе проекта папки с пронумерованными скриптами модификации структуры базы данных, в накапливании их в этой папке и во введении строжайшей дисциплины среди участников проекта разработки. Должен быть формализован процесс записи изменений в очередном скрипт-файле и процесс «накатки» изменений на базы данных.
Более продвинутым и удобным способом решения является использование систем типа Liquibase. Liquibase берёт на себя контроль номера последнего выполненного изменения структуры данных и обеспечивает прогон скриптов модификации структуры в линейном порядке, один и только один раз. Это уже очень много, и использование Liquibase однозначно ликвидирует хаос, царящий при разработке и обновлении схемы данных.
Но тем не менее, использование Liquibase и других инструментов, основанных на накоплении change-скриптов, не делает задачу модификации схемы данных столь же легкой и техничной, как легка и технична модификация программного исходного кода вашего приложения.
Во-первых, change-скрипты накапливаются. Достаточно долго идущий проект тянет за собой большой «хвост» из этих скриптов, большинство из которых утрачивают свою актуальность, т. к. содержат в себе изменения, выполненные очень давно. Сохранение всего «хвоста» бывает бессмысленно, т. к. изменение в одном месте лога может отменять результат изменения в другом месте: допустим, в ходе развития системы мы попробовали какой-то вариант и отказались от него, но два изменения уже навсегда остаются change-log-е. Глядя на разросшийся лог, становится невозможно понять наше текущее представление о структуре базы. А если мы хотим воспроизвести структуру базы данных «с нуля» на, допустим, совершенно новом проекте, нам придётся в процессе выполнения скрипта повторять весь её путь эволюционного развития!
Во-вторых, при таком подходе сохраняется колоссальная разница между сложностью модификации структуры БД через changeset-ы и простотой модификации исходного кода программы.
Допустим, у вас есть код, описывающий класс в вашем любимом языке программирования, и вам в процессе улучшения системы понадобилось один метод в класс добавить, а другой – удалить. Что вы делаете? Берёте и меняете исходный код класса, а затем коммитите его на систему контроля версий, не так ли? Не вынуждены же вы создавать change set с командами вида:
alter class drop method foo;
alter class add method bar(…) <
…
>
Почему мы должны это делать для структуры схемы данных?
Причина, конечно, в том, что в базе данных лежат ещё и данные, с которыми надо что-то делать при изменении структуры. Об этой проблеме мы ещё поговорим, но сначала давайте взглянем на другой возможный подход к решению нашей задачи.
Подход №2: идемпотентный DDL-скрипт
Проблемы, схожие с теми, что возникают при миграции схемы базы данных, давно возникали при конфигурировании серверов.
Как автоматизировать установку нужного софта на сервер и обеспечить обновление конфигураций? Мы можем написать shell-скрипт, который, будучи выполнен на пустой виртуальной машине, всё на ней установит и сконфигурирует. Это аналог скрипта создания структуры БД (CREATE TABLE…-скрипта), но его проблема в том, что он может быть выполнен только один раз и только на пустой машине. Если машина уже развёрнута и работает, а по новой спецификации для работы системы нам нужна, например, другая версия Java, как тут постуить — дописывать change скрипт, сносящий старую версию и устанавливающий новую версию Java? Ну а если нам нужно будет воспроизвести конфигурацию на пустой машине — нам что же, проходить через все шаги, которые мы прошли исторически?
Главный, ключевой вопрос, который при этом возникает: можно ли править инфраструктуру/схему данных так же легко, как мы правим исходный код – прямым изменением её описания и записи в контроль версий?
Ответом на эти вопросы для задачи конфигурирования серверов явилось появление принципа Infastructure as Code (IaC) и целого класса систем, известных как configuration management-системы: Ansible, Chef, Puppet и т. д. В основе всех систем этого вида стоят два главных принципа: идемпотентность (idempotence) и конвергентность (сходимость, convergence). Оба этих термина позаимствованы из математики. Если отбросить ненужный формализм, применительно к нашей проблематике термины эти обозначают следующее:
Я думаю, сказанного уже достаточно для того, чтобы понять, каким образом эти принципы могут помочь для контроля схемы данных. Предположим, что наша база данных поддерживает ключевое слово “CONVERGE” и у нас имеется такой скрипт:
CONVERGE TABLE OrderHeader(
id VARCHAR(30) NOT NULL,
date DATETIME DEFAULT GETDATE(),
customer_id VARCHAR(30),
customer_name VARCHAR(100),
CONSTRAINT Pk_OrderHeader PRIMARY KEY (id)
);
Ключевое слово CONVERGE должно интерпретироваться как «приведи таблицу к желаемой структуре». То есть: если таблицы нет — создай, если таблица есть, посмотри, какие в ней поля, с какими типами, какие индексы, какие внешние ключи, какие default-значения и т. п. и не надо ли что-то изменить в этой таблице, чтобы привести её к нужному виду.
Если бы базы данных могли поддерживать такое ключевое слово, т. е. если бы была возможность писать для баз данных идемпотентные и конвергентные DDL-скрипты — необходимости в написании этой статьи не возникло бы. Все мы просто держали бы в системе контроля версий “CONVERGE TABLE”-скрипты, описывающие желаемую на текущий момент схему данных, и работали бы с ними точно так же, как работаем с исходным кодом: нужно новое поле в таблице — добавили, нужен другой состав полей в индексе — отредактировали. (Я слышу ваш вопрос: а как же быть с миграцией данных — но терпение, я к этому перейду уже скоро.)
К сожалению, в мире реляционных БД движения к поддержке настоящей идемпотентности DDL пока не происходит. Всё, что до сих пор было сделано в базах данных по направлению к идемпотентности DDL-кода – это поддержка конструкции CREATE IF NOT EXISTS, но это, скажем прямо – довольно слабая попытка. Скрипт CREATE TABLE IF NOT EXISTS, конечно, внешне поведёт себя как идемпотентный (не выдаст ошибку, если таблица уже создана), но не как конвергентный (не будет модифицировать структуру уже созданной таблицы).
Приходится уповать на внешние инструменты. Идемпотентный и конвергентный DDL доступен, например, в системе Celesta. Чтобы для разработчиков и для инструментов разработки (например, визуального редактора ER-диаграмм) выглядеть как обычный DDL-скрипт, в Celesta применяется ключевое слово CREATE, хотя в Celesta оно обладает смыслом именно гипотетического CONVERGE. При каждом старте, Celesta сравнивает актуальную структуру базы данных, к которой она присоединена, с желаемой структурой, описанной в виде DDL-скрипта CelestaSQL, и при необходимости выполняет минимально необходимую серию CREATE/ALTER/DROP-команд. На сегодня поддерживаются четыре типа баз данных: PostgreSQL, Oracle, MS SQL Server и H2 (последняя, в основном, для нужд организации модульного тестирования).
Идемпотентный скрипт, задающий структуру БД, нельзя просто взять и линейно выполнить. Как известно, одни объекты в базе данных зависят от других объектов — например, таблицы ссылаются друг на друга через внешние ключи, представления и индексы зависят от таблиц и т. д. Поэтому, прежде чем выполнять серию создания / перестроения объектов, их необходимо ранжировать в порядке зависимости друг от друга: говоря формально, выполнить топологическую сортировку графа зависимостей, и далее обрабатывать объекты, начиная с тех, от которых не зависит ничего. Часто бывает необходимо сбросить внешние ключи, чтобы затем восстановить их после модификации соединяемых ими таблиц. Эта задача решена в Celesta, и это позволяет без проблем выполнять апгрейд для абсолютного большинства случаев.
Миграция данных
Так как же быть с трансформацией данных, ведь простого ALTER не всегда достаточно? Что делать, если мы, допустим, захотим добавить в непустую таблицу NOT NULL-поле и не снабдим его DEFAULT-значением? Ведь если такое поле не заполнить предварительно данными, то база данных не даст выполнить ALTER TABLE ADD-скрипт. А если мы хотим добавить Foreign Key, но не все данные в таблице удовлетворяют ограничению? А если, допустим, логика приложения изменилась и требуется перенести данные из одного столбца в другой?
Всё это вопросы совершенно корректные, но для начала заметим, что для большинства изменений, которые вы производите в базе данных в процессе развития вашего приложения, никакой миграции не требуется и простого ALTER-скрипта достаточно. Вам не надо мигрировать данные, если вы просто добавляете новую таблицу или новую колонку в таблицу (NULLABLE или с DEFAULT-значением). Вам не надо мигрировать данные, если вы добавляете или перестраиваете индекс. Не нужно ничего мигрировать, если изменяется запрос для view. Практика применения системы Celesta показывает, что подавляющее большинство производимых разработчиками изменений относится именно к такому типу.
Если же миграция данных действительно необходима, то, да: придётся написать и выполнить одноразовый миграционный скрипт. Вот только сохранять этот скрипт «на века» не нужно и история с его отладкой и применением гораздо проще, чем в системах, построенных на change log.
Рассмотрим случай добавления внешнего ключа на непустую таблицу, не все записи которой удовлетворяют такому ключу. В процессе обновления Celesta попробует создать такой ключ при помощи команды ALTER TABLE … ADD CONSTRAINT … FOREIGN KEY. Если ей это удаётся – отлично, если нет – система останавливается и Celesta сообщает, что апдейт такого-то объекта она выполнить полностью не сумела по такой-то причине, с таким-то сообщением БД.
Для changelog-систем нет ничего хуже changeset-а, выполнившегося наполовину и зафиксированного в промежуточном состоянии: в такой ситуации система находится «посередине» между двумя ревизиями и ситуацию можно разрулить лишь вручную. Отсутствие поддержки откатываемых DDL-транзакций во многих базах вносит дополнительные трудности.
Для «конвергентных» систем, в отличие от «changelog»-систем, апдейты, не выполненные до конца, не являются проблемой, т. к. для генерации ALTER-команд система сравнивает текущее фактическое состояние базы с желаемым, и изменения, не выполненные при одной попытке, она будет пытаться доделать в другой.
Столкнувшись с ситуацией, когда апдейт не может быть выполнен автоматически (ситуация, повторюсь, довольно редкая), вы можете сделать одноразовый скрипт. Допустим, наполняющий таблицу-справочник нужными данными и тем самым создающий для Celesta условия автоматического выполнения апдейта. Этот скрипт можно отладить на свежей копии production-базы, затем выполнить на production базе, затем произвести Celesta-апдейт.
После всего этого ваш скрипт можно просто выкинуть, потому что больше он вам не понадобится никогда! Ведь ваши рабочие базы уже находятся в нужном вам состоянии по структуре, а если вы задумаете делать новую базу «с нуля», то тогда вам не надо заставлять базу проходить весь тот путь, который вы прошли, дорабатывая её структуру в процессе разработки.
Возможно, такой подход покажется кому-то менее надёжным, чем использование changelog-систем, которые заставляют вас думать о необходимых шагах по миграции данных при каждом изменении структуры и о встраивании таких шагов в changeset. Но если подумать, то становится понятно, что надёжность changelog-систем в этом плане – мнимая. Как известно, не бывает программ без потенциальных ошибок, это правило относится и к скриптам модификации данных. Тот факт, что на имеющемся у вас наборе данных скрипт модификации change set-а был отлажен и показал корректную работу, на самом деле не гарантирует со 100% уверенностью, что он выполнится без ошибок на любых данных. В случае с применением идемпотентного DDL мы по крайней мере не объявляем скрипт модификации данных не подлежащим изменению, защищённым контрольной суммой атрибутом ревизии. В случае, если ошибка всё же произойдёт, мы всегда можем повторять попытки апдейта до тех пор, пока не сведём систему к желаемой структуре. Ваши данные не будут потеряны, т. к. Celesta никогда автоматически не выполняет drop столбцов и таблиц, содержащих данные, оставляя эту операцию на выполнение вручную.
К сожалению, область применения CelestaSQL ограничена использованием его в паре с системой Celesta для создания кода бизнес-логики, (уже нет — см. update!) поэтому именно для вашего проекта на сегодня, пожалуй, я бы порекомендовал Liquibase. Однако Celesta — проект открытый и одно из возможных направлений его развития – создание инструмента миграции структуры БД общего назначения.
Хотя лично я бы предпочёл, чтобы разработчики БД когда-нибудь реализовали реальную поддержку идемпотентности и конвергентности DDL.
UPD: С начала 2018 года CelestaSQL можно применять для идемпотентного обновления произвольной базы данных при помощи системы 2bass.