Содержание
Миграции базы данных без простоя делают в несколько шагов: сначала схему расширяют так, чтобы её понимали и старая, и новая версия кода, потом выкатывают код, переносят данные порциями и только в последнюю очередь удаляют лишнее. Главное правило — ни одна миграция не должна надолго блокировать таблицу, с которой работают пользователи.
Разберём, почему миграции ломают релизы, какие операции опасны и как провести типичные изменения схемы безопасно.
Почему миграции ломают релизы?
Миграции ломают релизы по двум причинам: долгие блокировки таблиц и несовместимость схемы с кодом. Пока миграция держит блокировку, запросы к таблице ждут, соединения с базой заканчиваются, и сайт перестаёт отвечать. А если колонку переименовали раньше, чем обновился код, старые экземпляры приложения начинают падать с ошибками.
Во время релиза почти всегда есть момент, когда одновременно работают старая и новая версии приложения. Поэтому схема в каждый момент должна подходить обеим. Как устроить сам выпуск без простоя, мы разбирали в статье релизы без простоя.
Какие операции опасны?
Опасны операции, которые переписывают таблицу целиком или берут эксклюзивную блокировку надолго. Точный список зависит от СУБД и её версии, поэтому перед релизом стоит проверить документацию и прогнать миграцию на копии данных.
| Операция | Риск | Безопасная альтернатива |
|---|---|---|
| Добавить колонку NOT NULL без значения по умолчанию | Ошибка или перезапись таблицы | Добавить nullable, заполнить, потом добавить ограничение |
| Создать индекс обычным способом | Блокировка записи | Создать индекс конкурентно или онлайн |
| Переименовать колонку | Старый код перестаёт работать | Новая колонка, двойная запись, перенос, удаление |
| Изменить тип колонки | Перезапись таблицы | Новая колонка нужного типа и перенос данных |
| Массовый UPDATE всей таблицы | Долгие блокировки, рост журнала | Обновление порциями в фоне |
Фото: Unsplash
Как переименовать колонку без простоя?
Колонку переименовывают через паттерн «расширить и сузить» (expand-contract): добавляют новую колонку, какое-то время пишут в обе, переносят старые данные и только потом удаляют старую. Каждый шаг — отдельный релиз, и после каждого систему можно откатить.
- Расширение. Добавьте новую колонку, допускающую пустые значения.
- Двойная запись. Выкатите код, который пишет и в старую, и в новую колонку, а читает из старой.
- Перенос данных. Фоновой задачей заполните новую колонку для старых строк порциями.
- Переключение чтения. Выкатите код, который читает из новой колонки.
- Сужение. Уберите запись в старую колонку, а затем удалите её отдельной миграцией.
Звучит долго, но для большинства изменений шаги укладываются в два-три обычных релиза.
Как переносить данные больших таблиц?
Данные больших таблиц переносят порциями по несколько тысяч строк с паузами между ними, а не одним запросом. Так каждая транзакция короткая, блокировки не накапливаются, а процесс можно остановить и продолжить с того же места.
DB::table('orders')
->whereNull('customer_name')
->chunkById(5000, function ($orders) {
foreach ($orders as $order) {
DB::table('orders')
->where('id', $order->id)
->update(['customer_name' => $order->client_name]);
}
usleep(200_000);
});
Такие задачи удобно запускать через очередь — об этом статья очереди задач в веб-приложении. Во время переноса следите за нагрузкой на базу и задержкой репликации.
Как проверять миграции перед релизом?
Миграции проверяют на копии продакшен-базы с реальным объёмом данных: только так видно, сколько времени займёт операция и какие блокировки она возьмёт. Дополнительно помогает ограничение времени ожидания блокировки — если миграция не может её получить, она завершается с ошибкой, а не вешает сайт.
Что проверить:
- время выполнения миграции на реальном объёме данных;
- что миграция не берёт долгих эксклюзивных блокировок;
- что старая версия кода работает с новой схемой;
- что для каждого шага есть план отката;
- что перед релизом есть свежая резервная копия.
Про индексы и медленные запросы подробнее — в статье индексы в PostgreSQL.
Коротко
- Схема в каждый момент релиза должна подходить и старому, и новому коду.
- Избегайте операций, которые переписывают таблицу или надолго её блокируют.
- Переименование и смену типа делайте через паттерн «расширить и сузить».
- Данные больших таблиц переносите порциями в фоне.
- Проверяйте миграции на копии продакшен-данных и ставьте таймаут на блокировки.
Частые вопросы
Можно ли откатить миграцию после релиза?
Откат схемы возможен, пока не потеряны данные. Удаление колонки или таблицы не откатить без резервной копии, поэтому такие шаги делают последними и отдельным релизом, когда новый код уже стабилен.
Сколько времени займёт миграция большой таблицы?
Зависит от объёма, индексов и нагрузки. Перед релизом миграцию прогоняют на копии продакшен-базы и замеряют время — это единственный надёжный способ узнать заранее.
Нужен ли отдельный инструмент для миграций?
Обычно хватает встроенных миграций фреймворка. Для очень больших таблиц в MySQL используют инструменты онлайн-изменения схемы, а в PostgreSQL многие операции и так выполняются без долгих блокировок.



