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.