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

α=> 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...). Журнальные записи реплицируются, но ничего не поломают.