Учебник веб-разработки
Разделы учебника
На этой странице

XI. DevOps

PostgreSQL: от инициализации до восстановления

Оглавление · SQL и JSONB · Миграции схемы · Эксплуатация

Задача: различать запуск сервера, состояние базы, подключение приложения и возможность восстановления. В Atmanki PostgreSQL 17 хранит данные Strapi; Next.js читает CMS API, а не подключается к БД напрямую.

Сервер, база, схема, роль

PostgreSQL server управляет кластером баз в одном data directory. Здесь «кластер» не означает несколько VPS: один сервер может содержать несколько баз. База содержит schemas — пространства имён таблиц и других объектов. Схема public не является JSON/Zod-схемой. Роль — субъект прав: она может входить, владеть объектами и получать разрешения. Имена базы, пользователя и сервиса могут совпадать, но это разные сущности.

Исходник схемы
flowchart TD
  Volume["postgres_data: постоянный data directory"] --> PG["PostgreSQL server"]
  PG --> Roles["Роли сервера: atmanki, strapi"]
  PG --> DB["База strapi"]
  DB --> Schema["Schema public"]
  Schema --> Tables["Таблицы, индексы, ограничения"]
  CMS["Процесс Strapi: пул соединений"] -->|"postgres:5432, роль strapi"| DB

compose.yaml задаёт POSTGRES_USER=atmanki и POSTGRES_DB=atmanki. init.sh создаёт отдельную роль strapi и базу strapi с этим владельцем. Это SQL-роль приложения, не администратор Content Manager. Создание схемы контентных таблиц затем относится к Strapi.

Новый и существующий том запускаются по-разному

На пустом data directory официальный образ выполняет initdb, создаёт начальные роль/базу и запускает файлы из /docker-entrypoint-initdb.d. При существующих данных эта инициализация пропускается. Пересоздание контейнера с тем же томом сохраняет пользователей и их пароли. Официальный образ PostgreSQL.

Исходник схемы
flowchart TD
  Start["Запуск контейнера PostgreSQL"] --> Empty{"Data directory пуст?"}
  Empty -->|да| Init["initdb и начальная роль/база"]
  Init --> Scripts["init.sh: роль и база Strapi"]
  Scripts --> Serve["Запуск сервера"]
  Empty -->|нет| Existing["Использовать сохранённое состояние"]
  Existing --> Serve

Поэтому изменение STRAPI_DB_PASSWORD только в .env не меняет пароль уже созданной SQL-роли. Нужна согласованная ротация роли и настроек клиента; конкретные действия выполняет администратор в отдельном этапе. Неверный пароль не исправляют удалением volume. При сбое первого init тоже исследуют логи и фактические роли: частично инициализированный data directory нельзя считать чистым повтором всех скриптов.

Контейнер, schema migration и импорт контента имеют разные жизненные циклы. Повторный deploy не должен автоматически заново создавать пользователей или затирать редакторские данные. Правила импорта.

Соединения — ограниченный ресурс

Strapi использует пул: запрос получает соединение, выполняет работу и возвращает его. В database.ts для PostgreSQL min=2, max=10 по умолчанию, acquireConnectionTimeout=60000 мс; значения можно переопределить. Это текущие настройки, не универсальные оптимальные числа. Конфигурация соединений Strapi.

Если работают k процессов CMS с максимумом p, верхняя граница их пулов — k × p. К ней добавляются importer, миграции и административные подключения. Лимит БД не увеличивается автоматически вместе с числом контейнеров. Очередь ожидания соединения и медленный SQL — разные задержки; повышение max может усилить перегрузку.

Транзакция должна оставаться на одном соединении. BEGIN на одном клиенте и UPDATE на другом не создают общей транзакции. Долгая транзакция удерживает соединение и может удерживать блокировки; не ждите пользовательского ввода или медленного внешнего API внутри неё без конкретной причины. У web в Atmanki нет такого пула: его запрос к CMS — отдельный HTTP-путь.

Запись, WAL и обслуживание

Транзакция задаёт границу commit/rollback; откат отменяет её изменения в БД, но не выполненную загрузку в S3. Сбой ответа после COMMIT может оставить клиенту неизвестный результат. Это связано с идемпотентностью, а не решается повтором любого запроса. Транзакции PostgreSQL.

