Потоковая репликация PostgreSQL 18 со слотами репликации

Содержание

Потоковая репликация PostgreSQL 18 со слотами репликации#

Пошаговое руководство по настройке физической потоковой репликации (physical streaming replication) в асинхронном режиме с обязательным использованием слотов репликации.

Используется штатный кластер 18/main, который создаётся автоматически при установке пакета postgresql-18. Никаких дополнительных кластеров, каталогов данных, systemd-юнитов и правок сервисов не требуется.

Схема: один master (primary) и произвольное число реплик (standby), читающих WAL напрямую с master. Слот репликации гарантирует, что master не удалит WAL-сегменты, которые ещё нужны конкретной реплике.

Все команды рассчитаны на чистую систему Debian/Ubuntu без предустановленного PostgreSQL.

Обозначения в тексте: блоки bash выполняются в оболочке на указанном узле, блоки sql — в интерактивном клиенте psql от имени суперпользователя (psql -U postgres), уже после подключения к нужному серверу.


Штатная раскладка кластера#

После установки пакета postgresql-18 на Debian/Ubuntu автоматически создаётся кластер 18/main, которым управляет postgresql-common:

Что

Где

Каталог данных (PGDATA)

/var/lib/postgresql/18/main

Конфиги

/etc/postgresql/18/main/

Каталог для доп. настроек

/etc/postgresql/18/main/conf.d/

Лог

/var/log/postgresql/postgresql-18-main.log

systemd-юнит

postgresql@18-main.service

Утилиты управления

pg_lsclusters, pg_ctlcluster, pg_dropcluster

Важная особенность: postgresql.conf и pg_hba.conf лежат вне каталога данных (в /etc/postgresql/18/main/), а postmaster запускается с -c config_file=/etc/postgresql/18/main/postgresql.conf. Поэтому базовый бэкап не затрагивает конфиги реплики — их не нужно восстанавливать после pg_basebackup.


Переменные, используемые в инструкции#

Перед началом подставьте свои значения — далее по тексту используются именно они:

# --- пути штатного кластера ---
PGDATA=/var/lib/postgresql/18/main          # каталог данных
PGCONF=/etc/postgresql/18/main              # каталог конфигов
CONFD=/etc/postgresql/18/main/conf.d        # каталог доп. настроек
PGBIN=/usr/lib/postgresql/18/bin            # бинарники PostgreSQL 18
UNIT=postgresql@18-main.service             # штатный systemd-юнит кластера
PGPASS=/var/lib/postgresql/.pgpass          # файл паролей (домашний каталог postgres)

# --- сеть ---
MASTER_IP=192.0.2.10                        # адрес master
REPLICA_IP=192.0.2.11                       # адрес ЭТОЙ реплики (на каждой свой)
SUBNET=192.0.2.0/24                         # подсеть, из которой разрешены подключения

# --- репликация ---
REPL_USER=replicator                        # пользователь БД для репликации
REPL_PASS=CHANGE_ME_strong_password         # пароль пользователя репликации
SLOT=replica1_slot                          # имя слота ЭТОЙ реплики (у каждой своё)
APP_NAME=replica1                           # application_name ЭТОЙ реплики

1. Установка PostgreSQL 18#

Выполняется на всех узлах (и master, и реплики).

1.1. Подключить репозиторий PGDG#

sudo apt update
sudo apt install -y wget ca-certificates

# ключ репозитория
sudo install -d -m 0755 /usr/share/keyrings
sudo wget -q -O /usr/share/keyrings/pgdg.asc \
  https://www.postgresql.org/media/keys/ACCC4CF8.asc
sudo chmod 0644 /usr/share/keyrings/pgdg.asc

# сам репозиторий
echo "deb [signed-by=/usr/share/keyrings/pgdg.asc] https://apt.postgresql.org/pub/repos/apt $(. /etc/os-release; echo $VERSION_CODENAME)-pgdg main" \
  | sudo tee /etc/apt/sources.list.d/pgdg.list

sudo apt update

В Debian 13 (trixie) пакета postgresql-18 в основных репозиториях нет — он есть только в PGDG. Если репозиторий PGDG уже подключён, шаг можно пропустить.

1.2. Установить пакеты#

sudo apt install -y postgresql-18 postgresql-client-18

При установке автоматически создаётся и запускается кластер 18/main. Проверка:

pg_lsclusters

Ожидаемо:

Ver Cluster Port Status Owner    Data directory              Log file
18  main    5432 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log

Проверка версии:

psql --version      # psql (PostgreSQL) 18.x

Важно. Пакет сразу поднимает кластер на порту 5432 и слушает только localhost. Никаких дополнительных кластеров создавать не нужно — вся дальнейшая настройка делается в этом же штатном кластере.

Если вы хотите использовать другой каталог данных, создайте отдельный кластер: sudo pg_createcluster 18 <имя> --datadir=/путь/к/данным и далее по тексту замените 18/main на 18/<имя> и путь PGDATA.


2. Настройка MASTER#

2.1. Параметры репликации#

Настройки кладём в отдельный файл в каталоге conf.d — так штатный postgresql.conf остаётся нетронутым, а настройки переживают обновление пакета. Каталог conf.d подключается в postgresql.conf директивой include_dir = 'conf.d' (в поставке Debian она уже присутствует).

sudo tee $CONFD/replication.conf >/dev/null <<'EOF'
# -----------------------------------------------------------------------------
# РЕПЛИКАЦИЯ (physical streaming replication)
# -----------------------------------------------------------------------------

listen_addresses = '*'               # по умолчанию localhost — репликам нужен TCP

wal_level = replica                  # WAL содержит всё необходимое для репликации
max_wal_senders = 10                 # макс. число одновременных процессов walsender
max_replication_slots = 10           # макс. число слотов репликации
hot_standby = on                     # разрешить чтение на резервном сервере
hot_standby_feedback = on            # реплика сообщает master'у о своих запросах
wal_keep_size = 512MB                # страховка, если слот придётся пересоздать
max_slot_wal_keep_size = 2GB         # защита диска от «зависшего» слота

# Синхронную репликацию НЕ включаем (режим асинхронный):
# synchronous_standby_names = 'ANY 1 (replica1, replica2)'
EOF

sudo chown postgres:postgres $CONFD/replication.conf
sudo chmod 0644 $CONFD/replication.conf

Ключевые параметры:

Параметр

Зачем

listen_addresses = '*'

единственный параметр, который обязательно нужно поменять: по умолчанию localhost, и реплика не сможет подключиться по TCP

wal_level = replica

уровень WAL, при котором возможна потоковая репликация

max_wal_senders

без него не запустятся процессы walsender

max_replication_slots

обязателен для слотов репликации

hot_standby = on

разрешает выполнять read-only запросы на реплике

hot_standby_feedback

реплика сообщает master’у о своих долгих запросах

wal_keep_size

сколько WAL держать дополнительно к слотам

max_slot_wal_keep_size

аварийный предел: слот не сможет занять весь диск

Примечание. В PostgreSQL 18 значения wal_level = replica, max_wal_senders = 10, max_replication_slots = 10 и hot_standby = on являются значениями по умолчанию. В файле они указаны явно — чтобы настройка была самодокументируемой и не зависела от изменения умолчаний в будущих версиях.

2.2. Правила доступа в pg_hba.conf#

sudo tee -a $PGCONF/pg_hba.conf >/dev/null <<EOF

# --- Физическая репликация (streaming) с резервных серверов ---
host    replication     $REPL_USER      $SUBNET                 scram-sha-256
EOF

Тип подключения обязан быть replication. Если указать all, pg_basebackup и walreceiver получат ошибку база данных "replication" не существует.

Строку нужно добавить на master для каждой реплики — либо одной строкой на всю подсеть, как в примере выше.

2.3. Перезапустить кластер#

listen_addresses — параметр уровня postmaster, поэтому требуется именно перезапуск, а не reload:

sudo systemctl restart postgresql@18-main.service

Эквивалентный вариант «в стиле Debian»:

sudo pg_ctlcluster 18 main restart

Проверка состояния:

pg_lsclusters

2.4. Проверить, что параметры применились#

SELECT name, setting
FROM pg_settings
WHERE name IN ('data_directory', 'listen_addresses', 'wal_level', 'max_wal_senders',
               'max_replication_slots', 'hot_standby', 'hot_standby_feedback',
               'wal_keep_size', 'max_slot_wal_keep_size')
ORDER BY name;

Ожидаемо:

data_directory        | /var/lib/postgresql/18/main
hot_standby           | on
hot_standby_feedback  | on
listen_addresses      | *
max_replication_slots | 10
max_slot_wal_keep_size| 2048
max_wal_senders       | 10
wal_keep_size         | 512
wal_level             | replica

