Миграция с MySQL на PostgreSQL без остановки сервиса

2026-05-02 · заметка

Переносить базу на живом сервисе страшно ровно один раз. После двух успешных миграций появляется схема, которую можно повторять. Опишу её — с оговоркой, что детали зависят от размера базы и от того, насколько приложение умеет терпеть задержки репликации.

Этапы

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 с обратными кавычками, придётся править.