Пост вышел многабукаф, поэтому ленивые могут пропускать части, помеченные как иллюстративные.
При разработке хоть сколь-нибудь сложных систем среди прочих всегда возникает проблема обновлений, а именно — миграции данных со старой версии в новую. И все бы ничего, но структуры данных имеют свойство меняться от версии к версии. Для веб-приложений это в первую очередь касается схемы БД, но сюда же относится изменение формата конфига, удаление/добавление отдельных параметров настроек, пересборка разных кэшей, индексов и так далее.
Типичный подход к решению этой проблемы выглядит так:
Казалось бы, все хорошо, но в этом процессе есть одна несчастливая сторона — это сами девелоперы. Их беда заключается в том, что очень часто они не смогут обновиться с одного нестабильного билда на другой, поскольку скриптов миграции данных между этими версиями не существует. В результате им придётся переставлять продукт, заново его конфигурировать таким образом, чтобы было удобно отлаживать, а все это отнимает драгоценные рабочие часы.
Чтобы осчастливить разработчиков нужно изобрести механизм обновления с любого билда на любой более новый. Такой механизм, если я не ошибаюсь, впервые получил широкое распространение во фреймворке Ruby on Rails под названием "Database Migrations", а теперь пробрался и во многие другие среды, например в мой любимый PHP-фреймворк Yii. И так, что же нам предлагают миграции?
Ключевым элементом в этой схеме является "миграция" — как правило небольшой скрипт, производящий одну более-менее атомарную операцию над файлами данных программы. Каждая миграция должна иметь свой порядковый номер, при чем более новые миграции должны иметь большие номера, чтобы можно было выстроить и запускать их в порядке появления. В качестве такого номера проще всего использовать UNIX timestamp момента создания миграции, чтобы понизить шансы появления миграций с одинаковыми номерами у разных разработчиков.
Многие, и я в их числе, ограничивают действия одной миграции изменениями в данных, привносимыми одним коммитом, при чем сама миграция включается в тот же самый коммит. В этом случае миграция должна переводить данные программы из формы, использовавшейся до изменений, привнесённых нашим коммитом, в форму, которая стала использоваться после этого коммита. В определённом смысле это привносит самодостаточность коммиту — он не только изменяет логику приложения, но и заботится о переносе данных на новую версию.
Запуском миграций управляет отдельная сущность, будем называть её менеджером миграций. Во-первых, он хранит список миграций, которые уже были применены, в момент начальной установки все имеющиеся миграции помечаются как установленные. Во-вторых, при наступлении какого-либо события (например, после установки обновления), этот менеджер получает список доступных приложению миграций и сравнивает его со списком уже установленных. Если найдутся такие миграции, которые не были установлены, менеджер миграций запускает их в хронологическом порядке и добавляет в список установленных.
Почему же все это должно осчастливить простых разработчиков? В первую очередь потому, что единицей обновления здесь становится один коммит, а не билд и тем более не релиз. Поэтому разработчик спокойно может обновлять свою рабочую копию из репозитория в любой момент, собирать приложение и быть уверенным, что оно корректно обновит все данные. А во вторую — такой подход сильно понижает шансы чего-нибудь забыть дописать в большом скрипте обновления или дописать не туда, поскольку миграция должна создаваться сразу, как только меняется структура данных и суть изменений ещё свежа в голове.
Чтобы немного облегчить понимание изложенного, сделаю лирическое отступление о том, как это работает. Пусть над проектом трудятся три программиста — Алексей, Борис и Вадим, и пусть они пишут CMS, ядро которой уже написано и опубликовано как релиз 1.0 перед новым годом, а теперь они заняты добавлением разных фитч.
А вот здесь следует проявить серьёзность и разобраться, какая ситуация сложилась у Вадима. У него установлена версия CMS, отличающаяся от релиза 1.0 наличием рейтинга у комментариев, лишним полем в профиле и возможностью обратной связи. К сожалению, в его версии все ещё присутствует опечатка в имени столбца. В среду, когда Вадим извлек свежую версию cms, благодаря механизму миграций, у него были запущены миграции 11_01-11:05:27 и 11_01-21:05:01, и не запущены те, что были добавлены 10 января, так как соответствующие изменения уже были учтены в момент установки копии Вадима. А вот если бы наши друзья использовали скрипт, обновляющий с релиза на релиз, Вадим бы оказался в очень неприятной ситуации: с одной стороны, у него уже готова куча отличных тестовых данных, с другой — чтобы их использовать для тестирования новой версии их нужно либо заново вводить, либо самому писать обновлятор, попутно разбираясь, что и как поменялось.
Резюмируя, у миграций есть следующие плюсы:
Однако, у миграций есть и некоторые недостатки, которые иногда играют важную роль:
Моё мнение на основе всего вышесказанного — в подавляющем большинстве случаев миграции приносят значительную выгоду проекту и его разработчиком, упрощая как процесс разработки, так и поддержку обновлений, особенно при правильной организации. Единственным случаем, когда миграции могут быть не оптимальны мне представляются системы максимальной доступности, да и то с оговорками. Более того, мне кажется, что следует задуматься и во внедрении этого механизма, даже если вы ведете старый проект с большим наследием эпохи мамонтов — тут даже небольшое облегчение поддержки будет как глоток воздуха утопающему.
Думаю database migration появился задолго до появления java / ruby / etc.. :) Разработчики сложнх систем всегда их использовали.
Unix timestamp использовать конечно можно, но практика показывает, что не удобно. Достаточно обычной нумерации 00-*.sql, 01-*.sql и так далее. Хотя конечно можно приписывать его в конец, но на самом деле он не решает ничего. Два программиста могут модифицировать одни и те же таблицы и тут уже.. кто успел тот и съел. :)
Пару слов о типичном случае миграции базы данных с которой работает одно или несколько приложений:
Сам процесс миграции можно разделить на 3 основных части: before (до отсановки сервера приложений / приложения), critical (во время остановки сервера) и post (после запуска сервера, новая версия приложения). Таким образом можно минимизировать процесс простоя сервера в неработоспособном состоянии (минимизировав количество патчей которые содержатся в секции critical). В before выносим созидающие патчи, в post - подчищающие. Лучше выполнять стадию post предыдущей версии приложения перед before следующей (вдруг понадобятся данные которые мы хотим удалить)
Так же для миграции можно использовать немного другой подход. Существует модель базы данных. От версии к версии эта модель преобразуется. Существуют программные средства делающие diff между двумя моделями в автоматическом режиме. То, что не возможно сделать в автоматическом режими ложится на руки dba или программистов.
Статья неплохая, начинающим разработчикам должно быть интересно. Не стоит бояться многобуквенных текстов. :)
Спасибо за ценное дополнение :)
UNIX timestamp предпочтителен для того, чтобы минимизировать шанс того, что два программиста попытаются создать одновременно миграции с одинаковыми номерами — шанс. Ну а конфликты в логике таких миграций как ни крути придётся решать вручную, как и с любым кодом, тут ничего не сделаешь, да.
Что же касается автоматических диффов схем - такого софта довольно мало, он довольно платный и справляется не очень хорошо. Поэтому с точки зрения возможности обновления с любой ревизии на любую миграции все же удобны.
Отправить комментарий