Проверить, что pg_hba.conf прочитан без ошибок (столбец error должен быть пустым):

SELECT line_number, type, database, user_name, address, auth_method, error
FROM pg_hba_file_rules
ORDER BY line_number;

2.5. Создать пользователя репликации#

ALTER ROLE postgres WITH PASSWORD 'CHANGE_ME_postgres_password';

CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'CHANGE_ME_strong_password';

Проверка:

SELECT rolname, rolsuper, rolreplication, rolcanlogin
FROM pg_roles
WHERE rolname IN ('postgres', 'replicator');

2.6. Создать слоты репликации#

Для каждой реплики — свой слот. Имя слота должно совпадать с SLOT в настройках соответствующей реплики.

SELECT pg_create_physical_replication_slot('replica1_slot');
SELECT pg_create_physical_replication_slot('replica2_slot');

Проверка (пока active = f, restart_lsn пустой — реплики ещё не подключились):

SELECT slot_name, slot_type, active, restart_lsn
FROM pg_replication_slots
ORDER BY slot_name;

MASTER готов. Дальше каждую реплику настраиваем одинаково, меняя только REPLICA_IP, SLOT и APP_NAME.

Проверка доступности репликационного протокола с узла реплики выполняется в разделе 3 (шаг 3.2) — после того как на реплике создан файл паролей.


3. Настройка РЕПЛИКИ#

Выполняется на каждой реплике.

3.1. Создать .pgpass для аутентификации на master#

Чтобы не хранить пароль открытым текстом в конфигах и не передавать его в командной строке, используем стандартный файл паролей libpq. Его читают и pg_basebackup, и walreceiver.

sudo -u postgres tee $PGPASS >/dev/null <<EOF
# host:port:database:user:password
# Поле database = *: физическая репликация подключается к псевдо-БД "replication",
# поэтому это поле надёжнее не ограничивать. host/port/user заданы точно.
$MASTER_IP:5432:*:$REPL_USER:$REPL_PASS
EOF

sudo chown postgres:postgres $PGPASS
sudo chmod 0600 $PGPASS

sudo ls -l $PGPASS      # ожидаем -rw------- postgres postgres

Формат строки — host:port:database:user:password. Если пароль содержит : или \, их нужно экранировать обратным слешем.

Требования libpq к файлу паролей: владелец — тот пользователь, от которого запущен процесс (postgres), права строго 0600. Если права шире, файл молча игнорируется и подключение падает с ошибкой аутентификации.

$PGPASS — это ~postgres/.pgpass, то есть /var/lib/postgresql/.pgpass (домашний каталог пользователя postgres в Debian). Файл можно положить и в другое место — тогда путь задаётся явно: passfile= в primary_conninfo (шаг 3.7) и PGPASSFILE для pg_basebackup (шаг 3.6). Именно так сделано ниже, чтобы не зависеть от переменной HOME процесса postmaster.

3.2. Проверить доступ к master#

Прежде чем ломать существующий кластер, убеждаемся, что реплика умеет подключиться к master с правами репликации. Пароль нигде не вводится — он берётся из созданного файла:

sudo -u postgres env PGPASSFILE="$PGPASS" \
  psql "host=$MASTER_IP port=5432 user=$REPL_USER dbname=replication replication=true" \
  -tAc "IDENTIFY_SYSTEM"

Ожидаемый ответ — systemid | timeline | xlogpos | dbname, например:

7685292910932688140|1|0/17645F8|

Для физической репликации обязательно replication=true. Если указать replication=database, сервер попытается открыть реальную БД с именем replication и вернёт база данных "replication" не существует.

Если подключение не прошло — дальше идти нет смысла: проверьте правила в pg_hba.conf на master (шаг 2.2), владельца и права $PGPASS.

3.3. Файл настроек в conf.d#

На реплике из параметров репликации реально нужен только hot_standby_feedback = on — остальное уже включено умолчаниями.

sudo tee $CONFD/replication.conf >/dev/null <<'EOF'
# -----------------------------------------------------------------------------
# РЕПЛИКАЦИЯ (physical streaming replication)
# -----------------------------------------------------------------------------

listen_addresses = '*'               # чтобы реплику можно было читать по сети
hot_standby = on                     # разрешить read-only запросы
hot_standby_feedback = on            # не отменять длинные SELECT на реплике
wal_level = replica
EOF

