Миграция с MySQL на PostgreSQL без остановки сервиса
Переносить базу на живом сервисе страшно ровно один раз. После двух успешных миграций появляется схема, которую можно повторять. Опишу её — с оговоркой, что детали зависят от размера базы и от того, насколько приложение умеет терпеть задержки репликации.
Этапы
1. Логическая репликация. На PostgreSQL ставится публикация, на MySQL — подписчик через Debezium или аналог. Задача — догнать историю и войти в поток. На этом этапе новая база только читает, приложение о ней не знает.
2. Сверка. Пока репликация идёт, сравниваем содержимое. Не «количество строк совпало», а контрольные суммы по батчам:
SELECT count(*), md5(string_agg(id::text || updated_at::text, ',' ORDER BY id))
FROM orders WHERE id BETWEEN 1 AND 100000;
Такой запрос на обеих сторонах даёт сравнимый результат и ловит расхождения, которые не видны по числу строк.
3. Двойная запись. Приложение начинает писать в обе базы. Читать продолжает из старой. Здесь важно, чтобы запись в новую была асинхронной и не роняла запрос при сбое — иначе вы добавили себе новую точку отказа.
4. Переключение чтения. Флагом в конфигурации, по одному сервису за раз. С возможностью откатить флаг обратно за секунды.
5. Отключение двойной записи. Только после того, как чтение с новой базы отработало хотя бы сутки под полной нагрузкой.
Про откат
Откат должен быть проверен до миграции, а не написан на бумаге. Я прогоняю его на копии продакшена: довожу до этапа 4, откатываю, убеждаюсь, что данные не потерялись и сервис не заметил. Если откат не работает — миграция не готова, какие бы красивые дашборды вы ни построили.
Грабли
- Разные представления о часовых поясах.
TIMESTAMPв MySQL иtimestamptzв PostgreSQL — не одно и то же. - Порядок сортировки влияет на уникальность. То, что в MySQL считалось разными строками, в PostgreSQL может конфликтовать.
- Автоинкремент vs sequences: после переключения не забудьте выставить последовательности на актуальные значения.
- Приложения, которые писали SQL с обратными кавычками, придётся править.