PostgreSQL – великолепная база данных, во многом – лучше MySQL. При этом у PostgreSQL довольно мало документации (кроме официальной) – MySQL раньше стал популярен и сейчас элементарно чаще встречается. Руководств по настройке репликации в MySQL - полный интернет, а для PostgreSQL на русском я пошаговых инструкций просто не видел. Это – именно такая инструкция.

Мотивация

Репликация – это очень просто. Репликация означает копирование состояние одного сервера на другой. То есть – любые изменения, примененные на основной сервер (master) будут скопированы на его “заместителя” (slave). Для чего это нужно:

  • Для распределения нагрузки. Slave не может записывать данные, но с него можно эти данные читать. По личному опыту, более 80% нагрузки на базу данных – это именно чтение в том или ином виде. Slave (или несколько) позволяют разгрузить мастер. Установка нескольких дешевых серверов чаще всего обходится дешевле, чем обновления одного, но дорогого (горизонтальное масштабирование дешевле вертикального. В некоторых случаях мешает закон Амдаля, но у нас не тот случай).
  • Для построения отказоустойчивых систем. В случае, если с мастером что-то случилось – превратить slave в master можно буквально за секунды, это снижает время простоя. Восстановление из резервной копии займет много больше времени. Кроме того, состояние slave-а будет максимально приближено к состоянию master-а на момент отказа. Бэкапы обычно делаются по расписанию. То есть – все данные, записанные после создания резервной копии и до отказа мастера можно считать потерянными безвозвратно.

Как и у всякой технологии, у репликации есть ограничения:

  • Репликация в PostgreSQL – исключительно однонаправленная (master -> slave). PostgreSQL не поддерживает мультимастер (есть внешние решения, но они выходят за рамки этой статьи)
  • Репликация дополняет бэкап, но не заменяет его. Реплика спасет данные, если с мастер-сервером что-то случилось: отказ электричества, сервер сгорел, жесткие диски умерли, пожар в ЦОД, правоохранительные органы изъяли оборудование и т.д. Репликация никак не поможет при логической ошибке (код запорол данные) или ошибке оператора (“призрак человека с консолью”).
  • Особенность именно PostgreSQL - репликация возможна только всего сервера целиком, нельзя выбрать базы, которые будут реплицироваться (или не будут).
  • По умолчанию репликация - асинхронная. Это значит, что мастер пишет данные постоянно, а slave вытаскивает изменения и применяет их у себя по мере возможности. Вообще, в норме это не вызывает проблем. Но, если вдруг у slave возникли с этим проблемы (мастер несравнимо мощнее и slave не успевает применять изменения, или проблемы с сетью между мастером и slave) – master “убежит” вперед. Данные при этом потеряны не будут, и slave догонит мастер, как только сможет. Такую ситуацию несложно отслеживать (дальше покажу, как), просто нужно иметь это ввиду. Репликацию можно сделать синхронной, чтобы гарантировать абсолютную консистентность данных между серверами, но это удорожает транзакции – производительность записи упадет, а нагрузка – вырастет.
  • Репликация использует отдачу WAL-сегментов с мастера на slave-ы. Эти сегменты надо на мастере где-то хранить, то есть нужно запланировать дополнительное место для них.
  • Репликация возможна только между серверами с общей майорной версией (то есть реплицироваться 9.5 -> 9.5 можно, а с 9.4 -> 10.0 – нельзя). На всякий случай напомню, что до версии 10.0 обновления 9.4 -> 9.5 считались майорными, а не минорными. У разных версий разный формат хранения данных.
  • потоковая репликация возможна только в PostgreSQL 9 и выше. Она не работает в 7 и 8 версиях.

Мутные технические подробности