sudo chown postgres:postgres $CONFD/replication.conf
sudo chmod 0644 $CONFD/replication.conf

Если реплика не должна принимать клиентские подключения, listen_addresses можно не менять — оставить localhost.

3.4. Остановить кластер#

sudo systemctl stop postgresql@18-main.service

Проверка:

pg_lsclusters       # статус должен стать down

3.5. Очистить каталог данных#

pg_basebackup требует, чтобы целевой каталог был пустым. Каталог данных штатного кластера удаляем целиком, но конфиги в /etc/postgresql/18/main/ не трогаем — они лежат отдельно и останутся на месте.

sudo rm -rf $PGDATA

Удаляем именно $PGDATA (/var/lib/postgresql/18/main), а не /var/lib/postgresql/18. Родительский каталог нужен, и его права должны остаться стандартными (0755, владелец postgres) — иначе pg_lsclusters перестанет определять владельца кластера.

3.6. Снять базовый бэкап с master#

sudo -u postgres env PGPASSFILE="$PGPASS" \
  $PGBIN/pg_basebackup \
    --host=$MASTER_IP \
    --port=5432 \
    --username=$REPL_USER \
    --pgdata=$PGDATA \
    --format=plain \
    --wal-method=stream \
    --checkpoint=fast \
    --slot=$SLOT \
    --progress --verbose

Разбор ключей:

Ключ

Смысл

--pgdata=$PGDATA

куда положить копию (штатный каталог данных)

--format=plain

обычная копия каталога (не tar)

--wal-method=stream

WAL, накопившийся за время копирования, льётся параллельно

--checkpoint=fast

принудительный немедленный checkpoint (быстрее старт)

--slot=$SLOT

использовать слот репликации, чтобы master не удалил нужный WAL

--progress --verbose

показывать прогресс

Пароль не передаётся ни в командной строке, ни через PGPASSWORD — он берётся из файла, указанного в PGPASSFILE. Это и есть продакшн-вариант: пароля нет ни в аргументах процесса (видны в ps), ни в истории команд.

Ключ -R здесь не используется намеренно: он прописывает primary_conninfo автоматически, но без явного passfile. Подключение настраивается явно на следующем шаге.

3.7. Объявить сервер резервным#

Два обязательных элемента: файл-маркер standby.signal и параметры подключения к master в postgresql.auto.conf.

# 1) маркер: сервер стартует в режиме восстановления (hot standby)
sudo -u postgres touch $PGDATA/standby.signal

# 2) подключение к master + привязка к слоту репликации
sudo -u postgres tee -a $PGDATA/postgresql.auto.conf >/dev/null <<EOF

# --- настройки реплики ---
primary_conninfo = 'host=$MASTER_IP port=5432 user=$REPL_USER passfile=$PGPASS application_name=$APP_NAME'
primary_slot_name = '$SLOT'
EOF

Пароля в primary_conninfo нет — аутентификация идёт через passfile=$PGPASS. Параметр passfile указан явно, а не передан через ~/.pgpass по умолчанию, потому что переменная HOME в окружении процесса postmaster может быть не задана.

Альтернатива — держать пароль прямо в primary_conninfo (password=...). Технически это тоже допустимо: postgresql.auto.conf имеет режим 0600 и читается только пользователем postgres. Но такой пароль попадает в дампы конфигурации, в pg_settings и в вывод диагностических команд, поэтому вариант с .pgpass предпочтительнее.

postgresql.auto.conf — единственный конфиг, который живёт внутри PGDATA: он не затирается при обновлении конфигов в /etc и персональный для каждого узла. Именно здесь pg_basebackup -R хранит primary_conninfo.

application_name задавать полезно: под этим именем реплика видна в pg_stat_replication на master, и это же имя используется в synchronous_standby_names при включении синхронного режима.

3.8. Запустить кластер#

sudo systemctl start postgresql@18-main.service

Проверка:

pg_lsclusters

Ожидаемо — статус online,recovery:

Ver Cluster Port Status          Owner    Data directory              Log file
18  main    5432 online,recovery postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log

3.9. Быстрая локальная проверка#

SELECT pg_is_in_recovery();   -- -> t
SELECT status, sender_host, sender_port, slot_name
FROM pg_stat_wal_receiver;

Ожидаемо:

status      | streaming
sender_host | 192.0.2.10
sender_port | 5432
slot_name   | replica1_slot

