Перейти к основному содержимому

Уровень 3.0

Предусловия:

  • Изучена лекция 6 «Физическая репликация»

Настройка и анализ потоковой репликации​

Подготовка виртуальной машины Host1​

  1. Подключитесь к виртуальной машине с помощью webssh, указав IP-адрес Host1.

  2. Запустите оболочку от имени пользователя postgres:

    [student@ServerName ~]$ sudo -iu postgres
  3. Остановите экземпляр Pangolin, включите подсчет контрольных сумм и запустите экземпляр Pangolin:

    [postgres@ServerName ~]$ pg_ctl stop
    waiting for server to shut down....... done
    server stopped
    [postgres@ServerName ~]$ pg_checksums --enable
    Checksum operation completed
    Files scanned: 1011
    Blocks scanned: 3486
    Files written: 827
    Blocks written: 3486
    pg_checksums: syncing data directory
    pg_checksums: updating control file
    Checksums enabled in cluster
    [postgres@ServerName ~]$ pg_ctl start -l logfile
    waiting for server to start.... done
    server started

    В рамках этой лабораторной работы подсчет контрольных сумм нужен для использования утилиты синхронизации данных pg_rewind.

  4. В терминале подключитесь в psql к базе данных postgres от имени пользователя postgres:

    [postgres@ServerName ~]$ psql
    psql (15.5)
    Type "help" for help.
  5. Создайте роль replicator:

    postgres=# CREATE ROLE replicator WITH LOGIN REPLICATION PASSWORD 'replicator';
    CREATE ROLE

    Указанная роль будет использоваться для подключения по протоколу репликации, поэтому нужен атрибут REPLICATION.

  6. Создайте базу данных replication_db и подключитесь к ней:

    postgres=# CREATE DATABASE replication_db;
    CREATE DATABASE
    postgres=# \c replication_db
    You are now connected to database "replication_db" as user "postgres".

Подготовка виртуальной машины Host2​

  1. Подключитесь к виртуальной машине посредством webssh, указав IP-адрес Host2.

  2. Запустите оболочку от имени пользователя postgres:

    [student@ServerName ~]$ sudo -iu postgres
  3. В домашнем каталоге пользователя postgres создайте каталог replica и задайте необходимые права для него:

    [postgres@ServerName ~]$ mkdir replica && chmod 0700 replica

    Созданный каталог будет являться каталогом данных для создаваемой реплики, поэтому его владельцем должен быть пользователь postgres с правами 0700 (или 0750).

