Если у вас 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=“(mktemp -pDIR”.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 “tmpDIR/DB-(date +%F).dump”

Дамп пишется во временный файл, проверяется pg_restore --list и только потом получает настоящее имя. Если pg_dump упал, под настоящим именем файла просто нет, и такой дамп невозможно принять за рабочий.

Что стоит проверить у себя

  • Откройте каталог бэкапов и посмотрите на размеры подряд идущих дампов. Одинаковый размер у меняющейся базы повод для вопросов.

  • Посмотрите журнал юнита или cron за последний месяц на ошибки pg_dump.

  • Если у вас RLS, выясните, у какой роли идёт дамп, чем она владеет и есть ли у неё BYPASSRLS. Не выдавайте BYPASSRLS роли, под которой работает приложение.

  • Регулярно восстанавливайте последний дамп в отдельную базу и сверяйте числа с боевой. Для больших баз полное восстановление дорого, тогда выберите несколько таблиц и восстановите только их.

  • Мониторьте то, что отличает рабочий бэкап от нерабочего: размер файла относительно предыдущего и статус юнита. Факт появления файла не доказывает ничего.

Начните с первых двух пунктов: на них уйдёт несколько минут.

Комментарии (6)


  1. Ast95NG
    08.10.2026 11:13

    Удивительное дело, вот уже лет 15 ситуация с бэкапами у "стандарнтного" PG не меняется ни капли, как их не было, так и нет. При том что продукт невероятно успешный и распространенный. pg_dump - не бэкап, это средство переноса баз на другой инстанс ровно той же версии ровно с теми же настройками. Бэкап для СУБД - это когда необязательно монопольно, не только вся база, можно только накопленные изменения, возможность поднять в любом инстансе не старее источника, на любую точку времени. И без необходимости проверять каждый раз, поднимется оно вообще, или нет, сработало оно вообще, или сеанс пользователя подвис.

    Неужели это так сложно? Вроде же понятно, как такое сделать, и очень нужно.


    1. alexeyparshin
      08.10.2026 11:13

      Backup у PG - не монопольно, можно только накопленные изменения (WALs), можно поднять в любом instance не старее источника на любую точку времени. Нет только одного - backup отдельной базы. pg_dump + pg_restore - это не backup.


      1. Ast95NG
        08.10.2026 11:13

        Это сторонний софт, в котором нужно разбираться, который совместим с некоторыми версиями, некоторыми бранчами, для некоторых платформ, под какими-то лицензиями, иногда бесплатными.

        Я хочу встроенную в базовый дистрибутив родную систему бэкапов. Надежную и доверенную не меньше, чем весь остальной движок. Не может быть СУБД без бэкапов, это как докупать двигатель к машине у другой конторы через дорогу.


        1. alexeyparshin
          08.10.2026 11:13

          Это - базовая функциональность PG, встроенная, надёжная, etc.


    1. yakov_etern8 Автор
      08.10.2026 11:13

      Согласен по терминологии: pg_dump это логическая выгрузка, а не бэкап в смысле базовой копии с архивом WAL, откуда базу можно поднять на любую точку времени. Статья про более узкую вещь: ночной дамп падал на row-level security и оставлял недописанный файл, который выглядел как бэкап. Физическая копия от этой ловушки как раз не страдает, тут вы правы.

      Для небольших баз с суточной точкой восстановления дампа часто хватает, для остальных нужен base backup плюс WAL (pgBackRest, Barman). Но и там поднимать копию на отдельном инстансе стоит регулярно: дыра в архиве WAL или битая базовая копия находятся только так. @alexeyparshin, про отдельную базу согласен, из физической копии её не достать, нужен временный инстанс.


      1. alexeyparshin
        08.10.2026 11:13

        Я бы еще добавил, что каждая команда в скрипте возвращает заветный нолик, или код ошибки. Есть при этом файл в output directory или нет - неважно.