Повторите раздел 3 для каждой следующей реплики, задав ей свой SLOT и APP_NAME, и не забудьте создать под неё слот на master (шаг 2.6) и добавить её адрес в pg_hba.conf master (шаг 2.2).


4. Проверка, что всё работает#

4.1. На master: видим все реплики#

SELECT pid, usename, application_name, client_addr, state,
       sync_state, sent_lsn, replay_lsn,
       pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_lag_bytes
FROM pg_stat_replication
ORDER BY application_name;

Ожидаемо — по строке на каждую реплику со state = streaming, sync_state = async и лагом 0.

4.2. На master: слоты активны#

SELECT slot_name, slot_type, active, restart_lsn,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots
ORDER BY slot_name;

Ожидаемо: все слоты active = t, retained_wal = 0 bytes.

4.3. Сквозная проверка репликации данных#

На master:

CREATE TABLE repl_check (id serial PRIMARY KEY,
                         note text,
                         created_at timestamptz DEFAULT now());

INSERT INTO repl_check (note) SELECT 'row-' || g FROM generate_series(1, 5) g;

SELECT pg_switch_wal();

На каждой реплике:

SELECT * FROM repl_check ORDER BY id;

Должны появиться те же 5 строк.

4.4. На реплике: запись запрещена#

INSERT INTO repl_check(note) VALUES ('should-fail');

Ожидаемая ошибка:

ОШИБКА:  в транзакции в режиме "только чтение" нельзя выполнить INSERT

4.5. Проверка, что слот реально удерживает WAL#

Самый показательный тест: останавливаем реплику, пишем данные на master, убеждаемся, что слот удерживает WAL, поднимаем реплику — она догоняет.

# на реплике
sudo systemctl stop postgresql@18-main.service

На master пишем данные, пока реплика выключена:

INSERT INTO repl_check (note)
SELECT 'while-replica-down-' || g FROM generate_series(1, 3) g;

SELECT pg_switch_wal();

На master смотрим состояние слота — он неактивен, но WAL удерживается:

SELECT slot_name, active, restart_lsn,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots
ORDER BY slot_name;

Ожидаемо, например:

replica1_slot | f | 0/504F918 | 16 MB      <- реплика выключена, WAL копится
replica2_slot | t | 0/6000000 | 0 bytes

Поднимаем реплику обратно:

sudo systemctl start postgresql@18-main.service

На реплике проверяем, что она догнала:

SELECT count(*) FROM repl_check;   -- -> 8

Данные, записанные пока реплика была выключена, доехали — слот работает.

4.6. Убрать тестовые данные#

На master:

DROP TABLE IF EXISTS repl_check;

5. Эксплуатация#

5.1. Мониторинг отставания#

На master:

SELECT application_name, client_addr, state, sync_state,
       pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes,
       replay_lag
FROM pg_stat_replication
ORDER BY application_name;

На реплике:

SELECT status, sender_host, slot_name,
       pg_last_wal_replay_lsn() AS replay_lsn,
       now() - pg_last_xact_replay_timestamp() AS replay_delay
FROM pg_stat_wal_receiver;

Признаки проблем:

Симптом

Вероятная причина

pg_stat_replication на master пуст

реплика не подключилась — смотрите /var/log/postgresql/postgresql-18-main.log

retained_wal на master растёт

реплика отстала или слот «осиротел», WAL копится

pg_stat_wal_receiver на реплике пуст

walreceiver не запущен: проверьте primary_conninfo и standby.signal

слот active = f при живой реплике

реплика подключается без слота либо с другим слотом

5.2. Полезные команды управления кластером#

pg_lsclusters                          # список кластеров и их статус
sudo pg_ctlcluster 18 main restart     # перезапуск
sudo pg_ctlcluster 18 main reload      # перечитать конфиги без перезапуска
sudo systemctl status postgresql@18-main.service
sudo tail -f /var/log/postgresql/postgresql-18-main.log

5.3. Добавить новую реплику#

  1. На master создать слот:

    SELECT pg_create_physical_replication_slot('replica3_slot');
    
  2. Добавить адрес новой реплики в pg_hba.conf master, затем:

    SELECT pg_reload_conf();
    
  3. На новой машине выполнить раздел 3, задав SLOT=replica3_slot и свой APP_NAME.

5.4. Пересоздать «сломанную» реплику#