Настройка потоковой репликации​

  1. В сеансе psql на Host1 проверьте значения параметров wal_level, max_wal_senders и max_replication_slots:

    replication_db=# SELECT name, setting FROM pg_settings WHERE name IN ('wal_level', 'max_wal_senders', 'max_replication_slots');
    name | setting
    -----------------------+---------
    max_replication_slots | 10
    max_wal_senders | 10
    wal_level | replica
    (3 rows)

    Вспомним, что для подключения по протоколу репликации уровень журнала (wal_level) должен быть не менее replica.

    Также должно быть достаточным допустимое количество процессов wal sender (max_wal_senders), и в случае использования слота репликации — значение параметра max_replication_slots.

    Значения по умолчанию указанных параметров позволяют использовать протокол репликации в рамках лабораторной работы.

  2. В сеансе psql на Host1 проверьте настройки в файле pg_hba.conf:

    replication_db=# SELECT line_number, type, database, user_name, address, netmask, auth_method FROM pg_hba_file_rules;
    line_number | type | database | user_name | address | netmask | auth_method
    -------------+-------+---------------+-----------+---------------+-----------------------------------------+---------------
    89 | local | {all} | {all} | | | trust
    91 | host | {all} | {all} | 172.29.53.165 | 255.255.255.255 | trust
    92 | host | {all} | {all} | 0.0.0.0 | 0.0.0.0 | scram-sha-256
    93 | host | {all} | {all} | 127.0.0.1 | 255.255.255.255 | trust
    95 | host | {all} | {all} | ::1 | ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff | trust
    98 | local | {replication} | {all} | | | trust
    99 | host | {replication} | {all} | 127.0.0.1 | 255.255.255.255 | trust
    100 | host | {replication} | {all} | ::1 | ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff | trust
    (8 rows)

    Текущие настройки pg_hba.conf не позволяют подключаться по протоколу репликации через сетевой интерфейс. Исправим это.

  3. В сеансе psql на Host1 добавьте в файл pg_hba.conf разрешение для доступа через сетевой интерфейс по протоколу репликации:

    replication_db=# \! sed -i '99i\host replication all 0.0.0.0/0 scram-sha-256' /pgdata/\{major-minor\}.0/data/pg_hba.conf

    В этом случае перед 99-й строкой файла pg_hba.conf была добавлена строка с разрешением доступа через сетевой интерфейс с аутентификацией по паролю.

  4. В сеансе psql на Host1 проверьте корректность внесенных в pg_hba.conf изменений и перечитайте конфигурацию:

    replication_db=# SELECT line_number, type, database, user_name, address, netmask, auth_method FROM pg_hba_file_rules;
    line_number | type | database | user_name | address | netmask | auth_method
    -------------+-------+---------------+-----------+---------------+-----------------------------------------+---------------
    89 | local | {all} | {all} | | | trust
    91 | host | {all} | {all} | 172.29.53.165 | 255.255.255.255 | trust
    92 | host | {all} | {all} | 0.0.0.0 | 0.0.0.0 | scram-sha-256
    93 | host | {all} | {all} | 127.0.0.1 | 255.255.255.255 | trust
    95 | host | {all} | {all} | ::1 | ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff | trust
    98 | local | {replication} | {all} | | | trust
    99 | host | {replication} | {all} | 0.0.0.0 | 0.0.0.0 | scram-sha-256
    100 | host | {replication} | {all} | 127.0.0.1 | 255.255.255.255 | trust
    101 | host | {replication} | {all} | ::1 | ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff | trust
    (9 rows)
    replication_db=# SELECT pg_reload_conf();
    pg_reload_conf
    ----------------
    t
    (1 row)

    Необходимое разрешение успешно добавлено. Для применения новых настроек файлы конфигурации были перечитаны функцией pg_reload_conf.

  5. В сеансе psql на Host1 создайте слот репликации:

    replication_db=# SELECT pg_create_physical_replication_slot('replica_slot');
    pg_create_physical_replication_slot
    -------------------------------------
    (replica_slot,)
    (1 row)
  6. В сеансе Host2 создайте базовую резервную копию, указав ip-адрес хоста Host1 и введя пароль для роли replicator:

    [postgres@ServerName ~]$ pg_basebackup -h 172.29.53.216 -U replicator -D ~/replica -R -S replica_slot
    Password:

    Вспомним, что утилита pg_basebackup позволяет сделать базовую резервную копию и подготовить ее для использования в качестве реплики.

    Для этого с помощью ключа -R дается указание о необходимости создания файла standby.signal и задания значений для параметров primary_conninfo и primary_slot_name в файле postgresql.auto.conf

    Каталог для размещения копии указан помощью ключа -D.

    С помощью ключа -S указано имя слота репликации на мастере.

  7. В сеансе Host2 проверьте содержимое каталога с созданной копией кластера:

    [postgres@ServerName ~]$ ls ~/replica
    backup_label pg_commit_ts pg_integrity pg_perf_insights pg_replslot pg_stat_tmp PG_VERSION postgresql.conf
    backup_manifest pg_dynshmem pg_logical pg_pp_cache pg_serial pg_subtrans pg_wal PRODUCT_VERSION
    base pg_hba.conf pg_multixact pg_prep_stats pg_snapshots pg_tblspc pg_xact standby.signal
    global pg_ident.conf pg_notify pg_quota.conf pg_stat pg_twophase postgresql.auto.conf tracing

    Помимо файлов standby.signal и postgresql.auto.conf утилита также создала файл backup_label, где указан LSN, начиная с которого требуется наличие журнальных записей для восстановления.

  8. В сеансе Host2 посмотрите содержимое файла postgresql.auto.conf:

    [postgres@ServerName ~]$ cat ~/replica/postgresql.auto.conf
    # Do not edit this file manually!
    # It will be overwritten by the ALTER SYSTEM command.
    primary_conninfo = 'user=replicator password=replicator channel_binding=prefer host={replica-IP} port=5432 client_encoding=UTF8 sslmode=prefer ssllegacyprovider=1 sslcompression=0 sslcert=''/pg_ssl/client.crt'' sslkey=''/pg_ssl/client.key'' sslrootcert=''/pg_ssl/root.crt'' sslsni=1 ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres target_session_attrs=any'
    primary_slot_name = 'replica_slot'

    В файле появилась строка подключения к мастеру (primary_conninfo) и имя слота (primary_slot_name), через который будут передаваться записи WAL.

  9. В сеансе Host2 посмотрите содержимое файла backup_label:

    [postgres@ServerName ~]$ cat ~/replica/backup_label
    START WAL LOCATION: 0/2000028 (file 000000010000000000000002)
    CHECKPOINT LOCATION: 0/2000070
    BACKUP METHOD: streamed
    BACKUP FROM: primary
    START TIME: 2025-11-06 19:32:00 UTC
    LABEL: pg_basebackup base backup
    START TIMELINE: 1

    Для восстановления из созданной копии требуется записи WAL начиная с LSN = 0/2000028 (START WAL LOCATION).

    Сравним это значение с тем, что хранит слот репликации.

  10. В сеансе psql на Host1 проверьте состояние созданного слота репликации:

    replication_db=# SELECT * FROM pg_replication_slots WHERE slot_name='replica_slot'\gx
    -[ RECORD 1 ]-------+-------------
    slot_name | replica_slot
    plugin |
    slot_type | physical
    datoid |
    database |
    temporary | f
    active | f
    active_pid |
    xmin |
    catalog_xmin |
    restart_lsn | 0/2000000
    confirmed_flush_lsn |
    wal_status | reserved
    safe_wal_size |
    two_phase | f

    Слот репликации не даст контрольной точке удалить необходимые реплике сегменты WAL.

    В параметре restart_lsn слота указана начальная позиция сегмента WAL, в котором есть записи, еще не переданные через слот.

  11. В сеансе Host2 проверьте содержимое файла pg_hba.conf скопированного кластера:

    [postgres@ServerName ~]$ tail -n 17 replica/pg_hba.conf

    # TYPE DATABASE USER ADDRESS METHOD

    # "local" is for Unix domain socket connections only
    local all all trust
    # IPv4 local connections:
    host all all 172.29.53.216/32 trust
    host all all 0.0.0.0/0 scram-sha-256
    host all all 127.0.0.1/32 trust
    # IPv6 local connections:
    host all all ::1/128 trust
    # Allow replication connections from localhost, by a user with the
    # replication privilege.
    local replication all trust
    host replication all 0.0.0.0/0 scram-sha-256
    host replication all 127.0.0.1/32 trust
    host replication all ::1/128 trust

    Файл pg_hba.conf был скопирован без изменений.

    Если реплика будет использоваться для выполнения запросов, то отредактируйте указанный файл.

    В рамках этой лабораторной работы подключение к реплике может быть выполнено через localhost (127.0.0.1/32), разрешение для которого в файле указано.

