← Все статьи

Переезд продового Postgres с RDS Multi-AZ instance на Multi-AZ DB cluster

Плановый cutover почти без потери данных — и три ловушки: «техработы», которые не остановили запись, пароли, которые RDS не даёт скопировать, и дамп за 25 минут через VPN против 31 секунды внутри VPC.

В Gambchamp мы переносили живой продовый PostgreSQL с RDS Multi-AZ instance на Multi-AZ DB cluster: writer плюс две читаемые standby-реплики и отдельный reader-эндпоинт. Приложение нужно было переключить, роли, данные и гранты — перенести один в один, а потерю данных свести почти к нулю.

Это была плановая миграция, а не инцидент. Три вещи всё равно укусили.

Ловушка первая: «техработы» не остановили запись

Мы перевели приложение в режим техработ и сравнили точные количества строк по таблицам в два момента времени. Пять таблиц продолжали меняться: колбэки платёжных провайдеров, уведомления и визиты продолжали вставляться. Единственный дамп живого источника всегда отстаёт.

Настоящая заморозка — отскейлить всех писателей в ноль и доказать это: количества стабильны, пишущих соединений нет, сессий idle-in-transaction нет. Сверху, на источнике:

ALTER DATABASE app SET default_transaction_read_only = on;

плюс завершение оставшихся соединений приложения. Это блокирует любую запоздалую запись и заодно не даёт случиться split-brain после переключения.

Ловушка вторая: RDS не отдаёт пароли

pg_dumpall --globals-only — обычный способ перенести роли. На RDS даже master не может прочитать pg_authid, так что ни хеши, ни что-либо ещё оттуда не достать. pg_dumpall -g --no-role-passwords всё равно даёт точные атрибуты, членства и настройки ролей — это стоит забрать.

Пароли пришлось брать в другом месте. Роль приложения сохранила продовый пароль — его взяли из Kubernetes-секрета приложения и проверили реальным логином на источнике. Вспомогательные роли получили новые пароли. Роль, которая уже была просрочена, создали просроченной.

Ловушка третья: всё решила сеть

Дамп через VPN полз: 110 МБ примерно за 25 минут. Тот же pg_dump с EC2-хоста в той же VPC завершился за 31 секунду. Пароли передавались через stdin, никогда — аргументами команды.

Детали восстановления, которые легко упустить

  • На RDS master — не владелец базы, поэтому DROP DATABASE не проходит. Чистую цель сделали, удалив и пересоздав схему public от имени роли-владельца. CREATE EXTENSION по-прежнему требует master.
  • Две ошибки при восстановлении ожидаемы. ALTER DEFAULT PRIVILEGES FOR ROLE <app> падает под master и должен быть переигран от имени этой роли. publication already exists появляется потому, что публикации — объекты уровня базы и переживают DROP SCHEMA.

Проверить, прежде чем открывать

  • полный diff количеств строк по таблицам равен нулю;
  • каждая последовательность сверена с максимумом своего столбца — отстающая последовательность означает duplicate key на первой же вставке;
  • реальный логин под каждой ролью и пробная запись от роли приложения.

Только после этого хост приложения переключили на writer-эндпоинт кластера, а чтение — на reader-эндпоинт. Старый instance остался изолированным: read-only и без единого соединения приложения.

Откат прост, пока первая запись не легла в кластер: сбросить default_transaction_read_only на старом instance и вернуть хост. После первой записи — точка невозврата.

Выводы

  • Доказывайте заморозку diff’ом количеств. Колбэки и вебхуки продолжают писать после «техработ».
  • На RDS планируйте перевыпуск паролей или берите их из секретов приложения; скопировать их из базы нельзя.
  • Гоняйте дамп и восстановление изнутри VPC. Здесь это было примерно в 50 раз быстрее.
  • Сверяйте последовательности со столбцами до того, как открыть запись.
  • Мониторинг не следует за базой сам — переключить приложение это только половина cutover.