Самое частое — слот инвалидирован (переполнение max_slot_wal_keep_size), и реплика просит уже удалённый WAL.

На master:

SELECT pg_drop_replication_slot('replica1_slot');
SELECT pg_create_physical_replication_slot('replica1_slot');

На реплике: остановить кластер и заново выполнить шаги 3.4–3.8 (снять новый базовый бэкап в очищенный PGDATA).

5.5. Promote реплики (ручной failover)#

На реплике, которую делаем новым master:

sudo pg_ctlcluster 18 main stop
sudo pg_ctlcluster 18 main promote
sudo pg_ctlcluster 18 main start
SELECT pg_is_in_recovery();   -- -> f

После этого остальные реплики нужно перевести на нового master: изменить primary_conninfo в их postgresql.auto.conf, создать на новом master слоты под них и перезапустить реплики.

5.6. Включить синхронную репликацию (опционально)#

На master в $CONFD/replication.conf:

synchronous_standby_names = 'ANY 1 (replica1, replica2)'

Затем:

sudo pg_ctlcluster 18 main reload
SELECT pg_reload_conf();

Теперь COMMIT на master будет ждать подтверждения минимум от одной реплики. Имена в списке должны совпадать с application_name реплик.


6. Типовые ошибки и их решение#

Ошибка

Причина

Решение

подключиться к серверу ... не удалось при pg_basebackup

на master listen_addresses = localhost либо нет правила в pg_hba.conf

шаги 2.1 и 2.2, перезапуск кластера

база данных "replication" не существует

в pg_hba.conf нет правила с типом replication, либо клиент использует replication=database вместо replication=true

добавить host replication <user> <subnet> scram-sha-256

pg_basebackup: error: directory "..." exists but is not empty

каталог данных не очищен

остановить кластер и удалить $PGDATA (шаг 3.5)

replication slot "..." is active for PID ...

слот занят другим процессом

погасить старый walreceiver либо пересоздать слот

реплика не стартует: отсутствует standby.signal

не создан маркер режима восстановления

sudo -u postgres touch $PGDATA/standby.signal

requested WAL segment ... has already been removed

слот отсутствовал или инвалидирован

пересоздать слот и снять новый базовый бэкап

реплика отвергает SELECT-запросы

hot_standby = off

поставить hot_standby = on и перезапустить

pg_lsclusters показывает Owner <unknown>

изменены права /var/lib/postgresql/18

вернуть sudo chown postgres:postgres /var/lib/postgresql/18 && sudo chmod 0755 /var/lib/postgresql/18

изменения в conf.d не применяются

include_dir перекрыт параметром ниже в postgresql.conf

проверить порядок директив include*

password authentication failed for user "replicator", хотя .pgpass создан

у файла неверный владелец или права (libpq молча игнорирует такой файл)

sudo chown postgres:postgres $PGPASS && sudo chmod 0600 $PGPASS

в логе WARNING: password file "..." has group or world access; permissions should be u=rw (0600) or less

права на файл паролей шире 0600

sudo chmod 0600 $PGPASS

passfile задан, но пароль всё равно не находится

не совпало поле database: физическая репликация использует псевдо-БД replication

использовать в .pgpass * в поле database либо указать replication

Проверить, что файл паролей реально работает, можно так (пароль нигде не вводится):

sudo -u postgres env PGPASSFILE="$PGPASS" \
  psql "host=$MASTER_IP port=5432 user=$REPL_USER dbname=replication replication=true" \
  -tAc "IDENTIFY_SYSTEM"

Если подключение прошло — .pgpass настроен верно. Если появилась ошибка аутентификации, проверьте владельца и права файла.

Проверить, что пароля нет в конфиге реплики:

sudo grep -c 'password=' $PGDATA/postgresql.auto.conf    # ожидаем 0

Проверить, что сервер видит актуальные настройки:

SELECT name, setting, pending_restart
FROM pg_settings
WHERE name IN ('listen_addresses', 'wal_level', 'max_wal_senders',
               'max_replication_slots', 'hot_standby');

pending_restart = true означает, что для применения параметра нужен перезапуск.


7. Приложение А. Итоговые конфиги#

/etc/postgresql/18/main/conf.d/replication.conf (master)#

listen_addresses = '*'

wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
hot_standby = on
hot_standby_feedback = on
wal_keep_size = 512MB
max_slot_wal_keep_size = 2GB