Проверка репликации​

  1. В сеансе Host2 остановите запущенный экземпляр Pangolin:

    [postgres@ServerName ~]$ pg_ctl stop
    waiting for server to shut down.... done
    server stopped
  2. В сеансе Host2 запустите экземпляр Pangolin, указав в параметре -D каталога данных скопированного кластера баз данных:

    [postgres@ServerName ~]$ pg_ctl start -D /home/postgres/replica -l logfile
    waiting for server to start.... done
    server started

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

  3. В сеансе Host2 подключитесь в psql к базе данных replication_db:

    [postgres@ServerName ~]$ psql -d replication_db -h localhost
    psql (15.5)
    Type "help" for help.
  4. В сеансе psql на Host2 проверьте состояние восстановления кластера:

    replication_db=# SELECT pg_is_in_recovery();
    pg_is_in_recovery
    -------------------
    t
    (1 row)

    Функция pg_is_in_recovery позволяет узнать, находится ли экземпляр в состоянии восстановления, то есть является ли он ведомым или репликой.

    Здесь, как и ожидалось, запущенный экземпляр находится в состоянии восстановления получаемых записей WAL.

    Проверим работу репликации.

  5. В сеансе psql на Host1 создайте простую таблицу и вставьте в нее одну строку:

    replication_db=# CREATE TABLE some_table(value text);
    CREATE TABLE
    replication_db=# INSERT INTO some_table VALUES('Строка, вставленная на мастере');
    INSERT 0 1
  6. В сеансе psql на Host2 проверьте содержимое таблицы some_table:

    replication_db=# SELECT * FROM some_table;
    value
    --------------------------------
    Строка, вставленная на мастере
    (1 row)

    Созданная таблица и вставленная в нее строка успешно «добрались» с мастера до реплики.

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

  7. В сеансе psql на Host2 проверьте значение параметра hot_standby:

    replication_db=# SHOW hot_standby;
    hot_standby
    -------------
    on
    (1 row)

    Действительно, параметр hot_standby по умолчанию включен.

  8. В сеансе psql на Host2 попробуйте вставить строку в таблицу some_table:

    replication_db=# INSERT INTO some_table VALUES('Строка, вставленная на реплике');
    ERROR: cannot execute INSERT in a read-only transaction

    В то время как изменения данных на реплике не допускаются.

  9. В сеансе psql на Host1 проверьте состояние репликации:

    replication_db=# SELECT * FROM pg_stat_replication\gx
    -[ RECORD 1 ]----+------------------------------
    pid | 19513
    usesysid | 16389
    usename | replicator
    application_name | host2
    client_addr | 172.29.53.58
    client_hostname |
    client_port | 51636
    backend_start | 2025-11-06 20:09:42.646771+00
    backend_xmin |
    state | streaming
    sent_lsn | 0/3024E30
    write_lsn | 0/3024E30
    flush_lsn | 0/3024E30
    replay_lsn | 0/3024E30
    write_lag |
    flush_lag |
    replay_lag |
    sync_priority | 0
    sync_state | async
    reply_time | 2025-11-06 21:06:27.534848+00

    Из представления pg_stat_replication на мастере можно получить информацию о текущем состоянии репликации.

    В частности, в нем для каждой подключенной реплики есть ее адрес (client_addr), состояние (state) и режим фиксации транзакций (sync_state).

    Отдельно стоит отметить группу параметров для мониторинга отставания реплики от мастера в различных точках трансляции записей WAL. Параметры с суффиксом _lsn содержат LSN последних записей, прошедших через указанные точки, а параметры с суффиксом _lag содержат временные задержки на различных этапах с момента сохранения записи в сегменте WAL на мастере.