Каждый postgresql-сервер пишет все изменения сначала в WAL (write-ahead log), и только затем – применяет изменения в реальное пространство базы данных. Это позволяет гарантировать целостность данных и отсутствие конфликтов изменений в табличном пространстве. В случае, если сервер по какой-то причине перезагрузился – он сначала проверяет текущий номер транзакции, примененный к табличному пространству (то есть - успешно завершенная запись). Затем сервер проверят WAL и дописывает разницу из WAL в tablespace. Номер транзакции всегда растет монотонно, что исключает конфликты очередности применения. Запись в WAL обходится дешевле, так как в WAL записываются только изменения, и они туда только последовательно пишутся (и эпизодически – последовательно читаются). Когда все транзакции из файла WAL считаются успешно примененными на сервер – WAL помечается как готовый (full) и удаляется. В случае репликации slave получает копию WAL с мастера (через специальный процесс wal streamer service - по одному на каждый slave). Для того, чтобы синхронизировать мастер со slave, нужно:

  • скопировать текущее состояние мастера на slave.
  • включить на мастере wal streaming (вещание wal-файлов)
  • дождаться, пока slave не подключится к мастеру и не вытянет изменения и не применит их

Пошаговое руководство

В примере будут участвовать два сервера:

  • master.db.local (10.0.0.1)
  • slave.db.local (10.0.0.2)

Для упрощения считаем, что мастер уже настроен, запущен и работает. Slave – это пустой сервер без данных вообще, там только установлена ОС и сам postgres. Версии PostgreSQL на обоих серверах имеют одинаковый номер версии в майоре (к примеру 9.6.0 на master и 9.6.4 на slave). В данном примере я использую Debian и PostgreSQL 9.6. Для других ОС и версий PostgreSQL настройки отличатся не будут, но могут отличаться пути, по которым лежат конфиги и файлы данных.

Настройка master

Для начала поправим postgresql.conf. В Debian он находится по адресу /etc/postgresql/VERSION/CLUSTER/postgresql.conf. В нашем примере это /etc/postgresql/9.6/master/postgresql.conf

#master должен быть доступен по сети для slave
#listen_addresses может принимать несколько значений (через запятую)
#можно поставить * - postgres будет доступен на всех сетевых интерфейсах
listen_addresses = '10.0.0.1'
#режим хранения WAL-сегментов. Для репликации – только hot_standby
wal_level = hot_standby
#максимальное количство wal_sender.
#это максимум slave-ов, который сможет подключится к этому серверу
max_wal_senders = 5
#сколько заполненных WAL-сегментов хранить на мастере перед удалением
#число можно подобрать только экспериментально (больше изменений – больше WAL надо хранить)
wal_keep_segments = 32
#папка для архива. Удаленный WAL-сегмент будет скопирован туда
#архивом можно пользоваться для восстановления slave, если slave не успел выкачать WAL с мастера, а мастер его уже удалил
#там должно быть много места – сам postgres не будет чистить свой архив
archive_mode    = on
archive_command = 'cp %p /var/lib/pg-archive/%f'

Теперь нужно разрешить slave-у подключаться к мастеру для репликации. Для этого отредактируем pg_hba.conf (лежит там же, где postgresql.conf), и добавим туда специального пользователя:

#TYPE   DB             USER           ADDRESS        #METHOD
host    replication    replication    10.0.0.2/32    md5

Теперь надо создать папку для архива:

mkdir /var/lib/pg-archive/
chown postgres /var/lib/pg-archive/
chmod 700 /var/lib/pg-archive/

И перезапустить master:

service postgresql restart

Создадим пользователя для репликации. Для этого в консоли самого постгреса выполним команду:

CREATE ROLE replication WITH REPLICATION PASSWORD 'Hw572BbvG7g4cwq5' LOGIN;

Пароль нам потребуется для авторизации slave-а на мастере. Рекомендуется делать его посложнее. Для того, чтобы slave мог читать данные с мастера – на мастере должно быть разрешено соединение с портом postgresql (по умолчанию - 5432), проверьте firewall!

Копируем данные

Для начала остановим postgres на slave и удалим данные из datadir на salve:

slave> service postgresql stop
slave> rm -rf /var/lib/postgresql/9.6/main/*

Теперь скопируем основной каталог данных с мастера (текущее состояние)

master> psql -c "SELECT pg_start_backup('sync', true)"
master> rsync -rahzP /var/lib/postgresql/9.6/main/ 10.0.0.2:/var/lib/postgresql/9.6/main/ --exclude=postmaster.pid
master> psql -c "SELECT pg_stop_backup('sync', true)"

Настраиваем salve

Если вы хотите читать данные из slave - нужно включить режим hot_standby. Это полезно, если slave используется для распределения нагрузки на чтение. Если slave нужен исключительно как горячая замена мастеру на случай аварии – этот параметр можно не трогать. В конфиге /etc/postgresql/9.6/master/postgresql.conf добавим:

hot_standby = on

В папке с данными (в нашем примере это /var/lib/postgresql/9.6/main/) создадим файл с настройками репликации. Он должен называться recovery.conf

standby_mode = 'on'
primary_conninfo = 'host=10.0.0.1 port=5432 user=replication password=Hw572BbvG7g4cwq5'
trigger_file = '/var/lib/postgresql/9.6/promote_to_master'
restore_command = 'cp /var/lib/pg-archive/%f "%p"'

Создадим на slave папку для архива WAL (так же, как это было сделано на master)

mkdir /var/lib/pg-archive/
chown postgres /var/lib/pg-archive/
chmod 700 /var/lib/pg-archive/

Теперь синхронизируем архив мастера с архивом slave, чтобы гарантировать успешный запуск:

master> rsync -rahzP /var/lib/pg-archive/ 10.0.0.2:/var/lib/pg-archive/

Поднимаем slave:

slave> service postgresql start

В журнале постгреса можно увидеть что сервис стартовал и готов обслуживать соединения:

2018-01-11 02:11:31 MSK [26781-1] LOG:  database system is ready to accept read only connections
2018-01-11 02:11:31 MSK [26788-1] LOG:  started streaming WAL from primary at 10B/33000000 on timeline 1

Чтобы WAL-сегменты не сожрали весь диск мастера подчистую – их надо периодически чистить. Несложный скрипт в crontab поможет:

10 6 * * * /usr/bin/find /var/lib/pg-archive/ -type f -mtime +7 -exec rm {} \;

В этом примере мы чистим архив от сегментов старше 7 дней, задача выполняется в 6:10 утра по времени сервера.

Диагностика и ремонт

Как проверить, что репликация работает? Проще всего - выяснить текущее положение WAL на мастере и slave:

master$ psql -c "SELECT pg_current_xlog_location()"
 pg_current_xlog_location
--------------------------
 0/2000000
(1 row)
slave$ psql -c "select pg_last_xlog_receive_location()"
 pg_last_xlog_receive_location
-------------------------------
 0/2000000
(1 row)

В норме положение мастера и slave должны быть близки или одинаковы (они будут одинаковы, если между выполнением команды на master и на slave на мастере не было изменений). Если на мастере число растет, а на slave – нет – репликация сломалась. Самый простой способ восстановить репликацию:

#остановим slave:
slave> service postgresql stop
#скопируем архив WAL-сегментов с мастера на salve
master> rsync -rahzP /var/lib/pg-archive/ 10.0.0.2:/var/lib/pg-archive/
#запустим slave обратно:
slave> service postgresql start

Это сработает, если синхронизация была потеряна недавно (в конкретно нашем примере – не более 7 дней назад) и WAL-ы из архива еще не удалены. Если синхронизацию сломали давно – придется синхронизироваться с нуля, как описано в разделах “копируем данные” и “настраиваем slave”. То есть – чистить datadir на slave, копировать состояние, копировать архивы и т.д.

Промотирование (перевод slave в master)

Это нужно в тех случаях, когда slave подменяет мастер на случай аварии. Для того, чтобы промотировать slave – нужно создать файл с именем, описанным в секции trigger_file конфига recovery.conf. В нашем примере это /var/lib/postgresql/9.6/promote_to_master

touch /var/lib/postgresql/9.6/promote_to_master

Содержание файла может быть любым.

После этого:

  • slave перестанет реплицироваться с master
  • slave станет доступен для операций записи
  • slave начнет собственный отсчет WAL. Это значит, что даже если master вернется – смигрировать с него данные на slav e автоматически уже не получится

Выводы

Репликация – мощная, удобная и надежная техника. Репликация в postgresql позволяет легко распределить нагрузку и повысить надежность инсталляции. Эта конструкция работает очень надежно и почти никогда не ломается (привет MySQL!). Разумеется, нужно помнить, что:

  • любая техника требует мониторинга. Проверяйте состояние реплик!
  • репликация не заменяет backup, а только дополняет его.