WAL записывает изменения до соответствующих страниц данных и позволяет серверу восстановиться после сбоя. Успех COMMIT и долговечность зависят также от настроек синхронизации и надёжности хранилища. WAL crash recovery не спасает от осмысленного DELETE, потери всех дисков или повреждения всех доступных копий. Write-Ahead Logging.

При UPDATE/DELETE старые версии строк не исчезают немедленно. VACUUM освобождает место для повторного использования и поддерживает служебное состояние; ANALYZE обновляет статистику планировщика. Autovacuum выполняет обслуживание автоматически, но его эффективность зависит от нагрузки и долгих транзакций. Обычный VACUUM не обязан уменьшить файл на диске; VACUUM FULL переписывает таблицу и требует сильной блокировки. Обслуживание PostgreSQL.

Рост диска не диагностируют только по числу записей. Нужны размеры таблиц/индексов, WAL, частота изменений и состояние обслуживания. Эта глава не добавляет настройку autovacuum или ручной VACUUM FULL в production.

Чтение состояния без payload

На отдельном учебном подключении можно посмотреть агрегаты:

SELECT current_database(), current_user, version();

SELECT state, wait_event_type, count(*) AS connections
FROM pg_stat_activity
WHERE datname = current_database()
GROUP BY state, wait_event_type
ORDER BY state, wait_event_type;

active означает исполняющийся запрос, idle — ожидание следующего запроса, idle in transaction — открытая транзакция без текущего SQL. Wait event помогает найти ожидание, но один снимок не показывает всю историю. Видимость чужих sessions зависит от прав. Здесь не выводим query: он может содержать приватные данные. pg_stat_activity. SQL этой главы не исполнялся в текущем этапе; локального PostgreSQL здесь нет.

pg_isready показывает состояние приёма соединений сервером. Он не требует правильных credentials для самого определения этого состояния и не подтверждает доступ роли strapi к нужным таблицам. pg_isready. Для прикладной готовности нужны реальное подключение и операция CMS; см. контракт probes.

Backup — артефакт, restore — проверяемый процесс

Логический pg_dump получает согласованное представление одной базы; custom format -Fc восстанавливается pg_restore. Dump одной базы не включает все роли сервера: для globals существует отдельный pg_dumpall --globals-only. Нужны также совместимые версии, владельцы и права. SQL dump PostgreSQL.

Физическая копия — другой формат. Обычный tar работающего data directory не заменяет согласованную физическую копию. PITR требует базовой копии и необходимой цепочки WAL; наличие WAL внутри работающего сервера не означает настроенный архив. PITR. В Atmanki регулярное архивирование WAL/PITR не настроено.

Исходник схемы
flowchart LR
  Live["БД, media, ключи и версия приложения"] --> Backup["Согласованная копия"]
  Backup --> Offsite["Защищённая копия вне VPS"]
  Backup --> Isolated["Изолированное восстановление"]
  Isolated --> Check["Схема, записи, права, изображения, CMS"]
  Check --> Plan["План переключения и критерии успеха"]

RPO — допустимая потеря данных по времени, RTO — допустимое время восстановления. Если последняя пригодная копия была ночью, возврат к ней может потерять дневные правки. Восстановление «успешно за пять минут» не считается RTO без подготовки сервера, ключей, медиа и проверки приложения. Это цели, измеряемые упражнением, не гарантии текущего VPS.

backup-content.sh делает предмиграционную копию, пробует восстановить SQL в scratch-базу того же PostgreSQL и Garage в отдельную сеть. Он читает количество news и контрольный объект; это ограниченная проверка, не доказательство всех связей и сценариев. Она не подтверждает готовность после полной потери хоста. Свежий restore здесь не запускался; порядок работы — в эксплуатации и жизненном цикле медиа.

Версия сервера и версия данных

Minor-обновление и переход на новый major — разные задачи. Для major нужны совместимый перенос через dump/restore, pg_upgrade или другой проверенный маршрут. Смена image tag поверх старого volume не является миграцией. Обновление PostgreSQL. Сначала проверяют копию, расширения, поведение запросов и путь возврата; версия PostgreSQL в этой задаче остаётся 17.

Упражнение

После изменения .env CMS перестала подключаться, но PostgreSQL healthy. Назовите три отдельные проверки: состояние сервера, аутентификация роли, доступ к объектам. Затем составьте минимальный набор для восстановления сайта на новом хосте: dump, globals или восстановленные роли, media, ключи, образы, конфигурация и критерии проверки. Отметьте, что нельзя получить из одного dump.