Настройка синхронного режима​

  1. В сеансе psql на Host2 задайте значение параметра cluster_name и выйдите из psql:

    replication_db=# ALTER SYSTEM SET cluster_name='host2';
    ALTER SYSTEM
    replication_db=# \q

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

    Для хранения имени реплики используется параметр cluster_name.

  2. В сеансе Host2 остановите экземпляр Pangolin с репликой:

    [postgres@ServerName ~]$ pg_ctl stop -D /home/postgres/replica
    waiting for server to shut down.... done
    server stopped
  3. В сеансе psql на Host1 проверьте текущие значения параметров режима синхронизации:

    replication_db=# SELECT name, setting FROM pg_settings WHERE name IN('synchronous_commit', 'synchronous_standby_names');
    name | setting
    ---------------------------+---------
    synchronous_commit | on
    synchronous_standby_names |
    (2 rows)

    Вспомним, что при значении synchronous_commit=on на мастере фиксация транзакции завершается только после попадания соответствующей журнальной записи на накопитель реплики.

    Однако, этого недостаточно для включения синхронного режима.

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

  4. В сеансе psql на Host1 укажите имя реплики в параметре synchronous_standby_names:

    replication_db=# ALTER SYSTEM SET synchronous_standby_names = host2;
    ALTER SYSTEM
    replication_db=# SELECT pg_reload_conf();
    pg_reload_conf
    ----------------
    t
    (1 row)

    Теперь синхронный режим должен работать. Проверим это.

  5. В сеансе psql на Host1 начните транзакцию, вставьте строку и зафиксируйте транзакцию:

    replication_db=# BEGIN;
    BEGIN
    replication_db=*# INSERT INTO some_table VALUES('Строка, вставленная в синхронном режиме');
    INSERT 0 1
    replication_db=*# COMMIT;

    Команда COMMIT на мастере ожидает подтверждения от реплики, экземпляр которой ранее был остановлен.

  6. В сеансе Host2 запустите экземпляр Pangolin с репликой:

    [postgres@ServerName ~]$ pg_ctl start -D /home/postgres/replica -l logfile
    waiting for server to start.... done
    server started
  7. В сеансе psql на Host1 убедитесь, что транзакция завершена:

    COMMIT

    Фиксация транзакции успешно завершена после того как реплика подтвердила, что записала соответствующую журнальную запись на накопитель.

