
Привет, Habr! На связи эксперты команды сервиса WatchDog — Дмитрий Коновалов и Геннадий Переломов.
В ВТБ, у нашего основного заказчика, мы развиваем сервисы автоматизации сопровождения баз данных. Одной из ключевых СУБД в инфраструктуре является PostgreSQL. Поддержка её в актуальном состоянии требует периодических мажорных обновлений, которые остаются одной из самых трудоёмких задач для DBA, особенно в ночные или выходные технологические окна.
В этой статье мы расскажем, как разработали внутренний сервис, позволяющий администраторам прикладных систем запускать мажорное обновление PostgreSQL в один клик и без участия DBA.
Зачем автоматизировать мажорные обновления PostgreSQL
Жизненный цикл мажорной версии PostgreSQL — 5 лет. Чтобы оставаться в зоне официальной поддержки, получать новые возможности и улучшения, и при этом соответствовать внутренним нормативным документам (ВНД), необходимо регулярно обновлять инсталляции. На момент старта проекта в ВТБ было несколько тысяч экземпляров PostgreSQL, и ручное обновление каждого из них было бы затратным и рискованным.
Постановка задачи
В мае 2023 года перед нами поставили задачу:
Создать сервис (условно — «кнопку»), который позволит администраторам АБС выполнять мажорное обновление PostgreSQL без участия DBA.
Требования:
Поддержка standalone и cluster конфигураций.
Надёжность — на входе и выходе должен быть рабочий экземпляр PostgreSQL.
Минимизация человеческого фактора.
Поддержка расширений pg_stat_statements и pgaudit.
Обязательное выполнение резервного копирования.
Полное соответствие корпоративным чеклистам.
Анализ и подготовка
Мы изучили:
статистику используемых версий PostgreSQL (от 11 до начинающейся 15),
типовые расширения и модули,
текущие процессы обновления,
особенности конфигураций (Patroni, standalone),
ограничения по времени обновлений (ночное окно).
В результате мы сузили фокус:
поддержка обновлений до версий 14 и 15,
анализ расширений и автоматическая проверка их наличия,
обязательные предварительные проверки, чтобы гарантировать запуск PostgreSQL после обновления.
Реализация: архитектура «кнопки»
Сценарий был разбит на две части:
Frontend: веб-интерфейс с выбором сервера и мажорной версии;
Backend: масштабный Ansible Playbook (более 3000 строк и ~100 шагов).
Форма пользователя:
Выбор сервера из доступных.
Выбор версии обновления (14 или 15).
Отображение ограничений сценария.
Чекбокс «Я понимаю, что делаю» — без него запуск невозможен.
Структура playbook
Шесть основных блоков + блоки отката:
1. Предварительные проверки:
Доступность экземпляра,
Конфигурация,
Наличие расширений,
Соответствие чеклисту.
2. Установка новой версии PostgreSQL.
3. Подготовка к pg_upgrade:
Dump-проверка,
Тестовое выполнение pg_upgrade.
4. Запуск pg_upgrade.
5. Бэкап:
Для cluster — бэкапом является сама реплика, которую сценарий не трогает пока успешно не обновим лидер,
Для standalone — до выполнения pg_upgrade.
6. Финальные проверки и правки конфигурации.
Механизмы отката
Механизмы отката предусмотрены для всех этапов до и после неуспешного выполнения pg_upgrade. В случае ошибки сценарий восстанавливает PostgreSQL в изначальное рабочее состояние.
Дополнительно:
Обновление Patroni до версии 3.0.2 (если требуется).
Асинхронное выполнение ресурсоёмких операций: pg_upgrade, vacuumdb, pg_dump, бэкап.
Тестирование
Мы провели тестирование по всем возможным сценариям:
Все поддерживаемые мажорные версии;
Разные объёмы $PGDATA, LO, количество таблиц;
Все комбинации включённых расширений;
Все блоки отката.
Результаты показали надежную работу сценария.
Результаты эксплуатации
На момент публикации сценарий находится в боевой эксплуатации более полутора лет, за это время проведено свыше 1000 успешных обновлений PostgreSQL, а также добавлена поддержка обновления до версии 16.
Статистика работы сценария в ПРОД среде:
config |
pg_ver |
patroni_ver_leader |
patroni_ver_replica |
pgdata_size |
count_unlogged_tables |
count_all_tables |
count_all_lo |
duration |
cluster |
14.8->15.6 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
849 GB |
0 |
25348 |
0 |
03:22:16 |
cluster |
14.8->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
1840 GB |
0 |
6140 |
0 |
03:00:36 |
cluster |
14.8->15.6 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
898 GB |
0 |
4419 |
0 |
02:58:19 |
cluster |
14.8->15.6 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
721 GB |
0 |
13846 |
0 |
01:39:38 |
cluster |
13.11->15.8 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
150 GB |
0 |
19834 |
307969 |
00:56:31 |
cluster |
14.8->15.8 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
829 GB |
0 |
382 |
0 |
00:50:25 |
cluster |
14.8->15.6 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
922 GB |
0 |
1454 |
0 |
00:43:47 |
cluster |
14.11->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
390 GB |
0 |
868 |
0 |
00:43:30 |
cluster |
14.8->15.6 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
377 GB |
0 |
236 |
0 |
00:40:59 |
cluster |
13.14->15.8 |
2.1.5->3.0.2 |
2.1.5->3.0.2 |
644 GB |
0 |
138 |
0 |
00:40:56 |
cluster |
14.8->15.6 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
750 GB |
0 |
778 |
0 |
00:39:47 |
cluster |
14.11->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
1022 GB |
0 |
45 |
0 |
00:35:58 |
cluster |
14.8->15.8 |
2.1.5->3.0.2 |
2.1.5->3.0.2 |
224 GB |
0 |
352 |
0 |
00:29:39 |
cluster |
14.6->15.8 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
241 GB |
0 |
150 |
0 |
00:29:04 |
cluster |
15.6->16.4 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
250 GB |
0 |
93 |
0 |
00:28:51 |
cluster |
14.8->15.8 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
436 GB |
0 |
101 |
0 |
00:26:50 |
cluster |
14.6->15.8 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
230 GB |
0 |
103 |
0 |
00:25:56 |
cluster |
14.11->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
381 GB |
0 |
257 |
0 |
00:25:16 |
cluster |
13.11->15.8 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
61 GB |
0 |
21758 |
180 |
00:24:27 |
cluster |
14.8->15.8 |
2.1.5->3.0.2 |
2.1.5->3.0.2 |
210 GB |
0 |
129 |
0 |
00:22:34 |
cluster |
13.11->15.6 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
218 GB |
0 |
197 |
0 |
00:21:33 |
cluster |
14.11->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
186 GB |
0 |
547 |
0 |
00:21:28 |
cluster |
14.8->15.8 |
2.1.5->3.0.2 |
2.1.5->3.0.2 |
80 GB |
0 |
397 |
0 |
00:21:12 |
cluster |
14.8->15.8 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
184 GB |
0 |
106 |
0 |
00:21:06 |
cluster |
13.11->15.6 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
223 GB |
0 |
197 |
0 |
00:20:34 |
cluster |
14.8->15.8 |
2.1.5->3.0.2 |
2.1.5->3.0.2 |
61 GB |
0 |
449 |
0 |
00:15:51 |
cluster |
14.11->15.6 |
2.1.5->3.0.2 |
2.1.5->3.0.2 |
163 GB |
0 |
57 |
0 |
00:15:00 |
cluster |
14.11->15.8 |
2.1.5->3.0.2 |
2.1.5->3.0.2 |
96 GB |
0 |
49 |
0 |
00:14:16 |
cluster |
14.4->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
9718 MB |
0 |
43 |
0 |
00:13:39 |
cluster |
14.8->15.8 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
57 GB |
0 |
118 |
0 |
00:11:52 |
cluster |
14.11->15.6 |
2.1.5->3.0.2 |
2.1.5->3.0.2 |
56 GB |
0 |
34 |
0 |
00:11:49 |
cluster |
14.4->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
9731 MB |
0 |
43 |
0 |
00:11:42 |
cluster |
15.6->16.4 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
80 GB |
0 |
81 |
0 |
00:11:23 |
cluster |
14.8->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
16 GB |
0 |
167 |
0 |
00:11:13 |
cluster |
14.8->15.8 |
2.1.5->3.0.2 |
2.1.5->3.0.2 |
10 GB |
0 |
408 |
0 |
00:11:11 |
cluster |
14.8->15.8 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
68 GB |
0 |
598 |
0 |
00:11:07 |
cluster |
14.13->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
14 GB |
0 |
73 |
0 |
00:10:41 |
cluster |
15.6->16.4 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
43 GB |
0 |
101 |
0 |
00:10:36 |
cluster |
14.11->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
20 GB |
0 |
129 |
0 |
00:09:50 |
cluster |
13.14->15.6 |
2.1.5->3.0.2 |
2.1.5->3.0.2 |
8613 MB |
0 |
21 |
0 |
00:09:37 |
cluster |
14.11->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
9005 MB |
0 |
74 |
0 |
00:09:30 |
cluster |
14.11->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
65 GB |
0 |
178 |
0 |
00:09:09 |
cluster |
14.8->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
8787 MB |
0 |
210 |
0 |
00:08:56 |
cluster |
14.8->15.8 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
21 GB |
0 |
324 |
14893 |
00:08:50 |
cluster |
15.6->16.4 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
8642 MB |
0 |
21 |
0 |
00:08:47 |
cluster |
14.8->15.6 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
8749 MB |
0 |
54 |
0 |
00:08:44 |
cluster |
14.8->15.8 |
3.0.2->3.0.2 |
3.0.2->3.0.2 |
31 GB |
0 |
44 |
0 |
00:08:41 |
cluster |
14.11->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
9434 MB |
0 |
76 |
0 |
00:08:33 |
cluster |
14.11->15.6 |
2.1.5->3.0.2 |
2.1.5->3.0.2 |
8567 MB |
0 |
21 |
0 |
00:08:28 |
cluster |
15.6->16.4 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
22 GB |
0 |
333 |
0 |
00:08:19 |
cluster |
14.8->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
17 GB |
0 |
165 |
0 |
00:08:07 |
cluster |
14.8->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
12 GB |
0 |
275 |
0 |
00:08:01 |
cluster |
12.18->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
8627 MB |
0 |
21 |
0 |
00:07:57 |
cluster |
15.8->16.4 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
11 GB |
20 |
1250 |
0 |
00:07:36 |
cluster |
14.11->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
8807 MB |
0 |
26 |
0 |
00:07:35 |
cluster |
15.6->16.4 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
9771 MB |
0 |
135 |
0 |
00:07:33 |
cluster |
12.18->15.8 |
3.3.2->3.3.2 |
3.3.2->3.3.2 |
8628 MB |
0 |
21 |
0 |
00:06:58 |
Заключение
Ключом к успешному внедрению стало соблюдение чеклистов и подготовка экземпляров — спасибо команде сопровождения PostgreSQL и сервису контроля инфраструктуры WatchDog.
Мы продолжаем развивать сценарий, и с нетерпением ждём появления в Банке версии PostgreSQL 17, чтобы добавить её поддержку в нашу «кнопку».
Что дальше
Если у вас есть опыт автоматизации обновлений PostgreSQL — поделитесь в комментариях. Какие подходы вы используете? Как решаете проблему rollback? Насколько глубоко интегрируете с CI/CD?
Мы открыты к диалогу.