Автор (http://eax.me/postgresql-replication/, https://www.facebook.com/afiskon?fref=nf) любезно разрешил клонировать его публикацию.
http://itc-life.ru/replikaciya-v-postgresql-9-5/ содержит почти точную копию этого текста, с добавкой об запуске нескольких репликаций с одного мастера.
21 января 2016
Вот многие жалуются, что PostgreSQL сложно масштабировать и нужно быть в нем очень большим специалистом, чтобы настроить обычную master-slave репликацию. По-моему, это все чушь. Не так давно мне потребовалась всего лишь пара часов вдумчивого чтения документации, чтобы во всем разобартся. В этой заметке я постараюсь показать, что с репликацией в PostgreSQL все очень просто. Заодно мы также разберемся, чем потоковая репликация отличается от логической, что такое синхронная и асинхронная репликация, а также как сделать фейловер в случае падения мастера.
Перекрестная ссылка: Вас также может заинтересовать заметка Начало работы с PostgreSQL. В частности, в ней рассказывается, для чего нужны файлы pg_hba.conf и postgresql.conf, как пользоваться утилитой psql, а также как производится резервное копирование и восстановление PostgreSQL. Далее предполагается, что все это вы уже знаете.
Коротко о главном
Когда вы изменяете данные в базе, все изменения пишутся во Write-Ahead Log, или WAL. После записи в WAL СУБД делает системный вызов fsync, благодаря чему данные попадают сразу на диск, а не висят в где-то в кэше файловой системы. Таким образом, если взять и обесточить сервер, при следующей загрузке СУБД прочитает последние записи из WAL и применит к базе данных соответствующие изменения.
Потоковая репликация (streaming replication) в сущности является передачей записей из WAL от мастера к репликам. Писать при этом можно только в мастер, но читать можно как с мастера, так и с реплик. Если с реплики разрешено читать, она называется hot standby, иначе — warm standby. Поскольку во многих приложениях 90% запросов являются запросами на чтение, репликация позволяет масштабировать базу данных горизонтально. Потоковая репликация бывает двух видов — синхронная и асинхронная.
Асинхронная репликация используется в PostgreSQL по умолчанию. При этом методе запрос тут же выполняется на мастере, а соответствующие данные из WAL доезжают до реплик отдельно, в фоне. Недостаток асинхронной репликации заключается в том, что при внезапном падении мастера (например, из-за сгоревшего диска) часть данных будет потеряна, так как они не успели доехать до реплик.
При использовании синхронной репликации данные сначала записываются в WAL как минимум одной реплики, после чего транзакция выполняется уже на мастере. Запросы на запись выполняются медленнее в результате возникающих сетевых задержек. Кроме того, чтобы запросы на запись не встали колом в результате падения одной из реплик, при использовании синхронной репликации рекомендуется использовать по крайней мере две реплики. Зато потерять данные становится намного сложнее.
Заметьте, что синхронная репликация не предотвращает возможности считать с реплики старые данные, так как потоковая репликация — она только про передачу WAL, а не то, что видно в базе с точки зрения пользователя. По крайней мере, так синхронная репликация работает конкретно в PostgreSQL.
В контексте репликации нельзя также не отметить еще один интересный термин. Если одна из реплик в свою очередь является мастером для другой реплики, такую конфигурацию называют каскадной репликацией.
Помимо потоковой репликации в последнее время выделяют еще и так называемую логическую репликацию (logical replication). Реализаций логической репликации в PostgreSQL существует несколько, например, slony и pglogical. Пожалуй, наиболее существенное отличие логической репликации от потоковой заключается в возможности реплицировать часть баз данных и таблиц на одни реплики, а часть — на другие. Платить за это приходится скоростью. И хотя pglogical в плане скорости выглядит многообещающе, на момент написания этих строк это очень молодое, сырое решение. В рамках этой заметки логическая репликация не рассматривается.
Быстрая установка PostgreSQL
При написании этой заметки я использовал Ubuntu 14.04 LTS и PostgreSQL 9.5. Скорее всего, многое из написанного будет также справедливо для других дистрибутивов Linux и версий PostgreSQL. Для эмуляции нескольких машин я использовал LXC контейнеры. Далее предполагается, что машины находятся в локальной сети 10.0.3/24.
Нам понадобится две машины — master и slave. На обеих говорим:
wget --quiet https://www.postgresql.org/media/keys/ACCC4CF8.asc -O - \
| sudo apt-key add -
В /etc/apt/sources.list.d/pgdg.list пишем:
deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main
Далее:
sudo apt-get update
sudo apt-get install postgresql-9.5
Готово, PostgreSQL установлен!
Настройка master
Правим /etc/postgresql/9.5/main/pg_hba.conf:
host replication postgres 10.0.3.0/24 md5
host all postgres 10.0.3.0/24 md5
Первая строчка нужна для работы утилиты pg_basebackup. Без второй не будет работать pg_rewind. Если хотим, чтобы в базу по сети мог ходить не только пользователь postgres, в последней строке можно написать вместо его имени all.
Правим /etc/postgresql/9.5/main/postgresql.conf:
# какие адреса слушать, замените на IP сервера
listen_addresses = '10.0.3.245'
wal_level = hot_standby
# опционально: не дожидаемся fsync на реплике при синхронной репликации
# synchronous_commit = remote_write
# это нужно, чтобы работал pg_rewind
wal_log_hints = on
max_wal_senders = 8
wal_keep_segments = 64
# если хотим синхронную репликацию на одну любую реплику
# synchronous_standby_names = '*'
hot_standby = on
Интересно, что при помощи параметра synchronous_standby_names можно указывать точно, на какие реплики производить синхронную репликацию. Подробности вы найдете в официальной документации.
Далее открываем psql:
sudo -u postgres psql
Меняем пароль пользователя postgres:
ALTER ROLE postgres PASSWORD 'secretpass';
Перезапускаем PostgreSQL:
sudo service postgresql restart
Мастер настроен!
Настройка slave
Останавливаем PostgreSQL:
sudo service postgresql stop
Становимся пользователем postgres:
sudo -u postgres
Под этим пользователем переливаем данные с мастера:
cd /var/lib/postgresql/9.5/
tar -cvzf main_backup-`date +%s`.tgz main
rm -rf main
mkdir main
chmod go-rwx main
pg_basebackup -P -R -X stream -c fast -h 10.0.3.245 -U postgres -D ./main
Последняя команда спросит пароль пользователя postgres, который мы меняли при настройке мастера. Используйте -c fast, чтобы синкнуться как можно быстрее, или -c spread, чтобы минимизировать нагрузку. Еще есть флаг -r, позволяющий ограничить скорость передачи данных (см man).
В /var/lib/postgresql/9.5/main/recovery.conf дописываем:
recovery_target_timeline = 'latest'
Когда у нас упадет мастер и мы запромоутим реплику до мастера, этот параметр позволит тянуть данные с него. Более подробна фича объяснена здесь и в официальной документации.
Также в recovery.conf можно дописать:
recovery_min_apply_delay = 10min
… если вы хотите реплику, отстающую от мастера на заданное количество времени. Это позволит быстро восстановить данные в случае выполненного случайно drop database.
Файлы:
/etc/postgresql/9.5/main/pg_hba.conf
/etc/postgresql/9.5/main/postgresql.conf
… правим аналогично мастеру, только в postgresql.conf нужно указать другой IP. Поскольку реплики могут становиться мастером, конфиги у реплик и мастера одинаковые, вся разница только в recovery.conf.
Запускаем PostgreSQL:
sudo service postgresql start
Поздравляю, репликация настроена!
Проверка репликации
На мастере говорим:
SELECT * FROM pg_stat_replication;
Должны увидеть, что реплика действительно забирает WAL:
-[ RECORD 1 ]----+------------------------------
pid | 5544
usesysid | 10
usename | postgres
application_name | walreceiver
client_addr | 10.0.3.223
client_hostname |
client_port | 45095
backend_start | 2016-01-01 16:42:30.350283+03
backend_xmin |
state | streaming
sent_location | 0/3000220
write_location | 0/3000220
flush_location | 0/3000220
replay_location | 0/30001E8
sync_priority | 0
sync_state | async
На реплике:
sudo less /var/log/postgresql/postgresql-9.5-main.log
Должны увидеть что-то на тему «read only connections»:
LOG: entering standby mode
LOG: redo starts at 0/2000028
LOG: consistent recovery state reached at 0/20000F8
LOG: database system is ready to accept read only connections
LOG: started streaming WAL from primary at 0/3000000 on timeline 1
Еще один способ проверить, что репликация работает — сказать на реплике:
ps wuax | grep receiver
Также можно создать базу данных и пару таблиц на мастере, записать туда какие-то данные, убедиться, что на реплике появляется все то же самое. При попытке писать в реплику должны увидеть:
ERROR: cannot execute INSERT in a read-only transaction
Если вы настроили синхронную репликацию, можете попробовать остановить реплику, попытаться выполнить INSERT на мастере, и убедиться, что он повисает. Еще при использовании синхронной репликации можно контролировать степень «синхронности» на стороне клиента.
Не дожидаемся fsync на реплике:
SET synchronous_commit = 'remote_write';
Пишем только локально, не ждем подтверждения от реплики:
SET synchronous_commit = 'local';
Возвращаем обычное
SET synchronous_commit = 'on';
Также на слейве можно смотреть, как давно было последнее обновление данных с мастера:
sudo -u postgres psql -c "select now()-pg_last_xact_replay_timestamp();"
Пример вывода:
?column?
-----------------
00:00:03.639424
(1 row)
Если запись происходит постоянно (возможно, искусственно, специальным скриптом), можно с большой точностью считать это значение лагом репликации.
Промоутим реплику до мастера
Остановим мастер. Допустим, какой-то мониторинг это дело запалил и теперь нам нужно ASAP сделать реплику новым мастером.
На реплике говорим: sudo -u postgres /usr/lib/postgresql/9.5/bin/pg_ctl promote \ -D /var/lib/postgresql/9.5/main/
В логе увидим:
LOG: received promote request
FATAL: terminating walreceiver process due to administrator command
LOG: redo done at 2/63000DC0
LOG: last completed transaction was at log time 2016-01-01 15:35:42
LOG: selected new timeline ID: 5
LOG: archive recovery complete
LOG: MultiXact member wraparound protections are now enabled
LOG: database system is ready to accept connections
LOG: autovacuum launcher started
При этом в каталоге /var/lib/postgresql/9.5/main файл recovery.conf автоматически будет переименован в recovery.done.
Легко проверить, что в бывшую реплику теперь можно писать. Конечно, если только вы не использовали синхронную репликацию с одной-единственной репликой.
Интересно, что хотя реплику и можно промоутнуть до мастера без перезапуска PostgreSQL, на практике вы, вероятно, все же захотите его перезапустить по следующей причине. Дело в том, что приложение, которое ранее подключилось к этой реплике, так и будет использовать ее в качестве реплики даже после промоута, хотя операции чтения можно было бы размазать по остальным репликам в кластере. Перезапустив PostgreSQL, вы порвете все сетевые соединения, а значит приложению придется подключиться заново, проверить, подключился ли он к мастеру или реплике (запрос SELECT pg_is_in_recovery(); вернет false на мастере и true на репликах), и использовать сетевое соединение соответствующим образом.
Переключение на новый мастер
Переключение остальных реплик на новый мастер, а также восстановление бывшего мастера в качестве реплики происходит одинаково.
Чтобы было чуть меньше путаницы с новым мастером, старым мастером, старой репликой и новой репликой, условимся, что сервера мы называем в соответствии с их текущими ролями. То есть, мастером мы называем новый мастер, бывший репликой до фейловера, а репликой — тот, второй сервер.
В простом и не совсем правильном варианте нужно отредактировать, или создать, если его еще нет, файл /var/lib/postgresql/9.5/main/recovery.conf, указав в нем правильный IP мастера, и сделать sudo service postgresql restart (простой reload не прокатит). Кто-то для того, чтобы не править конфиги и не останавливать СУБД, использует схему с балансировщикам и DNS, но я лично так никогда не делал. В любом случае, этот способ неправильный. Для того, чтобы все хорошо работало во всяких хитрых граничных случаях, реплику следует остановить, сделать pg_rewind, затем запустить реплику.
Утилита pg_rewind находит точку в WAL, начиная с которой WAL мастера и WAL реплики начинают расходиться. Затем она «перематывает» (отсюда и название) WAL реплики на эту точку и накатывает недостающую историю с мастера. Таким образом, реплика и местер всегда приходят к консистентному состоянию. Плюс к этому pg_rewind синхронизирует файлы мастера и реплики намного быстрее, чем pg_basebackup или rsync.
Если вы считаете, что pg_rewind не требуется при использовании синхронной репликации, вот пример маловероятной, но теоретически возможной ситуации. У вас много серверов с PostgreSQL. Сервера в кластере умирают сравнительно часто, поэтому вы решили автоматизировать фейловер. Умирает мастер, запускается фейловер. Среди реплик находится та, что имеет наиболее длинный WAL, на ней делается pg_ctl promote. В этот момент с очень большой задержкой (скажем, 5 секунд — были какие-то сетевые проблемы) на другую реплику прилетает пакет от уже мертвого мастера, и WAL этой реплики становится длиннее WAL нового мастера. Вы не сможете подключить эту реплику к новому мастеру, все сломалось. Если вы хотите, чтобы фейловер работал в том числе и при таких странных граничных случаях, используйте pg_rewind.
Итак, на реплике говорим:
sudo -u postgres /usr/lib/postgresql/9.5/bin/pg_rewind \
-D /var/lib/postgresql/9.5/main/ \
--source-server="host=10.0.3.223 port=5432 user=postgres password=123"
Типичный вывод:
servers diverged at WAL position 2/67002170 on timeline 5
rewinding from last common checkpoint at 2/67002100 on timeline 5
Done!
Перемещаем и правим recovery.conf:
sudo mv /var/lib/postgresql/9.5/main/recovery.done \
/var/lib/postgresql/9.5/main/recovery.conf
sudo vim /var/lib/postgresql/9.5/main/recovery.conf
Проверяем IP мастера и наличие строчки:
recovery_target_timeline = 'latest'
Запускаем реплику, смотрим в логи. Там обязательно должно быть:
LOG: database system is ready to accept read only connections
Значит PostgreSQL работает в качестве реплики.
Если вдруг видим что-то вроде:
ERROR: requested WAL segment 000000020000000005 has already been removed
… значит реплика слишком отстала от мастера, и нужно перенести файлы с мастера при помощи pg_basebackup, как было описано в начале этой статьи.
Заключение
Очевидно, что по той же схеме можно настраивать каскадную репликацию, репликацию между датацентрами и так далее.
В контексте репликации и фейловера PostgreSQL хотелось бы также отметить следующее:
Вам скорее всего также понадобятся (1) мониторинг, например, Nagios или Zabbix, (2) service discovery, через который все сервисы будут находить текущиих мастеров и слейвов, например, Consul, etcd, или в крайнем случае ZooKeeper, (3) агрегация логов, например, ELK-стэк и (4) метрики, скажем, Graphite и StatsD;
При наличии большого количества серверов с PostgreSQL возникает задача автоматического фейловера, которая на практике часто решается тупо написанием наколеночных скриптов на Python;
Если у вас небольшой кластер и машины не дохнут каждый день, лучше использовать ручной фейловер, а не автоматический;
На практике неплохо работает автофейловер, в котором решение о выборе нового мастера принимается за 5 секунд, и делается не более одного фейловера в час — чаще без участия человека никто ничего не фейловит;
При фейловере следует учитывать текущее состояние реплики и промоутить реплику с наиболее длинным WAL, иначе вы потеряете данные;
Если мастер падает, он должен падать совсем, чтобы не было ситуации, когда вы уже начали промоутить реплику, тут поднимается старый мастер, и все данные в кластере разъезжаются;
Не следует также упускать из виду важность равномерного распределения нагрузки в кластере при фейловере — порой это даже важнее, чем не терять никаких данных;
На ваш наколеночный автофейловер нужны тесты, очень много автоматических тестов, проверяющих как PostgreSQL, так и мониторинг с service discovery, а также работу всего кластера при нетсплитах (см заметку про iptables), медленной сети и так далее;
Ко всему написанному вы, вероятно, захотите еще и шардирование с автоматическим решардингом и распределенными транзакциями, но это уже очень другая история;
К пониманию многих пунктов из этого списка я пришел благодаря консультации со стороны товарища secwall, за что ему большое спасибо!
А пользуетесь ли вы чем-то из упомянутого в этой заметке и если да, то чем и как именно?
Leave a Reply
You must be logged in to post a comment.