Конфликты при выполнении запросов​

  1. В сеансе Host2 подключитесь в psql к базе данных replication_db:

    [postgres@ServerName ~]$ psql -d replication_db -h localhost
    psql (15.5)
    Type "help" for help.
  2. В сеансе psql на Host2 проверьте значение параметра max_standby_streaming_delay:

    replication_db=# SHOW max_standby_streaming_delay;
    max_standby_streaming_delay
    -----------------------------
    30s
    (1 row)

    Вспомним, что параметр max_standby_streaming_delay отвечает за временную задержку применения записей WAL, конфликтующих с выполняемыми на реплике запросами.

  3. В сеансе psql на Host2 начните транзакцию с уровнем изоляции REPEATABLE READ и выполните запрос к таблице some_table:

    replication_db=# BEGIN ISOLATION LEVEL REPEATABLE READ;
    BEGIN
    replication_db=*# SELECT * FROM some_table;
    value
    ------------------------------------------
    Строка, вставленная на мастере
    Строка, вставленная в синхронном режиме
    (2 rows)

    Транзакция была начата с уровнем изоляции REPEATABLE READ, то есть при выполнении первого оператора в ней (SELECT) был создан снимок данных, который будет использован на протяжении всей транзакции.

  4. В сеансе psql на Host1 выполните обновление в таблице some_table и очистку устаревших версий строк:

    replication_db=# UPDATE some_table SET value = value || ' - обновление';
    UPDATE 2
    replication_db=# VACUUM;
    VACUUM
  5. В сеансе psql на Host2 не позднее чем через 30 секунд после очистки повторите выполнение запроса :

    replication_db=*# SELECT * FROM some_table;
    value
    ------------------------------------------
    Строка, вставленная на мастере
    Строка, вставленная в синхронном режиме
    (2 rows)

    Запрос на реплике отработал без изменений с ранее построенным снимком данных.

    При этом, несмотря на то что версии строк, участвующие в построенном снимке данных, на мастере были очищены, применение результатов очистки на реплике было задержано на 30 секунд.

  6. В сеансе psql на Host2 по прошествии 30 секунд после очистки повторите выполнение запроса:

    replication_db=*# SELECT * FROM some_table;
    FATAL: terminating connection due to conflict with recovery
    DETAIL: User query might have needed to see row versions that must be removed.
    HINT: In a moment you should be able to reconnect to the database and repeat your command.
    server closed the connection unexpectedly
    This probably means the server terminated abnormally
    before or while processing the request.
    The connection to the server was lost. Attempting reset: Succeeded.

    В то время как по прошествии 30 секунд результаты очистки были применены на реплике, и устаревшие для мастера версии строк были удалены и на реплике, хотя они еще были ей нужны.

    Это можно исправить путем включения обратной связи реплики с мастером. Тогда реплика будет сообщать мастеру номер самой ранней активной транзакции (xmin), используемой на реплике, и мастер будет его учитывать при определении горизонта очистки.

  7. В сеансе psql на Host2 включите обратную связь:

    replication_db=# ALTER SYSTEM SET hot_standby_feedback = on;
    ALTER SYSTEM
    replication_db=# SELECT pg_reload_conf();
    pg_reload_conf
    ----------------
    t
    (1 row)
  8. В сеансе psql на Host2 посмотрите значение параметра wal_receiver_status_interval:

    replication_db=# SHOW wal_receiver_status_interval;
    wal_receiver_status_interval
    ------------------------------
    10s
    (1 row)

    Параметр wal_receiver_status_interval указывает, как часто реплика должна сообщать мастеру о своем состоянии.

    Повторим тот же эксперимент, но с включенной обратной связью.

  9. В сеансе psql на Host2 начните транзакцию с уровнем изоляции REPEATABLE READ и выполните запрос к таблице some_table:

    replication_db=# BEGIN ISOLATION LEVEL REPEATABLE READ;
    BEGIN
    replication_db=*# SELECT * FROM some_table;
    value
    -------------------------------------------------------
    Строка, вставленная на мастере - обновление
    Строка, вставленная в синхронном режиме - обновление
    (2 rows)
  10. В сеансе psql на Host1 выполните обновление в таблице some_table и очистку устаревших версий строк:

    replication_db=# UPDATE some_table SET value = value || ' - еще обновление';
    UPDATE 2
    replication_db=# VACUUM VERBOSE some_table;
    INFO: vacuuming "replication_db.public.some_table"
    INFO: finished vacuuming "replication_db.public.some_table": index scans: 0
    pages: 0 removed, 1 remain, 1 scanned (100.00% of total)
    tuples: 0 removed, 4 remain, 2 are dead but not yet removable, oldest xmin: 800
    removable cutoff: 800, which was 1 XIDs old when operation ended
    frozen: 0 pages from table (0.00% of total) had 0 tuples frozen
    index scan not needed: 0 pages from table (0.00% of total) had 0 dead item identifiers removed
    avg read rate: 0.000 MB/s, avg write rate: 386.757 MB/s
    buffer usage: 10 hits, 0 misses, 5 dirtied
    WAL usage: 5 records, 5 full page images, 27247 bytes
    system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
    INFO: vacuuming "replication_db.pg_toast.pg_toast_16391"
    INFO: finished vacuuming "replication_db.pg_toast.pg_toast_16391": index scans: 0
    pages: 0 removed, 0 remain, 0 scanned (100.00% of total)
    tuples: 0 removed, 0 remain, 0 are dead but not yet removable, oldest xmin: 800
    removable cutoff: 800, which was 1 XIDs old when operation ended
    frozen: 0 pages from table (100.00% of total) had 0 tuples frozen
    index scan not needed: 0 pages from table (100.00% of total) had 0 dead item identifiers removed
    avg read rate: 0.000 MB/s, avg write rate: 0.000 MB/s
    buffer usage: 3 hits, 0 misses, 0 dirtied
    WAL usage: 0 records, 0 full page images, 0 bytes
    system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
    VACUUM

    Очистка не смогла удалить устаревшие для мастера версии строк. Об этом говорит запись:

    tuples: 0 removed, 4 remain, 2 are dead but not yet removable, oldest xmin: 800

    Две строки мертвые, но еще не удалены (2 are dead but not yet removable).

    Также указан номер самой ранней активной транзакции (oldest xmin: 800), удерживающей горизонт очистки.

  11. В сеансе psql на Host1 проверьте состояние слота репликации:

    replication_db=# SELECT slot_name, active, xmin FROM pg_replication_slots;
    slot_name | active | xmin
    --------------+--------+------
    replica_slot | t | 800
    (1 row)

    Тот же xmin хранит слот репликации, то есть именно он удерживает горизонт очистки.

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

  12. В сеансе psql на Host2 по прошествии 30 секунд после очистки повторите выполнение запроса:

    replication_db=*# SELECT * FROM some_table;
    value
    -------------------------------------------------------
    Строка, вставленная на мастере - обновление
    Строка, вставленная в синхронном режиме - обновление
    (2 rows)

    Запрос успешно выполнен. Нужные реплике устаревшие версии строк удалены не были.

  13. В сеансе psql на Host2 зафиксируйте транзакцию:

    replication_db=*# COMMIT;
    COMMIT

    После завершения транзакции на реплике ее xmin продвинулся вперед и теперь очистка может быть выполнена.

    Проверим это.

  14. В сеансе psql на Host1 повторите очистку устаревших версий строк:

    replication_db=# VACUUM VERBOSE some_table;
    INFO: vacuuming "replication_db.public.some_table"
    INFO: finished vacuuming "replication_db.public.some_table": index scans: 0
    pages: 0 removed, 1 remain, 1 scanned (100.00% of total)
    tuples: 2 removed, 2 remain, 0 are dead but not yet removable, oldest xmin: 801
    removable cutoff: 801, which was 0 XIDs old when operation ended
    new relfrozenxid: 800, which is 1 XIDs ahead of previous value
    frozen: 0 pages from table (0.00% of total) had 0 tuples frozen
    index scan not needed: 0 pages from table (0.00% of total) had 0 dead item identifiers removed
    avg read rate: 0.000 MB/s, avg write rate: 504.032 MB/s
    buffer usage: 9 hits, 0 misses, 6 dirtied
    WAL usage: 6 records, 6 full page images, 35292 bytes
    system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
    INFO: vacuuming "replication_db.pg_toast.pg_toast_16391"
    INFO: finished vacuuming "replication_db.pg_toast.pg_toast_16391": index scans: 0
    pages: 0 removed, 0 remain, 0 scanned (100.00% of total)
    tuples: 0 removed, 0 remain, 0 are dead but not yet removable, oldest xmin: 801
    removable cutoff: 801, which was 0 XIDs old when operation ended
    new relfrozenxid: 801, which is 1 XIDs ahead of previous value
    frozen: 0 pages from table (100.00% of total) had 0 tuples frozen
    index scan not needed: 0 pages from table (100.00% of total) had 0 dead item identifiers removed
    avg read rate: 0.000 MB/s, avg write rate: 0.000 MB/s
    buffer usage: 2 hits, 0 misses, 0 dirtied
    WAL usage: 1 records, 0 full page images, 266 bytes
    system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
    VACUUM

    Действительно, две версии строки успешно удалены, а xmin теперь равен 801:

    tuples: 2 removed, 2 remain, 0 are dead but not yet removable, oldest xmin: 801

