Разберем процесс репликации. Репликация в PostgreSQL — это процесс синхронизации нескольких копий кластера баз данных на разных серверах. Она бывает логической и физической.
Задачи и виды репликации
Репликация PostgreSQL решает две задачи:
- отказоустойчивость — если сломается один из серверов, клиенты могут продолжить работать на резервном;
- масштабируемость — резервный сервер принимает запросы на чтение, так что некоторую нагрузку можно возложить на него.
Репликация на серверах PostgreSQL бывает двух видов:
- Физическая — основной сервер передает поток wal записей на сервер репликации. Требования следующие:
- одинаковые версии PostgreSQL;
- одинаковые ОС;
- возможна репликация только всего кластера.
- Логическая — поставщик публикует свои изменения, а подписчик получает и применяет эти изменения у себя. Особенности следующие:
- оба сервера могут быть и поставщиком и подписчиком, но на разные объекты. Например на в одном кластере опубликована одна табличка, а на другом — другая, и эти кластера подписаны друг на друга (на эти таблички). Это позволяет использовать двухсторонний обмен;
- репликация возможна между разными ОС;
- возможна выборочная репликация отдельных объектов кластера.
Физическая репликация PostgreSQL
Алгоритм создания такой репликации следующий:
- Делаем резервную копию с помощью pg_basebackup. С помощью отдельных опций указываем что копию нужно подготовить к дальнейшей репликации.
- Разворачиваем полученную резервную копию на сервере репликации.
- Там же, создаем специальный файл с настройками репликации (если были указаны нужные опции для команды pg_basebackup, то все необходимые файлы, нужные для репликации, создадутся автоматом):
- в 10 версии PostgreSQL создаём файл recovery.conf, прописываем там standby_mode = on;
- начиная с 12 версии создаём пустой файл standby.signal;
- Стартуем PostgreSQL на реплике, после чего начнётся процесс репликации. Сервер начнёт процесс восстановления из потока wal записей.
Сервер репликации может принимать запросы, но только на чтение. Этот сервер не генерирует wal записи, а продолжает их получать из потока по протоколу репликации с основного сервера.
Журнальные записи можно передавать по протоколу репликации или можно использовать архив WAL. Но обычно применяют первое.
На главном сервере появляется процесс wal sender, который передаёт wal записи. На сервере репликации появляется wal receiver, который принимает эти записи.
Репликация может быть синхронной и асинхронной. При синхронной репликации основной сервер не только отправляет запись на реплику, но и дожидается подтверждения что запись туда дошла и была там применена. Но синхронная реплика сильно тормозит производительность кластера.
Переключение на реплику
Переключение на реплику осуществляется либо планово, либо аварийно. Но в любом случае оно происходит в ручном режиме. Хотя процесс можно автоматизировать, например с помощью скриптов.
Для восстановления основного сервера его обычно превращают в реплику и синхронизируют с бывшей репликой. После синхронизации сервера опять меняют местами.
Восстановление основного сервера проделывают следующими способами:
- Если основной сервер был остановлен корректно и все журнальные записи успели перейти на реплику, то можно просто поменять сервера местами:
- останавливаем основной сервер;
- реплику превращаем в основной сервер;
- основной сервер превращаем в реплику.
- Если основной сервер был остановлен некорректно, то может произойти рассогласование журнальных записей. В таком случае алгоритм следующий:
- реплику превращаем в основной сервер (пусть клиенты пока поработают на этом сервере);
- на основном сервере (который был выключен некорректно) очищаем каталог PGDATA;
- затем основной сервер превращаем в реплику.
- Восстановление с помощью утилиты pg_rewind позволяет не очищать PGDATA даже при некорректном завершении. Но не всегда это срабатывает. Обычно для восстановления можно вначале пробовать утилиту pg_rewind, а если не сработает то очищать PGDATA.
Сценарии использования физической репликации
- обычная репликация — для создания резервного сервера;
- каскадная репликация — к основному серверу подключаем реплику, а к этой реплики еще одну реплику;
- отложенная репликация — в recovery.conf специальным параметром можно указать задержку воспроизведения. Чтобы реплика всегда отставала от основного сервера, например, на час.
Логическая репликация PostgreSQL
При репликации передаются wal записи, но для работы логической репликации нужно изменить формат этих записей. Для этого нужно поменять параметр кластера wal_level = logical.
Поставщик — передаёт логические wal записи. Но передаются не все команды, а только INSERT, UPDATE, DELETE и TRANSCATE. То есть CREATE не передаётся, поэтому нужна начальная физическая синхронизация. Также мы можем ограничить публикацию некоторыми командами, например передавать только INSERT, а UPDATE не передавать.
При такой репликации всегда используется слот логической репликации. Благодаря которому понятно, какие записи уже были переданы, а какие нет.
Подписчик — получает wal записи и применяет изменения без разбора, трансформаций и планирования.
Подписчик и поставщик это равноценные сервера, они оба доступны на чтение и запись для клиентов. Поэтому могут возникать конфликты. Например на поставщике выполняется INSERT, а на подписчике уже такая строка есть. Подобные конфликты решаются пока только в ручном режиме.
На поставщике работает уже знакомый процесс wal sender, а на подписчике logical replication worker который получает логические wal записи и применяет их от имени суперпользователя.
Сценарии использования логической репликации
- Собираем данные на центральном кластере. Например в каждом филиале есть сервер баз данных и данные со всех этих серверов собираются на каком-нибудь центральном сервере.
- Распространяем данные с центрального кластера. Например на центральном сервере мы меняем какую-то табличку, а подписчики в филиалах обновляют её у себя.
- Можно использовать логическую репликация для обновления кластера. Так как версия Postgres не имеет значение, можем выключить и обновить подписчик до новой версии. Затем поменять местами поставщика и подписчика. И наконец выключить и обновить основной сервер.
- Мультимастер — кластер в котором данные могут менять несколько серверов. Логическая репликация для этого тоже предназначена, но в PostgreSQL пока реализовать такое нельзя. Существует проблема зацикливания. Если оба сервера будут публиковать одну и туже таблицу и будут подписчиками друг на друга, то возникнет зацикливание и произойдёт ошибка репликации.
Практика
Физическая репликация
После предыдущего урока у нас два каталога PGDATA. При этом второй кластер выключен.
Очистим каталог /usr/local/pgsql/data2/ и заново сделаем туда резервную копию с помощью pg_basebackup. Ключ -R подготовит архивную копию к дальнейшей репликации (создаст файл standby.signal):
postgres@s-pg13:~$ rm -rf /usr/local/pgsql/data2/* postgres@s-pg13:~$ pg_basebackup --pgdata=/usr/local/pgsql/data2 -R postgres@s-pg13:~$ ls /usr/local/pgsql/data2 backup_label base pg_commit_ts pg_hba.conf pg_logical pg_notify pg_serial pg_stat pg_subtrans pg_twophase pg_wal postgres.conf postgresql.conf backup_manifest global pg_dynshmem pg_ident.conf pg_multixact pg_replslot pg_snapshots pg_stat_tmp pg_tblspc PG_VERSION pg_xact postgresql.auto.conf standby.signal
Помимо standby.signal в postgresql.auto.conf были внесены параметры для последующей репликации:
postgres@s-pg13:~$ cat /usr/local/pgsql/data2/postgresql.auto.conf # Do not edit this file manually! # It will be overwritten by the ALTER SYSTEM command. work_mem = '10MB' track_io_timing = 'on' track_functions = 'all' log_min_duration_statement = '0' log_line_prefix = '(pid=%p) ' primary_conninfo = 'user=postgres passfile=''/home/postgres/.pgpass'' channel_binding=disable port=5432 sslmode=disable sslcompression=0 ssl_min_protocol_version=TLSv1.2 gssencmode=disable krbsrvname=postgres target_session_attrs=any'
Был добавлен параметр primary_conninfo, в котором указаны опции подключения к основному серверу:
- port=5432
- sslmode=disable
- и другие.
Вот ещё пример, который не нужно выполнять!
Задаются параметры подключения к основному серверу с помощью опций pg_basebackup. Например мы подключались бы к другому серверу, а для репликации был бы отдельный пользователь, тогда команда выглядела бы так:
$ pg_basebackup -h 192.168.0.100 -D /var/lib/postgresql/12/main -R -P -U replication -X stream
В команде выше мы используем следующие опции:
- -h — хост основного сервера;
- -D — каталог PGDATA на основном сервере;
- -R — создает файл standby.signal;
- -P — включает отчет о прогрессе;
- -U — указываем пользователя для подключения;
- -X stream — используется для включения необходимых wal файлов в резервную копию, stream означает потоковую передачу WAL.
После такой команды, конфигурационный файл postgresql.auto.conf будет содержать такой параметр подключения к основному серверу:
primary_conninfo = 'user=replication passfile=''/var/lib/postgresql/.pgpass'' host=192.168.0.100 port=5432 sslmode=prefer sslcompression=0 gssencmode=prefer krbsrvname=postgres target_session_attrs=any'
Поменяем порт второго кластера, чтобы он мог работать одновременно с первым:
postgres@s-pg13:~$ echo 'port = 5433' >> /usr/local/pgsql/data2/postgresql.auto.conf
Файл standby.signal который появился во втором кластере означает настройку standby_mode=on в recovery.conf до PostgreSQL 12. То есть ничего делать не нужно для того чтобы перевести сервер в режим работы реплики.
Можем запустить второй кластер:
postgres@s-pg13:~$ pg_ctl -w -l /home/postgres/logfile2 -D /usr/local/pgsql/data2 start waiting for server to start.... done server started
Посмотрим на процессы реплики. Процесс walreceiver streaming принимает поток wal записей, а процесс startup recovering применяет изменения:
postgres@s-pg13:~$ ps -o pid,command --ppid `head -n 1 /usr/local/pgsql/data2/postmaster.pid` PID COMMAND 8320 postgres: startup recovering 000000010000000000000009 8321 postgres: checkpointer 8322 postgres: background writer 8323 postgres: stats collector 8324 postgres: walreceiver streaming 0/9000148
В основном кластере появился процесс walsender postgres, который передаёт wal записи:
postgres@s-pg13:~$ ps -o pid,command --ppid `head -n 1 /usr/local/pgsql/data/postmaster.pid` PID COMMAND 8325 postgres: walsender postgres [local] streaming 0/9000148 29392 postgres: checkpointer 29393 postgres: background writer 29394 postgres: walwriter 29395 postgres: autovacuum launcher 29396 postgres: stats collector 29397 postgres: logical replication launcher
Проверить состояние репликации можно в представлении pg_stat_replication на главном сервере:
postgres@s-pg13:~$ psql -p 5432 Timing is on. psql (13.3) Type "help" for help. postgres@postgres=# SELECT * FROM pg_stat_replication \gx -[ RECORD 1 ]----+------------------------------ pid | 8325 usesysid | 10 usename | postgres application_name | walreceiver client_addr | client_hostname | client_port | -1 backend_start | 2021-06-30 11:18:38.820549+03 backend_xmin | state | streaming sent_lsn | 0/9000148 write_lsn | 0/9000148 flush_lsn | 0/9000148 replay_lsn | 0/9000148 write_lag | flush_lag | replay_lag | sync_priority | 0 sync_state | async reply_time | 2021-06-30 11:21:19.151904+03 Time: 1,885 ms
Внесём некоторые изменения на мастере:
postgres@postgres=# CREATE DATABASE replica_overview; CREATE DATABASE Time: 84,617 ms postgres@postgres=# \c replica_overview; You are now connected to database "replica_overview" as user "postgres". postgres@replica_overview=# CREATE TABLE test(id integer PRIMARY KEY, descr text); CREATE TABLE Time: 4,285 ms
Проверим, создались ли эти объекты на сервере репликации:
postgres@replica_overview=# \q postgres@s-pg13:~$ psql -p 5433 -d replica_overview Timing is on. psql (13.3) Type "help" for help. postgres@replica_overview=# SELECT * FROM test; id | descr ----+------- (0 rows) Time: 0,961 ms
Сама реплика ничего менять не может:
postgres@replica_overview=# INSERT INTO test VALUES (2, 'Два'); ERROR: cannot execute INSERT in a read-only transaction Time: 0,295 ms
Переведём реплику в обычный режим, то есть отключим репликацию. Это делается с помощью команды promote. При этом у нас пропадет файлик standby.signal.
postgres@replica_overview=# \q postgres@s-pg13:~$ pg_ctl -w -D /usr/local/pgsql/data2 promote waiting for server to promote.... done server promoted postgres@s-pg13:~$ ls /usr/local/pgsql/data2/ backup_label.old global pg_hba.conf pg_multixact pg_serial pg_stat_tmp pg_twophase pg_xact postgresql.conf backup_manifest pg_commit_ts pg_ident.conf pg_notify pg_snapshots pg_subtrans PG_VERSION postgres.conf postmaster.opts base pg_dynshmem pg_logical pg_replslot pg_stat pg_tblspc pg_wal postgresql.auto.conf postmaster.pid
После этого второй кластер может писать данные:
postgres@s-pg13:~$ psql -p 5433 -d replica_overview Timing is on. psql (13.3) Type "help" for help. postgres@replica_overview=# INSERT INTO test VALUES (2, 'Два'); INSERT 0 1 Time: 1,328 ms
Логическая репликация
Поменяем параметр wal_level = logical для первого кластера и перезапустим этот его:
postgres@replica_overview=# \q postgres@s-pg13:~$ psql -p 5432 -d replica_overview Timing is on. psql (13.3) Type "help" for help. postgres@replica_overview=# ALTER SYSTEM SET wal_level = logical; ALTER SYSTEM Time: 0,898 ms postgres@replica_overview=# \q postgres@s-pg13:~$ pg_ctl -w -l /home/postgres/logfile -D /usr/local/pgsql/data restart waiting for server to shut down.... done server stopped waiting for server to start.... done server started
В первом кластере создадим публикацию (CREATE PUBLICATION) и посмотрим на неё с помощью команды \dRp+:
postgres@s-pg13:~$ psql -d replica_overview Timing is on. psql (13.3) Type "help" for help. postgres@replica_overview=# CREATE PUBLICATION test_pub FOR TABLE test; CREATE PUBLICATION Time: 2,804 ms postgres@replica_overview=# \dRp+ Publication test_pub Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ----------+------------+---------+---------+---------+-----------+---------- postgres | f | t | t | t | t | f Tables: "public.test"
Во втором кластере подписываемся на эту публикацию (CREATE SUBSCRIPTION) и отключаем первоначальное копирование данных (copy_data = false):
postgres@replica_overview=# \q postgres@s-pg13:~$ psql -p 5433 -d replica_overview Timing is on. psql (13.3) Type "help" for help. postgres@replica_overview=# CREATE SUBSCRIPTION test_sub CONNECTION 'host=localhost port=5432 user=postgres dbname=replica_overview' PUBLICATION test_pub WITH (copy_data = false); NOTICE: created replication slot "test_sub" on publisher CREATE SUBSCRIPTION Time: 11,027 ms
Выше вы можете заметить предупреждение, что на публикующем сервере создался слот репликации (NOTICE: created replication slot «test_sub» on publisher)!
Теперь посмотрим на подписку с помощью команды \dRs:
postgres@replica_overview=# \dRs List of subscriptions Name | Owner | Enabled | Publication ----------+----------+---------+------------- test_sub | postgres | t | {test_pub} (1 row)
Проверим настроенную логическую репликацию. Для этого в первом кластере, вставим новую строчку в опубликованной табличке:
postgres@replica_overview=# \q postgres@s-pg13:~$ psql -d replica_overview Timing is on. psql (13.3) Type "help" for help. postgres@replica_overview=# INSERT INTO test VALUES (3, 'Три'); INSERT 0 1 Time: 1,416 ms
Во втором кластере увидим эту строку:
postgres@replica_overview=# \q postgres@s-pg13:~$ psql -p 5433 -d replica_overview Timing is on. psql (13.3) Type "help" for help. postgres@replica_overview=# SELECT * FROM test; id | descr ----+------- 2 | Два 3 | Три (2 rows) Time: 0,540 ms
Состояние подписки можно посмотреть в представлении pg_stat_subscription:
postgres@replica_overview=# SELECT * FROM pg_stat_subscription \gx -[ RECORD 1 ]---------+------------------------------ subid | 24587 subname | test_sub pid | 8578 relid | received_lsn | 0/9036BE8 last_msg_send_time | 2021-06-30 11:37:07.893511+03 last_msg_receipt_time | 2021-06-30 11:37:07.893552+03 latest_end_lsn | 0/9036BE8 latest_end_time | 2021-06-30 11:37:07.893511+03 Time: 1,585 ms
К процессам сервера подписчика добавился logical replication worker:
postgres@replica_overview=# \q postgres@s-pg13:~$ ps -o pid,command --ppid `head -n 1 /usr/local/pgsql/data2/postmaster.pid` PID COMMAND 8321 postgres: checkpointer 8322 postgres: background writer 8323 postgres: stats collector 8424 postgres: walwriter 8425 postgres: autovacuum launcher 8426 postgres: logical replication launcher 8578 postgres: logical replication worker for subscription 24587
Если репликация больше не нужна, надо удалить подписку, иначе на публикующем сервере останется открытым слот репликации:
postgres@s-pg13:~$ psql -p 5433 -d replica_overview Timing is on. psql (13.3) Type "help" for help. postgres@replica_overview=# DROP SUBSCRIPTION test_sub; NOTICE: dropped replication slot "test_sub" on publisher DROP SUBSCRIPTION Time: 13,534 ms
На этом этот курс подошёл к концу! За основу взяты видео с этого плейлиста на YouTube. Я просто проделал все на PostgreSQL 13 и лишь некоторые моменты связанные с репликацией отличаются от PostgreSQL 10. В дальнейшем, если вернусь к теме PostgreSQL постараюсь усовершенствовать этот курс, спасибо за внимание!