i break you recoveryВ PostgreSQL начиная с очень давних времён аж версии 8.0 вышедшей в далёком 2005 году для восстановления в определённую точку времени использовался специальный файл конфигурации recovery.conf. Этот же файл впоследствии стал использоваться для режима standby и потоковой репликации.

Однако начиная со следующего релиза PostgreSQL 12 больше recovery.conf работать не будет: я его сломал.
Но зачем?

Была у recovery.conf одна особенность: читался он только при старте СУБД. И если для point-in-time recovery который нужен реже чем раз в год с этим смириться ещё можно, то вот необходимость рестарта всей базы для изменения адреса upstream сервера репликации несколько удручает. Видел разные способы извращений для обхода этого ограничения вроде использования L3 роутинга, схемы с каскадными репликациями (чтобы соответственно перезапускать не все реплики, а хотя бы только часть) и даже (хоть в production не встречал) walbouncer.

После очередного планового рестарта реплик только из-за необходимости изменить параметры репликации решил я поковырять, а чего будет стоить научить PostgreSQL перечитывать primary_conninfo по SIGHUP? Оказалось всё плохо. В принципе, необходимо поменять всего одну переменную в startup процессе и оттуда же запросить рестарт WalReceiver — и на этом всё, репликация продолжится с новым адресом корректно. Осталось корректно это реализовать. Несколько недель спустя я закончил патч с реализацией перечитывания recovery.conf по SIGHUP сигналу, при этом тот патч никак не ломал имеющееся поведение базы.

Затем, набравшись смелости, отправил его в список рассылок разработчиков PostgreSQL. На что довольно быстро ответил Michael Paquier:
Before making some of them as reloadable, let's switch them to be GUCs first and not re-invent the SIGHUP handling of parameters as your patch does.
Упс, оказалось, что я задавал поисковику неправильный вопрос. Спрашивать надо было не про перечитывание recovery.conf, а о превращении параметров из живущего отдельно recovery.conf в инфраструктуру GUC (grand unified configuration), используемую для всех остальных параметров СУБД. То есть однозначно нет, такой патч нам не нужен, такое мы не хотим. Давайте сначала перенесём все эти настройки из recovery.conf в нашу стандартную инфраструктуру настроек.

На этом невесёлом известии я погоревал и подумал: «А вот давайте перенесём!». Почитал архивные обсуждения по правильному поисковому запросу, открыл последнее найденное обсуждение переноса настроек (ссылку любезно предоставил Michael Paquier в своём ответе, за что ему отдельное спасибо, как и за быстрый ответ). На тот момент мая 2018 года патч был заброшен уже больше года. Значит, отсюда и начнём. Или правильнее сказать «продолжим»? По плану развлечения:

  1. прочитать и составить список правок к последней опубликованной версии патча
  2. разобрать изменения в патче и перенести нужное на актуальную версию кодовой базы
  3. исправить все упоминания recovery.conf и его параметров в документации
  4. починить тесты
  5. отправить новый патч в список рассылок
  6. получить какие-нибудь отклики
  7. чего-нибудь поправить согласно пожеланиям и вернуться на пункт 5
  8. получить ещё раз отказ в принятии патча (через годик-полтора)

Похоже на план действий? Ну вот и двигаемся по нему!

Долго ли, коротко ли, добрался до пункта номер пять и 21 июня 2018 года опубликовал новую версию патча, в новой ветке обсуждений. Затем 3 месяца в гнетущей тишине леденящего душу молчания рыбы Баскервилей. На исходе сентября неожиданно к патчу проявляет интерес Peter Eisentraut — один из Core разработчиков с правом коммита. Спустя несколько итераций правок, пока я тихо-мирно уехал на несколько дней погулять-посмотреть Будапешт и осматриваю достопримечательности, вдруг приходит обескураживающее письмо от Peter Eisentraut:
I went over the patch and did a bunch of small refinements. I have also updated the documentation more extensively. The attached patch is committable to me.
То есть, Peter Eisentraut поправил ещё некоторые мелочи по своему усмотрению, обновил документацию, сжёг целиком раздел recovery-config.sgml и считает, что в таком виде патч уже можно принимать. Ой, а я думал это случится только для postgresql 13, даже если так очень повезёт, что патч вообще дойдет до состояния готовности к коммиту.

