Пример использования логической репликации PostgreSQL

26 июля 2017

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

Возможности и ограничения логической репликации

Итак, зачем же все это нужно:

  • Обновление без даунтайма;
  • Репликация только части данных, а не всего подряд;
  • На реплике можно использовать временные таблицы, да и вообще писать что угодно, в том числе в реплицируемые таблицы;
  • Одна реплика может подтягивать данные с двух и более мастеров;
  • И прочие сценарии, когда потоковая репликация чем-то не подошла;

Стоит также отметить, что схема таблиц на мастере и на реплике может различаться. Во-первых, может отличаться порядок столбцов. Во-вторых, на реплике таблица может иметь nullable столбцы, которых нет на мастере. Тогда при репликации в них будет записываться NULL. Однако в таблице на мастере не может быть больше столбцов, чем на реплике, даже если в этих столбцах хранятся только NULL’ы. Если такое случится, репликация встанет и возобновится только после решения проблемы. То есть, добавления столбцов на реплике или исправления схемы на мастере.

Важное ограничение логической репликации заключается в том, что реплицируемые таблицы должны иметь primary key. Кроме того, DDL, операция TRUNCATE, а также sequences не реплицируются.

Установка и предварительная настройка PostgreSQL

Далее будет описана упрощенная установка PostgreSQL. Она подходит для тестирования логической репликации, но не для использования в боевом окружении. Если вас интересует что-то более приближенное к использованию в бою, обратите внимание на заметки Начало работы с PostgreSQL, а также PostgreSQL: сборка из исходников и настройка под Linux.

Все описанные ниже шаги были проверены мной на PostgreSQL 10 Beta 2 при помощи двух виртуалок. Для работы с виртуалками я использую самопальный скрипт на Python, ранее описанный в заметке Управление VirtualBox из консоли с помощью vboxmanage. Мастер работал под управлением Arch Linux, а реплика — под управлением Ubuntu. Установка PostgreSQL на обеих машинах производилась следующим образом:

# переменную окружения также можно вписать в ~/.bash_profile
export PGINSTALL=~/postgresql-install
export PATH="$PGINSTALL/bin:$PATH"
mkdir -p $PGINSTALL

git clone https://github.com/afiskon/pgscripts.git
git clone git://git.postgresql.org/git/postgresql.git
cd postgresql
git checkout REL_10_BETA2

./configure --prefix=$PGINSTALL # --enable-cassert --enable-debug
make && make check
~/pgscripts/single-install.sh

Скрипт single-install.sh из моей коллекции pgscripts автоматически настраивает PostgreSQL пригодным для использования логической репликации образом. Однако в бою вы скорее всего не сможете на него положится. Поэтому рассмотрим параметры из postgresql.conf, на которые стоит обратить внимание.

На мастере:

  • wal_level — должен быть как минимум logical;
  • listen_address — сервер должен слушать интерфейс, через который в него будет ходить реплика;
  • max_wal_senders — ожидаемое число реплик, плюс небольшой запас для случаев, когда реплика переподключается, а старое соединение еще не закрылось по таймауту;
  • max_connections — должен быть равен max_wal_senders прибавить ожидаемое число клиентов;
  • max_replication_slots — максимальное количество replication slot’ов;
  • synchronous_commit — имеет такой же смысл для логической репликации, что и для потоковой, то есть, стоит поставить в on или remote_apply;

На реплике, скорее всего, ничего тюнить не придется, но на всякий случай стоит перепроверить:

  • max_worker_processes;
  • max_replication_slots;
  • max_sync_workers_per_subscription;
  • max_logical_replication_workers;

Полное и самое подробное описание всех упомянутых параметров вы найдете в официальной документации.

У меня машины объединены в сеть 10.128.0.0/16. Чтобы можно было ходить с одной машины на другую, в pg_hba.conf следует прописать что-то вроде:

host all all 10.128.0.0/16 trust

Вместо trust на практике может иметь смысл использовать md5, scram-sha-256 или любую другую аутентификацию, поддерживаемую PostgreSQL. Стоит отметить, что поддержка scram-sha-256 появилась только в PostgreSQL 10.

После внесения изменений в конфигурацию говорим:

pg_ctl -D $PGINSTALL/data-master reload

… и проверяем, что можем ходить с одной машины на другую:

# здесь -U - имя пользователя, а -d - имя базы
psql -h 10.128.0.16 -U eax -d eax

Если все получилось, значит теперь можно воспользоваться логической репликацией.

Использование логической репликации

На обеих машинах создаем какие-нибудь таблицы:

DROP TABLE IF EXISTS test;
CREATE TABLE test(k TEXT PRIMARY KEY, v TEXT);

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

На мастере заполняем таблицу и создаем публикацию:

INSERT INTO test VALUES ('k1', 'v1'), ('k2', 'v2');
CREATE PUBLICATION allpub FOR ALL TABLES;

Здесь в публикацию были добавлены все сущесвтующие таблицы, но также можно указать и список конкретных таблиц:

CREATE PUBLICATION allpub FOR TABLE table1, table2;

Кроме того, можно указать, какие именно изменения следует публиковать:

CREATE PUBLICATION allpub
  FOR ALL TABLES WITH (publish = 'insert,update');

Изменение списка таблиц в публикации происходит так:

ALTER PUBLICATION pubname ADD  TABLE t1, t2;
ALTER PUBLICATION pubname SET  TABLE t3, t4;
ALTER PUBLICATION pubname DROP TABLE t5, t6;

На реплике создаем подписку и проверяем содержимое таблицы:

-- можно указывать и несколько публикаций через запятую
CREATE SUBSCRIPTION allsub
  CONNECTION 'host=10.128.0.16 user=eax dbname=eax'
  PUBLICATION allpub;
SELECT * FROM test;

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

INSERT INTO test SELECT 'kk' || i, 'vv' || i
  FROM generate_series(1, 10000) AS i;

При создании новых таблиц реплика не будет автоматически их тянуть, даже если публикация была создана как FOR ALL TABLES. Как уже было отмечено, DDL не реплицируется. Для решения проблемы нужно создать на реплике таблицы с такой же схемой, что и на мастере (с поправкой на порядок полей и прочее, см выше), а затем сказать:

ALTER SUBSCRIPTION allsub REFRESH PUBLICATION;

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

ALTER SUBSCRIPTION allsub ENABLE;
ALTER SUBSCRIPTION allsub DISABLE;

… а также менять мастера:

ALTER SUBSCRIPTION allsub CONNECTION 'host=...';

Наконец, рассмотрим удаление публикации и подписки:

-- на реплике
DROP SUBSCRIPTION allsub;

-- на мастере
DROP PUBLICATION allpub;

Казалось бы, что это все. Однако на практике репликацию нужно еще и как-то мониторить. Давайте выясним, как это делается.

Узнаем состояние логической репликации

Просмотр списка публикаций на мастере:

eax=# \dRp
                   List of publications
  Name  | Owner | All tables | Inserts | Updates | Deletes
--------+-------+------------+---------+---------+---------
 allpub | eax   | t          | t       | t       | t
(1 row)

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

eax=# \dRs
         List of subscriptions
  Name  | Owner | Enabled | Publication
--------+-------+---------+-------------
 allsub | eax   | t       | {allpub}
(1 row)

Состояние логической репликации на мастере:

eax=# select * from pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid              | 24649
usesysid         | 10
usename          | eax
application_name | allsub
client_addr      | 10.128.0.23
client_hostname  |
client_port      | 41736
backend_start    | 2017-07-17 19:19:57.617374+03
backend_xmin     |
state            | streaming
sent_lsn         | 0/163C100
write_lsn        | 0/163C100
flush_lsn        | 0/163C100
replay_lsn       | 0/163C100
write_lag        |
flush_lag        |
replay_lag       |
sync_priority    | 0
sync_state       | async

eax=# select * from pg_replication_slots;
-[ RECORD 1 ]-------+----------
slot_name           | allsub
plugin              | pgoutput
slot_type           | logical
datoid              | 16384
database            | eax
temporary           | f
active              | t
active_pid          | 24723
xmin                |
catalog_xmin        | 582
restart_lsn         | 0/1696600
confirmed_flush_lsn | 0/1696638

Состояние на реплике:

eax=# select * from pg_stat_subscription;
-[ RECORD 1 ]---------+------------------------------
subid                 | 16408
subname               | allsub
pid                   | 2411
relid                 |
received_lsn          | 0/163C100
last_msg_send_time    | 2017-07-17 19:20:31.400501+03
last_msg_receipt_time | 2017-07-17 16:25:09.14483+03
latest_end_lsn        | 0/163C100
latest_end_time       | 2017-07-17 19:20:31.400501+03

В безысходных ситуациях что на мастере, что на реплике, можно сказать:

SELECT * FROM pg_stat_activity;

Как видите, все необходимые отладочные ручки в наличии.

Заключение

Логическая репликация довольно проста в использовании и открывает много новых возможностей. Стоит однако учесть, что на момент написания этих строк PostgreSQL 10 еще находился в состоянии беты. Для использовании в серьезном продакшене стоит подождать какое-то время (которое зависит от степени серьезности вашего продакшена), пока эту фичу как следует не обкатают.

Дополнение: Вас также может заинтересовать статья Пример использования logical decoding в PostgreSQL.

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.