Переключение на реплику​

  1. Выйдите из сеанса psql на Host1:

    replication_db=# \q
  2. В сеансе Host1 узнайте pid процесса postgres и завершите его принудительно:

    [postgres@ServerName ~]$ head -n 1 /pgdata/\{major-minor\}.0/data/postmaster.pid
    15954
    [postgres@ServerName ~]$ kill -9 15954

    Таким образом был смоделирован сбой на мастере. Контрольная точка при завершении работы выполнена не была, часть сгенерированных записей WAL до реплики могла не дойти.

    В случае сбоя для минимизации времени простоя можно выполнить переключение клиентов на реплику.

    Для этого необходимо повысить ее статус до мастера.

  3. В сеансе psql на Host2 повысьте статус реплики до мастера:

    replication_db=# SELECT pg_promote();
    pg_promote
    ------------
    t
    (1 row)
    replication_db=# SELECT pg_is_in_recovery();
    pg_is_in_recovery
    -------------------
    f
    (1 row)

    Повышение статуса выполнено с использованием функции pg_promote.

    Функция pg_is_in_recovery показывает, что бывшая реплика теперь не находится в режиме восстановления, то есть является мастером.

    Теперь восстановим бывший мастер и подключим его к новому мастеру в качестве реплики.

    Для синхронизации данных используем утилиту pg_rewind.

  4. В сеансе psql на Host2 создайте слот репликации:

    replication_db=# SELECT pg_create_physical_replication_slot('replica_slot');
    pg_create_physical_replication_slot
    -------------------------------------
    (replica_slot,)
    (1 row)
  5. В сеансе psql на Host2 убедитесь, что включено сохранение полных образов страниц:

    replication_db=# SHOW full_page_writes;
    full_page_writes
    ------------------
    on
    (1 row)

    Для работы утилиты pg_rewind требуется включение записи полных образов страниц на источнике, которым В этом случае является новый мастер.

    Помимо этого, целевой экземпляр должен быть либо инициализирован с подсчетом контрольных сумм, либо на нем должно быть установлено значение параметра wal_log_hits=on, при котором полные образы страниц сохраняются в WAL даже при незначительных изменениях в них, а именно изменениях информационных битов в версиях строк.

    Ранее мы включили подсчет контрольных сумм страниц на целевом экземпляре.

  6. В сеансе psql на Host2 создайте пароль для роли postgres:

    replication_db=# ALTER ROLE postgres PASSWORD 'postgres';
    ALTER ROLE

    Утилита pg_rewind должна подключаться экземпляру-источнику от имени суперпользователя, поэтому для удаленного подключения потребовалось задать пароль для роли postgres.

  7. В сеансе Host1 выполните синхронизацию с новым мастером с использованием утилиты pg_rewind, указав в параметре --source-server IP-адрес хоста Host2:

    [postgres@ServerName ~]$ pg_rewind -D /pgdata/\{major-minor\}.0/data --source-server 'host={IP-Address} user=postgres password=postgres' -R -P
    pg_rewind: connected to server
    pg_rewind: servers diverged at WAL location 0/30D9E90 on timeline 1
    pg_rewind: rewinding from last common checkpoint at 0/30D9DB0 on timeline 1
    pg_rewind: reading source file list
    pg_rewind: reading target file list
    pg_rewind: reading WAL in target
    pg_rewind: need to copy 53 MB (total source directory size is 85 MB)
    54441/54441 kB (100%) copied
    pg_rewind: creating backup label and updating control file
    pg_rewind: syncing target data directory
    pg_rewind: Done!

    С использованием ключа --source-server указана строка подключения к экземпляру-источнику (новому мастеру), а с помощью ключа -D указан каталог кластера баз данных целевого экземпляра (новой реплики).

    С использованием ключа -R дано указание, что необходимо подготовить целевой кластер для использования в качестве реплики (по аналогии с pg_basebackup -R).

    При этом файл postgresql.auto.conf копируется с нового мастера в исходном виде и затем в его конец добавляется параметр primary_conninfo. Поэтому неперекрытые значения параметров сохраняются для новой реплики, включая значение primary_slot_name.

    Ключ -P использован для вывода сообщений о прогрессе выполнения синхронизации.

    В результате pg_rewind нашел место расхождения журнальных записей двух экземпляров, определил общую для них ближайшую контрольную точку и заменил страницы данных на новой реплике измененными на новом мастере с момента найденной контрольной точки.

  8. В сеансе Host1 посмотрите содержимое каталога данных:

    [postgres@ServerName ~]$ ls /pgdata/\{major-minor\}.0/data/
    backup_label pg_commit_ts pg_integrity pg_perf_insights pg_replslot pg_stat_tmp PG_VERSION postgresql.conf
    backup_label.old pg_dynshmem pg_logical pg_pp_cache pg_serial pg_subtrans pg_wal PRODUCT_VERSION
    base pg_hba.conf pg_multixact pg_prep_stats pg_snapshots pg_tblspc pg_xact standby.signal
    global pg_ident.conf pg_notify pg_quota.conf pg_stat pg_twophase postgresql.auto.conf tracing

    По аналогии с pg_basebackup -R утилита pg_rewind создала файлы standby.signal, postgresql.auto.conf и backup_label.

  9. В сеансе Host1 запустите подготовленный экземпляр Pangolin:

    [postgres@ServerName ~]$ pg_ctl start -l logfile
    waiting for server to start.... done
    server started
  10. В сеансе Host1 запустите psql и проверьте состояние восстановления данных в экземпляре:

    [postgres@ServerName ~]$ psql -d replication_db
    psql (15.5)
    Type "help" for help.
    replication_db=# SELECT pg_is_in_recovery();
    pg_is_in_recovery
    -------------------
    t
    (1 row)

    Теперь бывший мастер находится в состоянии восстановления, то есть является новой репликой.

  11. В сеансе psql на Host2 вставьте новую строку в таблицу some_table:

    replication_db=# INSERT INTO some_table VALUES('Строка, вставленная на новом мастере');
    INSERT 0 1
  12. В сеансе psql на Host1 сделайте запрос к таблице some_table:

    replication_db=# SELECT * FROM some_table;
    value
    ------------------------------------------------------------------------
    Строка, вставленная на новом мастере
    Строка, вставленная на мастере - обновление - еще обновление
    Строка, вставленная в синхронном режиме - обновление - еще обновление
    (3 rows)

    Вставленная строка успешно «добралась» с нового мастера на новую реплику.

    Экземпляры поменялись ролями.