И несколькими днями спустя, а именно 25 ноября этого 2018 года, Peter Eisentraut действительно принимает этот патч:
recovery.conf settings are now set in postgresql.conf (or other GUC sources). Currently, all the affected settings are PGC_POSTMASTER; this could be refined in the future case by case.
Recovery is now initiated by a file recovery.signal. Standby mode is initiated by a file standby.signal. The standby_mode setting is gone. If a recovery.conf file is found, an error is issued.
The trigger_file setting has been renamed to promote_trigger_file as part of the move.
The documentation chapter «Recovery Configuration» has been integrated into «Server Configuration».
pg_basebackup -R now appends settings to postgresql.auto.conf and creates a standby.signal file.
Author: Fujii Masao <masao(dot)fujii(at)gmail(dot)com>
Author: Simon Riggs <simon(at)2ndquadrant(dot)com>
Author: Abhijit Menon-Sen <ams(at)2ndquadrant(dot)com>
Author: Sergei Kornilov <sk(at)zsrv(dot)org>
Уже прошло две недели и этот коммит не откатили. Удивительно. И, кажется, даже не собираются. Поразительно. Неизвестно, не решит ли сообщество поменять поведение в какую-нибудь сторону ещё раз, тем более до feature freeze релиза postgresql 12 в апреле ещё есть немного времени. Но, похоже, больше recovery.conf всё-таки у нас не будет.

Так что же изменилось


Сперва-наперво, СУБД откажется стартовать, если найдёт recovery.conf файл — это было сделано специально, чтобы пользователь воспользовавшись одной из множества старых инструкций не удивлялся, почему база игнорирует конфигурацию в этом файле.

Старая настройка standby_mode пропала. Теперь её, равно и как сам факт наличия recovery.conf для включения режима восстановления, заменяют два файла (любого содержания, обычно пустые):

  • $PGDATA/recovery.signal — идеалогический наследник standby_mode=off, восстановление из архива будет произведено до указанной в конфигах точки;
  • $PGDATA/standby.signal — соответственно, standby_mode=on. Этот файлик мы будем видеть на всех репликах

Если startup процесс базы нашёл оба файла — то считаем что мы в режиме standby.

Если для ясности свести изменения параметров в табличку:
old recovery.conf
PostgreSQL 12+ postgresql.conf
primary_conninfo
primary_conninfo
primary_slot_name
primary_slot_name
trigger_file
promote_trigger_file
recovery_min_apply_delay
recovery_min_apply_delay
recovery_target
recovery_target
recovery_target_name
recovery_target_name
recovery_target_time
recovery_target_time
recovery_target_xid
recovery_target_xid
recovery_target_lsn
recovery_target_lsn
recovery_target_inclusive
recovery_target_inclusive
recovery_target_timeline
recovery_target_timeline
recovery_target_action
recovery_target_action
restore_command
restore_command
archive_cleanup_command
archive_cleanup_command
recovery_end_command
recovery_end_command

То можно заметить, что изменено чуть меньше чем ничего. На данный момент (за единственным исключением префикса promote_ для promote_trigger_file) все новые параметры называются точно так же как и старые и принимают те же самые возможные значения. Хотя на самом деле есть ещё изменение касательно настроек цели для восстановления. Не знаю, пользовался ли этим кто-то раньше, но это было задокументированное поведение и можно было указать одновременно несколько recovery_target, recovery_target_lsn, recovery_target_name, recovery_target_time или recovery_target_xid. Например,

recovery_target_lsn = '1/1D9FEA00'
recovery_target_xid = '5238954'

Реально использовалась для восстановления последняя строка из recovery.conf. Больше так нельзя, цель для восстановления должна быть указана максимум одна. Впрочем, из-за логики инфраструктуры GUC по-прежнему можно указать одноимённый параметр несколько раз:

recovery_target_lsn = '1/1D9FEA00'
recovery_target_lsn = '1/16AC7D0'

Такое допустимо, восстанавливаться будем до значения настройки указанной последней.

И, в общем-то, это всё что следует сказать об изменениях видимых снаружи PostgreSQL. pg_basebackup -R (--write-recovery-conf) остался на своём месте и делает то что и предназначен, лишь только теперь он вместо recovery.conf добавит параметры в postgresql.auto.conf и создаст файл standby.signal

К сожалению, говоря о том, что это все видимые в данный момент изменения я именно это и имею в виду. Все новые параметры по-прежнему установлены как PGC_POSTMASTER, то есть могут быть изменены только при старте PostgreSQL. Как уже упоминалось, это было требование со стороны сообщества разработчиков: сначала перенести все настройки, и только потом уже смотреть, можно ли их менять на запущенной базе. Так что сейчас PostgreSQL в замечательной стадии разработки, когда старое поведение уже сломали, а изменений к лучшему ещё не завезли.

Я уже опубликовал патч который разрешит менять primary_conninfo и primary_slot_name налету. Посмотрим, что из этого получится.

Простите, я сломал ваш recovery.conf

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


  1. vesper-bot
    12.12.2018 14:39
    +2

    Спасибо за решительность и последовательность в воплощении того, что приносит пользу многим людям.


  1. tgz
    12.12.2018 15:07

    Прекрасная работа, recovery.conf нинужын.


  1. shurutov
    12.12.2018 15:29
    +1

    Осталось pg_hba.conf выкинуть, перенеся информацию в БД, и будет совсем хорошо.
    trust и ident — исключить как класс, ибо небезопасно.
    local peer — прибить гвоздями, всё остальное — через CREATE/ALTER USER/ROLE CONNECT FROM <откуда> BY/VIA и/или GRANT CONNECT TO INSTANCE FROM <откуда> TO BY/VIA .
    По умолчанию разрешить подключаться локально (и сокет, и 127.0.0.1) по через SCRAM-SHA-256.
    PUBLIC-у по-умолчанию никаких грантов на подключение.


    1. Melkij Автор
      12.12.2018 15:39

      На самом деле мысль интересная (хоть и не возьмусь proposal писать)
      Очевидный встречный вопрос от сообщества будет, а как сделать разные настройки для реплик (для начала «как» на уровне описания желаемого поведения). Это явный usecase, для, например, аналитической реплики с прямым доступом для сотрудников, но в то же время не пускать их на мастер. Да, можно через firewall отпилить доступ, но обосновать целесообразность ломания поведения правил доступа надо будет.


      1. shurutov
        12.12.2018 17:13

        Да, с доступом только к реплике — это вопрос.
        В рамках предлагаемого переноса видится такое решение:
        INSTANCE использовать для доступа и к мастеру, и к репликам;
        MASTER/REPLICA в TO использовать для ограничения только к мастеру (пока непонятно для зачем ограничивать только к мастеру, но мало ли) и реплике.
        Запросы, соответственно, выполняются на мастере.


        1. shurutov
          12.12.2018 17:16

          Для определения мастера/реплики использовать pg_is_in_recovery().


          1. Melkij Автор
            12.12.2018 17:38

            Определить кто сейчас не в recovery — это элементарный вызов RecoveryInProgress() и совсем не проблема. Их много по коду раскидано в нужных местах.

            А вот придти с таким proposal к какому-то консенсусу — задача нетривиальная и я думаю будут противники поактивнее, чем для отпиливания recovery.conf. Пачка настроек вне GUC сообщество не устраивала уже не первый год, а вот про hba как-то не уверен.
            Если кто почувствует в себе силы — настоятельно рекомендую сначала написать proposal в pgsql-hackers и только потом начинать сооружать патч. И разделить proposal на 3 разных: соответственно под спиливание trust/ident, не выдавать connect всем при создании базы, собственно замена pg_hba.


            1. nox1725
              13.12.2018 11:08

              Посмотрите как сделано в HP Vertica, которая, кстати, была основана на кодовой базе PostgreSQL, но потом ушла сильно в сторону

              Там есть два варианта — full backup / copy cluster или репликация, так вот в первом случае переносится вся база, включая гранты, а во втором случае переносятся только структура таблиц и данные, соответсвенно легко можно назначить разные права на master/replica.

              Думаю такое же поведение можно продумать и для PostgreSQL — сделать два режима репликации — с переносом прав (системных таблиц) или нет.

              Или же сделать еще проще и дать возможность исключать таблицы (она есть в логической репликации же) и соответственно по дефолту включить таблицы отвечающие за права в blacklist

              p.s. но я не возьмусь даже за proposal, не то что за реализацию, к сожалению(((


              1. Melkij Автор
                13.12.2018 12:16

                физическая и логическая репликация соответственно. Логическая репликация уже появилась в postgresql 10.
                Здесь можно много писать об их различиях, но и у того и у другого подхода есть как плюсы, так и ограничения. И против выбрасывания физической репликации я буду возражать очень сильно — она проста, надёжна и не требовательна ни к CPU ни к памяти. Взять страничку, записать страничку. Ну, ладно, чуть сложнее на самом деле из-за обработки конфликтов репликации. Но до сложностей логической репликации очень далеко. А игнорировать в физической репликации часть таблиц нельзя. И даже если сделать можно (против чего опять же даже я буду сильно возражать) — то проблем от этого добавится много. В частности, что делать с durability этой таблицы, раз её нет в wal (реплика свои wal писать не может и не должна).


                1. nox1725
                  13.12.2018 12:25

                  Я говорю только о системных, специальных таблицах, которые являются аналогом hal, то есть это гранты, но в более гибком смысле. Они могут (и скорее всего даже должны) храниться отдельно, а не как типичная таблица и находиться после старта БД в памяти, но записываться на диск само собой

                  В таком случае WAL писать не нужно, как и ломать физическую репликацию, достаточно добавить ключ, вроде того — синхронизировать гранты или нет

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

                  Пример: www.vertica.com/docs/9.1.x/HTML/index.htm#Authoring/SQLReferenceManual/SystemTables/CATALOG/GRANTS.htm — весь Catalog хранится в datapath, но он отделен от данных, то есть таплы для grants можно копировать (fullbackup), а можно и не копировать


                  1. Melkij Автор
                    13.12.2018 14:18

                    В таком случае WAL писать не нужно

                    Как их восстанавливать при сбое? Важно: согласованно с состоянием системного каталога и, особенно теми несколькими shared relations, например как раз pg_authid.
                    Сейчас все системные таблицы обязательно WAL-logged.
                    Они специальные, дополнительно защищены, ряд таблиц запинен даже в private memory каждого backend'а, а не только в shared_buffers.
                    Как делать rollback?

                    С внешним pg_hba.conf прячем голову в песок, это конфигурация, СУБД его читает при старте/reload, в остальное время он неважен и обязанность админа за ним следить, как и за основным конфигом. На уровне SQL это уже будет обязанность базы. Вы замечали, что даже postgresql.auto.conf пишется во временный файл и затем подменяет старый с fsync?
                    Тем более важно по той причине, что без postgresql.auto.conf работать можно, а вот без правил авторизации — уже нет, вы и подключиться к базе не сможете, чтобы перебить руками grant connect from из бекапа.


                    1. nox1725
                      13.12.2018 15:07

                      В общем да, соглашусь, что вопрос не простой. Можно вести отдельный WAL-лог — единственное что на ум приходит, с другой стороны очевидно, что это костыль


                      1. Melkij Автор
                        13.12.2018 15:30

                        А отдельный WAL нельзя атомарно записать. Нет атомарного fsync на два файла сразу. И надо придумывать двухфазный коммит в пределах одной базы для этого или консистентно уже не встать: может быть ситуация, что мы записали один wal, но не записали второй.


                        1. nox1725
                          13.12.2018 16:08

                          Для таблицы вроде grants не нужна атомарность на уровне БД же.

                          При создании сессии пользователя или даже при попытке выполнения SQL мы проверяем в системной таблице в памяти — имеет ли он на это право, если да — генерируем исключение, если нет — выполняем его SQL

                          Зачем тут атомарность? Мы просто заменим правку HAL + релоад на механизм хранения этих данных «рядом» с данными в БД

                          Ну не записали мы grant — свет выключили — во-первых у нас есть юзер postgres с полными правами (кстати в упомянутой Vertica есть dbadmin, он как root в Linux внутри БД) и мы можем накатить грантов после того как сервер оживет. Вариантов с непустой БД и отсутствием прав на нее для некоего админа представить сложно


                          1. Melkij Автор
                            13.12.2018 16:24

                            То есть вы предлагаете исключить из механизма грантов на коннект суперпользователя? Почему? И при каких условиях он тогда должен проходить авторизацию, а когда — нет? Почему?

                            А если так получилось, что ему нужен сетевой доступ? Или, наоборот, хотим дать ему доступ только через unixsock? Дефолтом это сделать нельзя, unixsock существует не на всех поддерживаемых платформах.

                            Гранты на выполнение запросов и на доступ к объектам внутри конкретной базы есть и они обязаны быть транзакционными и wal-logged, об этом речи нет.


                            1. nox1725
                              13.12.2018 16:42

                              Расскажу на примере той же Вертики, очень уж нравится мне их архитектура, при том, что она остается в чем-то postgres-like

                              У них есть superuser, который просто системный пользователь и у него всегда тип авторизации только пароль, при этом он рекомендуется к использованию только для запуска/остановки базы, бэкапов и других сервисных функций + давать права pseudosuperuser'ам. При этом у него есть сетевой доступ

                              А вот как раз pseudosuperuser это уже роль и она хранится в самой БД и соответственно делегируется другим пользователям через GRANT pseudosuperuser TO username;

                              То есть тут четко разделяется сервисный уровень и уровень доступа к данным

                              При этом реализуется полная и частичная репликация на WAL уровне, т.к. просто объекты (страницы) принадлежащие конкретным схемам, таблицам и т.д. копирутся просто как файлы, а каталог формируется в виду снапшота (каталог описывает какой логической сущности какие файлы/страницы соответствуют)

                              Правда стоит отметить, что все-таки Вертика не реляционная база, хоть и ACID

                              UDP: ну да и у них нет авторизации типа ident и access


    1. a0fs
      12.12.2018 22:26
      +1

      Осталось pg_hba.conf выкинуть

      А что плохого в этом файлике. С применением параметров проблем нет, и ничего из вне не сможет добавить лишних доступов. ИМХО очень полезный файлик местами.


      1. Melkij Автор
        12.12.2018 23:35

        Да в общем-то ничего плохого. Для бекапа всё равно надо бекапить и основной конфиг, файлом больше/файлом меньше не сильная разница. Выставлять базу в мир нельзя, ddos'ом на авторизацию кладётся элементарно, а для своих рабочих подсетей нередко просто all/all по паролю есть правило будь то md5 или SCRAM-SHA-256. Или, что в моей практике встречается чаще — всё равно есть отдельный userlist на pgbouncer, а база на сетевом интерфейсе доступна для работы репликации.

        Правила доступа непосредственно в базе рядом с самими пользователями имеют как преимущества (хотя бы не надо конфиг перезагружать, что не проблема, но надо помнить об этом и, что более важно, проверять по логу, успешно ли загрузился конфиг или где опечатались — популярная ошибка указать IP без маски сети), так и недостатки.
        И вот поскольку pg_hba.conf есть уже — то любому, кому этот конфиг не нравится, придётся как раз описывать его недостатки и преимущества переноса правил в саму базу.

        ничего из вне не сможет добавить лишних доступов

        Вот, тоже полезная точка зрения.

        А вот ограничить в использовании trust и не давать connect права всем на новую базу — это я бы поддержал.