# Физическая потоковая репликация в синхронном режиме

    α=> CREATE DATABASE replica_physical;

    CREATE DATABASE

Создаем и запускаем реплику точно так же, как в демонстрации.

Слот репликации:

    α=> SELECT pg_create_physical_replication_slot('replica');

     pg_create_physical_replication_slot
    -------------------------------------
     (replica,)
    (1 row)

Резервная копия:

    postgres$ rm -rf /var/lib/postgresql/10/beta/*

    postgres$ pg_basebackup -U student --pgdata=/var/lib/postgresql/10/beta -R --slot=replica

Меняем порт и устанавливаем режим горячего резерва:

    postgres$ echo 'port = 5433' >> /var/lib/postgresql/10/beta/postgresql.auto.conf

    postgres$ echo 'hot_standby = on' >> /var/lib/postgresql/10/beta/postgresql.auto.conf

В файл recovery.conf добавляем указание application_name:

    postgres$ sed -i 's/^\(primary_conninfo = '\''\)/\1application_name=replica /' /var/lib/postgresql/10/beta/recovery.conf

    postgres$ cat /var/lib/postgresql/10/beta/recovery.conf

    standby_mode = 'on'
    primary_conninfo = 'application_name=replica user=student passfile=''/var/lib/postgresql/.pgpass'' host=''/var/run/postgresql'' port=5432 sslmode=prefer sslcompression=1 krbsrvname=postgres target_session_attrs=any'
    primary_slot_name = 'replica'

Запускаем:

    student$ sudo pg_ctlcluster 10 beta start

Теперь включаем на мастере режим синхронной репликации. Сразу переключимся на суперпользовательскую роль.

    student$ psql -d replica_physical -U postgres

    α=> ALTER SYSTEM SET synchronous_commit = on;

    ALTER SYSTEM

    α=> ALTER SYSTEM SET synchronous_standby_names = 'replica';

    ALTER SYSTEM

    student$ sudo pg_ctlcluster 10 alpha reload

# Проверка физической репликации

    α=> SELECT * FROM pg_stat_replication \gx

    -[ RECORD 1 ]----+------------------------------
    pid              | 7519
    usesysid         | 16384
    usename          | student
    application_name | replica
    client_addr      |
    client_hostname  |
    client_port      | -1
    backend_start    | 2018-06-13 20:00:49.943947+03
    backend_xmin     |
    state            | streaming
    sent_lsn         | 0/1A00100C
    write_lsn        | 0/1A00100C
    flush_lsn        | 0/1A00100C
    replay_lsn       | 0/1A00100C
    write_lag        | 00:00:00.077777
    flush_lag        | 00:00:00.078191
    replay_lag       | 00:00:00.078232
    sync_priority    | 1
    sync_state       | sync

sync_state: sync говорит о том, что репликация работает в синхронном режиме.

Остановим реплику.

    student$ sudo pg_ctlcluster 10 beta stop

    α=> \c - student

    You are now connected to database "replica_physical" as user "student".

    α=> BEGIN;

    BEGIN

    α=> CREATE TABLE test(id integer);

    CREATE TABLE

    α=> COMMIT;

Фиксация ждет появления синхронной реплики...

    student$ sudo pg_ctlcluster 10 beta start

После старта реплики фиксация завершается.

    COMMIT

# Отмена запроса из-за очистки на мастере

    α=> INSERT INTO test VALUES (1);

    INSERT 0 1

    student$ psql -p 5433 -d replica_physical

    β=> SHOW max_standby_streaming_delay;

     max_standby_streaming_delay
    -----------------------------
     30s
    (1 row)

Начинаем транзакцию с уровнем изоляции repeatable read: первый оператор создаст снимок данных, который будет использоваться всеми последующими операторами этой транзакции.

    β=> BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;

    BEGIN

    β=> SELECT * FROM test;

     id
    ----
      1
    (1 row)

На мастере изменяем единственную строку и выполняем очистку. При этом первая версия строки будет удалена.

    α=> UPDATE test SET id = 2;

    UPDATE 1

    α=> VACUUM test;

    VACUUM

Через 20 секунд запрос на реплике еще сработает - реплика задерживает применение конфликтующей журнальной записи:

    β=> SELECT * FROM test;

     id
    ----
      1
    (1 row)

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

    β=> SELECT * FROM test;

    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.
    connection to server was lost

# Запрет откладывания применения конфликтующей записи

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

    student$ psql -p 5433 -d replica_physical -U postgres

    β=> ALTER SYSTEM SET max_standby_streaming_delay = 0;

    ALTER SYSTEM

    β=> \c - student

    You are now connected to database "replica_physical" as user "student".

    student$ sudo pg_ctlcluster 10 beta reload

Снова начинаем транзакцию...

    β=> BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;

    BEGIN

    β=> SELECT * FROM test;

     id
    ----
      2
    (1 row)

На мастере изменяем строку и выполняем очистку...

    α=> UPDATE test SET id = 3;

    UPDATE 1

    α=> VACUUM test;

    VACUUM

Запрос на реплике прерывается тут же:

    β=> SELECT * FROM test;

    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.
    connection to server was lost

# Обратная связь

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

    student$ psql -p 5433 -d replica_physical -U postgres

    β=> ALTER SYSTEM SET hot_standby_feedback = on;

    ALTER SYSTEM

    β=> ALTER SYSTEM SET wal_receiver_status_interval = '1s';

    ALTER SYSTEM

    β=> \c - student

    You are now connected to database "replica_physical" as user "student".

    student$ sudo pg_ctlcluster 10 beta reload

Снова начинаем транзакцию...

    β=> BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;

    BEGIN

    β=> SELECT * FROM test;

     id
    ----
      3
    (1 row)

На мастере изменяем строку и выполняем очистку...

    α=> UPDATE test SET id = 4;

    UPDATE 1

    α=> VACUUM VERBOSE test;

    INFO:  vacuuming "public.test"
    INFO:  "test": found 0 removable, 2 nonremovable row versions in 1 out of 1 pages
    DETAIL:  1 dead row versions cannot be removed yet, oldest xmin: 630
    There were 0 unused item pointers.
    Skipped 0 pages due to buffer pins, 0 frozen pages.
    0 pages are entirely empty.
    CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
    VACUUM

Благодаря обратной связи, очистка не может удалить старую версию строки (found 0 removable, 2 nonremovable row versions).

    β=> SELECT * FROM test;

     id
    ----
      3
    (1 row)

    β=> COMMIT;

    COMMIT

    α=> VACUUM VERBOSE test;

    INFO:  vacuuming "public.test"
    INFO:  "test": found 1 removable, 1 nonremovable row versions in 1 out of 1 pages
    DETAIL:  0 dead row versions cannot be removed yet, oldest xmin: 631
    There were 1 unused item pointers.
    Skipped 0 pages due to buffer pins, 0 frozen pages.
    0 pages are entirely empty.
    CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
    VACUUM

После завершения транзакции на реплике, мастер может выполнить очистку (found 1 removable...). Журнальные записи реплицируются, но ничего не поломают.