Если у вас PostgreSQL с включённым row-level security и ночной pg_dump по таймеру, откройте каталог с бэкапами и сравните размеры вчерашнего и позавчерашнего дампов. Если файлы подозрительно одинаковые и маленькие, возможно, у вас нет ни одного рабочего бэкапа. У нас так было не меньше двух недель.
Что мы увидели
Небольшой VPS, на нём один из наших проектов с PostgreSQL. Каждую ночь запускается systemd-таймер, он вызывает скрипт, скрипт делает pg_dump в каталог бэкапов. Файлы появлялись исправно:
appdb-<дата>.dump ~120 КБ appdb-<дата>.dump ~120 КБ appdb-<дата>.dump ~120 КБ
Одинаковый размер у дампов базы с живыми данными уже странно. В журнале юнита нашлась причина:
pg_dump: error: query failed: ERROR: query would be affected by row-level security policy for table “”
Юнит падал при каждом запуске. Подтверждённые падения в журнале за четыре последних ночи, но журнал ротируется, а по датам файлов битые дампы шли не меньше двух недель подряд.
Опасность в том, что файл остаётся. pg_dump открывает выходной файл в начале, а упав на таблице с политикой, оставляет за собой недописанный файл. По каталогу бэкапов всё выглядит нормально: файлы по расписанию, свежие даты. Падение видно только в журнале юнита, а смотрит туда тот, кто уже подозревает.
Почему pg_dump падает
По документации pg_dump по умолчанию выставляет row_security = off, чтобы выгрузить все строки таблицы без исключения. Если у роли для этого не хватает прав, он завершается с ошибкой, а не выгружает часть данных. Права обходить RLS бывают у суперпользователя, у роли с атрибутом BYPASSRLS и у владельца таблицы (если не включён FORCE ROW LEVEL SECURITY).
В нашем случае дамп шёл под ролью, которая называлась как будто хозяйская (..._owner), но таблицами не владела: их владельцем был postgres. Атрибута BYPASSRLS у неё не было, а на таблицах были включены политики, и дамп упал на первой же такой таблице.
Самый короткий фикс, который я не применил
Одна строка:
ALTER ROLE <backup_role> BYPASSRLS;
Остановило то, что эта роль не просто «для бэкапа». Под ней работает само приложение. Выдать ей BYPASSRLS означало бы отключить row-level security для приложения на всех 11 таблицах, включая таблицу с учётными данными пользователей.
Отдельная роль только для бэкапа с BYPASSRLS тоже рабочий вариант. Мы пошли другим путём.
Что сделали
Дамп стал сниматься суперпользователем postgres через unix-сокет:
runuser -u postgres – env -u PGUSER -u PGPASSWORD
pg_dump -h /var/run/postgresql -U postgres
–format=custom --no-owner --no-privileges appdb > “$backup_file”
Два замечания к этой строке.
runuser, а не sudo. В юните стоит NoNewPrivileges=true, и sudo под ним не работает: ему нужен setuid. runuser вызывается от root и просто переключает пользователя, ему повышение привилегий не нужно.
Редирект вместо --file. Каталог бэкапов принадлежит root, а pg_dump теперь работает от имени postgres и не смог бы создать в нём файл. Поэтому вывод идёт в stdout, а открывает файл оболочка скрипта, которая работает от root.
env -u PGUSER -u PGPASSWORD сбрасывает переменные окружения, которые могли бы подменить пользователя и пароль подключения.
Как мы проверили, что теперь настоящий бэкап
Файл уже один раз нас обманул, поэтому на этот раз проверок было несколько:
размер дампа вырос больше чем в пять раз по сравнению с битыми;
совпала контрольная сумма;
pg_restore --listпрошёл без ошибок;тестовое восстановление в отдельную базу прошло без ошибок;
число строк в восстановленной базе совпало с боевой по каждой таблице, которую мы сверяли.
Тестовую базу после этого удалили.
Что бы я заложил в скрипт сразу
Это набросок под такую же ситуацию, а не наш боевой скрипт:
set -euo pipefail
DB=appdb DIR=/var/backups/postgresql/appdb
tmp=“DIR” .dump.XXXXXX)" trap ‘rm -f “$tmp”’ EXIT
runuser -u postgres – env -u PGUSER -u PGPASSWORD
pg_dump -h /var/run/postgresql -U postgres
–format=custom --no-owner --no-privileges “
"" class="formula inline">tmp”
pg_restore --list “$tmp” > /dev/null
mv “DIR/
(date +%F).dump”
Дамп пишется во временный файл, проверяется pg_restore --list и только потом получает настоящее имя. Если pg_dump упал, под настоящим именем файла просто нет, и такой дамп невозможно принять за рабочий.
Что стоит проверить у себя
Откройте каталог бэкапов и посмотрите на размеры подряд идущих дампов. Одинаковый размер у меняющейся базы повод для вопросов.
Посмотрите журнал юнита или cron за последний месяц на ошибки
pg_dump.Если у вас RLS, выясните, у какой роли идёт дамп, чем она владеет и есть ли у неё
BYPASSRLS. Не выдавайтеBYPASSRLSроли, под которой работает приложение.Регулярно восстанавливайте последний дамп в отдельную базу и сверяйте числа с боевой. Для больших баз полное восстановление дорого, тогда выберите несколько таблиц и восстановите только их.
Мониторьте то, что отличает рабочий бэкап от нерабочего: размер файла относительно предыдущего и статус юнита. Факт появления файла не доказывает ничего.
Начните с первых двух пунктов: на них уйдёт несколько минут.

