Если у вас 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роли, под которой работает приложение.Регулярно восстанавливайте последний дамп в отдельную базу и сверяйте числа с боевой. Для больших баз полное восстановление дорого, тогда выберите несколько таблиц и восстановите только их.
Мониторьте то, что отличает рабочий бэкап от нерабочего: размер файла относительно предыдущего и статус юнита. Факт появления файла не доказывает ничего.
Начните с первых двух пунктов: на них уйдёт несколько минут.
Ast95NG
Удивительное дело, вот уже лет 15 ситуация с бэкапами у "стандарнтного" PG не меняется ни капли, как их не было, так и нет. При том что продукт невероятно успешный и распространенный. pg_dump - не бэкап, это средство переноса баз на другой инстанс ровно той же версии ровно с теми же настройками. Бэкап для СУБД - это когда необязательно монопольно, не только вся база, можно только накопленные изменения, возможность поднять в любом инстансе не старее источника, на любую точку времени. И без необходимости проверять каждый раз, поднимется оно вообще, или нет, сработало оно вообще, или сеанс пользователя подвис.
Неужели это так сложно? Вроде же понятно, как такое сделать, и очень нужно.
alexeyparshin
Backup у PG - не монопольно, можно только накопленные изменения (WALs), можно поднять в любом instance не старее источника на любую точку времени. Нет только одного - backup отдельной базы. pg_dump + pg_restore - это не backup.
Ast95NG
Это сторонний софт, в котором нужно разбираться, который совместим с некоторыми версиями, некоторыми бранчами, для некоторых платформ, под какими-то лицензиями, иногда бесплатными.
Я хочу встроенную в базовый дистрибутив родную систему бэкапов. Надежную и доверенную не меньше, чем весь остальной движок. Не может быть СУБД без бэкапов, это как докупать двигатель к машине у другой конторы через дорогу.
alexeyparshin
Это - базовая функциональность PG, встроенная, надёжная, etc.
yakov_etern8 Автор
Согласен по терминологии: pg_dump это логическая выгрузка, а не бэкап в смысле базовой копии с архивом WAL, откуда базу можно поднять на любую точку времени. Статья про более узкую вещь: ночной дамп падал на row-level security и оставлял недописанный файл, который выглядел как бэкап. Физическая копия от этой ловушки как раз не страдает, тут вы правы.
Для небольших баз с суточной точкой восстановления дампа часто хватает, для остальных нужен base backup плюс WAL (pgBackRest, Barman). Но и там поднимать копию на отдельном инстансе стоит регулярно: дыра в архиве WAL или битая базовая копия находятся только так. @alexeyparshin, про отдельную базу согласен, из физической копии её не достать, нужен временный инстанс.
alexeyparshin
Я бы еще добавил, что каждая команда в скрипте возвращает заветный нолик, или код ошибки. Есть при этом файл в output directory или нет - неважно.