Завершение​

Завершение на виртуальной машине Host1​

  1. В сеансе psql повысьте статус экземпляра до мастера:

    replication_db=# SELECT pg_promote();
    pg_promote
    ------------
    t
    (1 row)
  2. В сеансе psql удалите слот репликации:

    replication_db=# SELECT pg_drop_replication_slot('replica_slot');
    pg_drop_replication_slot
    --------------------------

    (1 row)
  3. В сеансе psql подключитесь к базе данных postgres и удалите базу данных replication_db:

    replication_db=# \c postgres
    You are now connected to database "postgres" as user "postgres".
    postgres=# DROP DATABASE replication_db;
    DROP DATABASE
  4. В сеансе psql удалите роль replicator:

    postgres=# DROP ROLE replicator;
    DROP ROLE
  5. В сеансе psql сбросьте значения всех конфигурационных параметров и выйдите из psql:

    postgres=# ALTER SYSTEM RESET ALL;
    ALTER SYSTEM
    postgres=# \q
  6. Перезапустите экземпляр Pangolin:

[postgres@ServerName ~]$ pg_ctl restart -l logfile
waiting for server to shut down.... done
server stopped
waiting for server to start.... done
server started
  1. Выйдите из оболочки, запущенной от имени пользователя postgres:

    [postgres@ServerName ~]$ exit
    logout