/etc/postgresql/18/main/pg_hba.conf (добавленные строки на master)#

# --- Физическая репликация (streaming) с резервных серверов ---
host    replication     replicator      192.0.2.0/24            scram-sha-256

/var/lib/postgresql/18/main/postgresql.auto.conf (только на репликах)#

# --- настройки реплики ---
primary_conninfo = 'host=192.0.2.10 port=5432 user=replicator passfile=/var/lib/postgresql/.pgpass application_name=replica1'
primary_slot_name = 'replica1_slot'

/var/lib/postgresql/.pgpass (только на репликах)#

# host:port:database:user:password
192.0.2.10:5432:*:replicator:CHANGE_ME_strong_password

Владелец — postgres, права строго 0600.

/var/lib/postgresql/18/main/standby.signal (только на репликах)#

Пустой файл-маркер.


8. Приложение Б. Краткая шпаргалка#

# ================= НА ВСЕХ УЗЛАХ: установка =================
sudo apt update
sudo install -d -m 0755 /usr/share/keyrings
sudo wget -q -O /usr/share/keyrings/pgdg.asc https://www.postgresql.org/media/keys/ACCC4CF8.asc
echo "deb [signed-by=/usr/share/keyrings/pgdg.asc] https://apt.postgresql.org/pub/repos/apt $(. /etc/os-release; echo $VERSION_CODENAME)-pgdg main" \
  | sudo tee /etc/apt/sources.list.d/pgdg.list
sudo apt update
sudo apt install -y postgresql-18 postgresql-client-18
pg_lsclusters                       # кластер 18/main создан и online

# ================= MASTER =================
CONFD=/etc/postgresql/18/main/conf.d
PGCONF=/etc/postgresql/18/main

# conf.d/replication.conf: listen_addresses='*' + wal_level/max_wal_senders/
#                          max_replication_slots/hot_standby/wal_keep_size
# pg_hba.conf:             host replication replicator <subnet> scram-sha-256

sudo systemctl restart postgresql@18-main.service

# далее в psql:
#   CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD '...';
#   SELECT pg_create_physical_replication_slot('replica1_slot');
#   SELECT pg_create_physical_replication_slot('replica2_slot');

# ================= РЕПЛИКА (для каждой своя SLOT / APP_NAME) =================
PGDATA=/var/lib/postgresql/18/main
PGPASS=/var/lib/postgresql/.pgpass
SLOT=replica1_slot
APP_NAME=replica1
MASTER_IP=192.0.2.10

sudo systemctl stop postgresql@18-main.service
sudo rm -rf $PGDATA

# файл паролей вместо пароля в командной строке
sudo -u postgres tee $PGPASS >/dev/null <<EOF
$MASTER_IP:5432:*:replicator:$REPL_PASS
EOF
sudo chown postgres:postgres $PGPASS
sudo chmod 0600 $PGPASS

sudo -u postgres env PGPASSFILE="$PGPASS" \
  /usr/lib/postgresql/18/bin/pg_basebackup -h $MASTER_IP -p 5432 -U replicator \
     -D $PGDATA -Fp -Xs --checkpoint=fast -S $SLOT -P

sudo -u postgres touch $PGDATA/standby.signal
sudo -u postgres tee -a $PGDATA/postgresql.auto.conf >/dev/null <<EOF
primary_conninfo = 'host=$MASTER_IP port=5432 user=replicator passfile=$PGPASS application_name=$APP_NAME'
primary_slot_name = '$SLOT'
EOF

sudo systemctl start postgresql@18-main.service
pg_lsclusters                       # ожидаем статус online,recovery

9. Ожидаемый результат#

Проверка

master

реплика

pg_lsclusters

online

online,recovery

SHOW data_directory

/var/lib/postgresql/18/main

/var/lib/postgresql/18/main

SELECT pg_is_in_recovery()

f

t

pg_stat_replication.state (на master)

—

streaming

pg_stat_wal_receiver.status (на реплике)

—

streaming

pg_replication_slots.active

t

—

sync_state

—

async

лаг реплея

—

0 bytes

timeline_id

1

1

systemctl is-enabled postgresql@18-main.service

enabled

enabled

запись на реплике

разрешена

запрещена (read-only transaction)

Если все пункты совпали — репликация настроена корректно: данные с master попадают на реплики, слоты удерживают WAL при отключении реплики, а после перезапуска master реплики переподключаются автоматически.