Завершение на виртуальной машине Host2​

  1. В сеансе psql выйдите из psql:

    replication_db=# \q
  2. Остановите экземпляр Pangolin с каталогом данных replica:

    [postgres@ServerName ~]$ pg_ctl stop -D /home/postgres/replica
    waiting for server to shut down.... done
    server stopped
  3. Удалите каталог данных replica:

    [postgres@ServerName ~]$ rm -rf ~/replica
  4. Запустите исходный экземпляр Pangolin:

    [postgres@ServerName ~]$ pg_ctl start -l logfile
    waiting for server to start.... done
    server started
  5. Выйдите из оболочки, запущенной от имени пользователя postgres:

    [postgres@ServerName ~]$ exit
    logout

Самопроверка​

Вопрос 1

В сеансе psql выполнена следующая последовательность команд:

postgres=# SHOW hot_standby;
hot_standby
-------------
on
(1 row)

postgres=# SHOW hot_standby_feedback;
hot_standby_feedback
----------------------
off
(1 row)

postgres=# SELECT pg_is_in_recovery();
pg_is_in_recovery
-------------------
t
(1 row)

Какие из перечисленных SQL-команд могут быть выполнены в этом сеансе psql? Выберите все правильные варианты

Вопрос 2

Что из перечисленного дополнительно выполняет утилита pg_basebackup в случае использования ключа -R при создании базовой резервной копии? Выберите все правильные варианты

Вопрос 3

Настроена потоковая репликация между мастером и двумя репликами. На одной из реплик произошел сбой, в результате которого экземпляр Pangolin был остановлен.

После этого в сеансе psql на мастере выполнена следующая последовательность команд:

postgres=# BEGIN ISOLATION LEVEL REPEATABLE READ;
BEGIN

postgres=# SELECT name, setting FROM pg_settings WHERE name IN('synchronous_commit', 'synchronous_standby_names');
name            | setting
---------------------------+---------
synchronous_commit        | on
synchronous_standby_names |
(2 rows)

COMMIT;

Что произойдет в результате вызова последней команды?

Вопрос 4

Между мастером и репликой настроена потоковая репликация с использованием слота репликации с именем replica_slot.

В сеансе psql на мастере выполнена следующая команда:

some_db=# SELECT slot_name, active, xmin, restart_lsn, wal_status FROM pg_replication_slots;
slot_name   | active | xmin | restart_lsn | wal_status
--------------+--------+------+-------------+------------
replica_slot | t      |  820 | 0/9000198   | reserved
(1 row)

Может ли мастер очистить устаревшие для него версии строк, необходимые для выполнения запросов на реплике?