Пошук уроків, статей та іншого контенту
Підходи до побудови PostgreSQL-систем, здатних працювати без тривалих простоїв у разі відмови компонентів
High Availability (HA) — це здатність системи продовжувати обробляти запити або швидко відновлювати роботу після відмови окремих компонентів.
Для PostgreSQL зазвичай прагнуть мінімізувати два показники:
RTO (Recovery Time Objective) — максимально допустимий час відновлення роботи;
RPO (Recovery Point Objective) — максимально допустимий обсяг даних, який можна втратити.
Наприклад:
RTO 30 секунд означає, що база має стати доступною не пізніше ніж через 30 секунд після відмови;
RPO 0 означає, що після перемикання не повинно бути втрати підтверджених транзакцій.
Повністю усунути простої та втрату даних неможливо без компромісів. Висока доступність — це поєднання:
реплікації даних;
автоматичного або контрольованого перемикання;
маршрутизації клієнтів до активного вузла;
моніторингу;
процедур відновлення після відмови.
Реплікація сама по собі ще не є повноцінною HA-системою. Якщо standby-сервер існує, але клієнти не можуть автоматично підключитися до нього після відмови primary, система все ще має тривалий простій.
Найпоширеніший підхід — primary/standby:
primary приймає записи та більшість запитів;
standby отримує WAL primary і відтворює зміни;
після відмови primary standby може бути підвищений до нового primary.
Спрощена схема:
Клієнти
|
v
Маршрутизатор або сервісне ім'я
|
+--> Primary PostgreSQL
|
+--> Standby PostgreSQLStandby може використовуватися для:
перемикання після відмови primary;
читання запитів;
резервування;
тестування процедур відновлення.
Однак читання зі standby має особливості:
дані можуть бути неактуальними через replication lag;
під час recovery деякі запити можуть конфліктувати з відтворенням WAL;
standby не можна використовувати для запису, поки його не підвищено до primary.
PostgreSQL підтримує фізичну реплікацію на рівні WAL. Primary передає standby записи журналу Write-Ahead Log, а standby відтворює їх у себе.
Фізична реплікація копіює кластер цілком. Вона не реплікує окремі таблиці або окремі SQL-команди як логічна реплікація.
Типовий процес:
primary генерує WAL;
процес walsender передає WAL;
standby приймає WAL через walreceiver;
standby записує WAL на диск;
процес recovery відтворює зміни.
Для налаштування primary зазвичай потрібні параметри:
# postgresql.conf
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
wal_keep_size = 1GBЗначення wal_keep_size — це лише мінімальний обсяг WAL, який primary намагається зберігати локально. Для надійного утримання WAL конкретного standby зазвичай використовують replication slot.
У pg_hba.conf потрібно дозволити підключення користувача реплікації з адреси standby:
# Дозвіл на фізичну реплікацію від standby
host replication replicator 10.0.0.12/32 scram-sha-256Користувач реплікації:
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'strong-password';Пароль не варто зберігати безпосередньо в командному рядку або в конфігурації з відкритими правами доступу. Для підключення standby зазвичай використовують файл .pgpass з правами 0600.
Для PostgreSQL 12 і новіших standby можна підготувати за допомогою pg_basebackup.
Приклад команди запускається на сервері standby:
pg_basebackup \
--host=10.0.0.11 \
--port=5432 \
--username=replicator \
--pgdata=/var/lib/postgresql/data \
--progress \
--verbose \
--wal-method=stream \
--write-recovery-conf \
--slot=standby_1Параметр --write-recovery-conf створює конфігурацію підключення до primary і файл standby.signal.
Replication slot потрібно створити на primary до запуску резервної копії:
SELECT pg_create_physical_replication_slot('standby_1');У результаті standby підключатиметься до primary та отримуватиме WAL через цей slot.
У нових версіях PostgreSQL режим standby визначається наявністю файлу:
standby.signalПісля запуску standby він не приймає записи й перебуває в режимі recovery.
На primary корисно переглядати підключені standby:
SELECT
application_name,
client_addr,
state,
sync_state,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn,
write_lag,
flush_lag,
replay_lag
FROM pg_stat_replication;Основні значення:
state = streaming — standby отримує WAL у потоковому режимі;
sync_state = async — асинхронна реплікація;
sync_state = sync — синхронна реплікація;
replay_lag — оцінка затримки відтворення WAL;
replay_lsn — остання позиція WAL, відтворена standby.
На standby можна виконати:
SELECT
pg_is_in_recovery() AS is_standby,
pg_last_wal_receive_lsn() AS received_lsn,
pg_last_wal_replay_lsn() AS replayed_lsn,
now() - pg_last_xact_replay_timestamp() AS replay_delay;pg_is_in_recovery() повертає true, якщо сервер працює як standby.
Значення pg_last_xact_replay_timestamp() може бути NULL, якщо standby ще не відтворив жодної транзакції. Крім того, великий часовий інтервал не завжди означає проблему: якщо на primary давно не було транзакцій, остання транзакція природно має старий timestamp.
За асинхронної реплікації primary не чекає, доки standby підтвердить отримання або запис WAL.
Переваги:
мінімальний вплив на latency записів;
primary може продовжити роботу, навіть якщо standby тимчасово недоступний;
зручно для віддалених регіонів і великих відстаней.
Недолік — можливість втрати останніх підтверджених транзакцій під час аварійного перемикання. Якщо primary вийшов з ладу до передачі WAL на standby, standby не матиме цих змін.
Асинхронна реплікація не гарантує RPO 0.
За синхронної реплікації primary очікує підтвердження від одного або кількох standby перед тим, як вважати транзакцію підтвердженою.
Наприклад:
synchronous_commit = on
synchronous_standby_names = 'FIRST 1 (standby_1, standby_2)'У такому режимі primary очікує синхронний standby відповідно до списку.
PostgreSQL підтримує різні рівні очікування через synchronous_commit:
on — очікування підтвердження запису WAL на синхронному standby;
remote_write — WAL записано операційною системою standby;
remote_apply — WAL застосовано на standby;
local — primary не очікує standby;
off — коміт не чекає навіть локального flush WAL, хоча PostgreSQL все одно зберігає узгодженість бази після власного збою.
Для HA найчастіше використовують on. Якщо клієнти повинні бачити на standby вже застосовані дані, може знадобитися remote_apply, але це збільшує latency.
Переваги синхронної реплікації:
менший ризик втрати підтверджених транзакцій;
можливість досягти RPO, близького до нуля.
Недоліки:
збільшення latency комітів;
недоступність або затримка синхронного standby можуть блокувати записи;
відмова мережі між primary і standby без правильної політики може вплинути на доступність.
HA-дизайн має чітко визначити пріоритет:
consistency first — краще блокувати записи, ніж ризикувати втратою даних;
availability first — краще продовжити записи асинхронно, приймаючи можливий RPO більше нуля.
Replication slot утримує WAL на primary, доки standby не підтвердить його отримання.
Переглянути slots:
SELECT
slot_name,
slot_type,
active,
restart_lsn,
confirmed_flush_lsn,
wal_status
FROM pg_replication_slots;Перевага slot — standby не втратить потрібний WAL через звичайне очищення старих сегментів.
Небезпека — якщо standby назавжди відключений, його slot може безмежно утримувати WAL. Це поступово заповнить диск primary.
Тому потрібно:
моніторити розмір WAL, утримуваного кожним slot;
видаляти непотрібні slots;
перевіряти стан standby;
не створювати slots без контролю їхнього життєвого циклу.
Для захисту від необмеженого утримання WAL можна налаштовувати обмеження на кількість WAL, який slot має право утримувати, але таке обмеження потрібно узгодити з допустимою тривалістю відключення standby.
Failover — це перемикання з несправного primary на standby.
Саме по собі підвищення standby виконується командою:
pg_ctl -D /var/lib/postgresql/data promoteАбо через SQL:
SELECT pg_promote();Після promotion:
сервер перестає працювати як standby;
файл standby.signal більше не визначає його як recovery-сервер;
сервер починає приймати записи;
формується нова timeline.
Проте одного pg_promote() недостатньо для повного failover. Потрібно також:
переконатися, що старий primary справді недоступний;
запобігти його поверненню в мережу як другого primary;
перенаправити клієнтів на новий primary;
перевірити стан застосунку;
перебудувати старий primary як standby нового primary.
Якщо старий primary все ще доступний і його теж підвищити або залишити приймати записи, виникає split-brain.
У split-brain дві сторони вважають себе primary та приймають незалежні записи. Це може призвести до:
конфліктів даних;
різних історій WAL;
неможливості простого об'єднання кластерів;
втрати частини транзакцій;
складного ручного відновлення.
Перед автоматичним failover потрібен механізм fencing — гарантоване вимкнення, ізоляція або блокування старого primary. У середовищах з керуванням інфраструктурою для цього можуть використовуватися power fencing, cloud-операції або інші механізми ізоляції вузла.
Монітор не повинен підвищувати standby лише тому, що primary тимчасово не відповів на один запит. Потрібні:
кілька перевірок;
перевірка мережевої доступності;
кворум між моніторами;
захист від мережевих розділень;
зрозуміла політика fencing.
Після failover старий primary не можна просто запустити та підключити до нового primary. У нього може бути інша timeline.
Один із підходів — повторно створити його як standby через новий pg_basebackup. Для великих баз це може бути довго.
Якщо можливо, використовують pg_rewind. Він синхронізує кластер, який відстав або пішов власною timeline, з новим primary. Для цього зазвичай потрібно, щоб у кластері були доступні необхідні WAL або була налаштована WAL-архівація, а також щоб primary мав достатню історію checkpoint.
Загальна послідовність:
зупинити старий primary;
переконатися, що він більше не приймає клієнтські записи;
виконати pg_rewind у напрямку нового primary або заново створити базову копію;
налаштувати primary_conninfo;
створити standby.signal;
запустити сервер;
переконатися, що він підключився як standby.
Після кожного failover потрібно відновлювати симетрію системи: один primary і щонайменше один актуальний standby.
Клієнти не повинні жорстко підключатися до IP-адреси конкретного primary, якщо очікується автоматичне перемикання.
Зазвичай використовують:
стабільне DNS-ім'я;
віртуальну IP-адресу;
балансувальник;
сервісне ім'я в оркестрованому середовищі;
окремі endpoints для запису та читання.
Маршрутизатор має відрізняти:
вузол, який приймає записи;
standby, який перебуває в recovery;
вузол, що недоступний або ще не готовий приймати трафік.
Перевірка TCP-порту недостатня. PostgreSQL може приймати TCP-з'єднання, але:
ще перебувати в recovery;
мати критичну затримку реплікації;
бути ізольованим від решти системи;
не мати достатньо вільного дискового простору.
Перевірка ролі вузла:
SELECT pg_is_in_recovery();Для write endpoint значення має бути false. Для read endpoint допустимі standby, але політика застосунку має враховувати відставання даних.
Після failover довгоживучі connection pools можуть продовжувати використовувати старі з'єднання. Застосунок або пул підключень повинен уміти:
перепідключатися;
обробляти розірвані транзакції;
повторювати безпечні операції;
не повторювати неконтрольовано транзакції з побічними ефектами.
Standby можна використовувати для read-only запитів, щоб зменшити навантаження на primary.
Але реплікація може відставати. Тому запит, який одразу після запису читає дані з standby, може їх не побачити.
Це створює проблему read-after-write consistency.
Можливі рішення:
після запису читати з primary;
прикріплювати пов'язаний read-запит до primary протягом потрібного часу;
перевіряти позицію WAL перед читанням зі standby;
прийняти eventual consistency як властивість системи.
Збільшення кількості standby не гарантує кращу доступність. Кожен додатковий вузол збільшує:
мережевий трафік;
складність моніторингу;
кількість можливих станів під час failover;
вимоги до дискового простору та WAL.
За наявності кількох standby можна вимагати підтвердження не від конкретного вузла, а від кворуму.
Наприклад:
synchronous_standby_names = 'ANY 2 (standby_1, standby_2, standby_3)'Це означає, що транзакція має отримати підтвердження від будь-яких двох standby зі списку.
Кворумна схема може пережити відмову одного standby без негайного переходу до асинхронного режиму. Проте вона не усуває вимоги до:
низької затримки мережі;
моніторингу доступних синхронних реплік;
коректного керування конфігурацією;
перевірки, чи справді обрані standby перебувають у потрібному стані.
Моніторинг має показувати не лише доступність PostgreSQL, а й здатність системи виконати failover.
На primary потрібно контролювати:
наявність підключеного standby;
state та sync_state;
lag у байтах і часі;
стан replication slots;
швидкість генерації WAL;
вільне місце на диску;
помилки walsender;
блокування та довгі транзакції.
На standby потрібно контролювати:
час останнього отримання WAL;
час останнього replay;
pg_is_in_recovery();
помилки walreceiver;
затримку застосування;
вільне місце на диску;
конфлікти recovery.
Важливо розрізняти:
standby підключений, але не встигає застосовувати WAL;
standby не отримує WAL;
standby отримує WAL, але не може його записати;
standby застосовує WAL, але клієнти все одно спрямовані на несправний primary.
Реплікація не замінює резервні копії.
Помилки користувача, наприклад:
DROP TABLE important_data;будуть репліковані на standby. Якщо всі standby синхронні, помилка може миттєво з'явитися на всіх вузлах.
Резервні копії та WAL-архівація потрібні для:
відновлення після логічної помилки;
point-in-time recovery;
захисту від пошкодження або видалення даних;
відновлення після втрати всіх вузлів;
підготовки нового standby.
Отже:
HA зменшує простій після відмови;
резервні копії зменшують ризик незворотної втрати даних;
Disaster Recovery охоплює відновлення після масштабної аварії, наприклад втрати всього датацентру.
Перед вибором архітектури потрібно зафіксувати вимоги:
Який допустимий RTO?
Який допустимий RPO?
Чи прийнятна втрата останніх транзакцій?
Чи є другий датацентр або регіон?
Хто ініціює failover?
Як відбувається fencing?
Як клієнти знаходять активний primary?
Як старий primary повертається в кластер?
Як перевіряється реплікація?
Коли та як тестується аварійний сценарій?
Приклад компромісів:
асинхронна реплікація в іншому регіоні дає нижчу latency, але може мати ненульовий RPO;
синхронна реплікація в тому самому датацентрі може забезпечити кращий RPO, але відмова мережі здатна блокувати записи;
автоматичний failover зменшує RTO, але вимагає надійного fencing і захисту від split-brain;
ручний failover простіший для контролю, але може збільшити простій.
Практичний runbook має бути детермінованим:
Моніторинг виявляє проблему primary.
Перевіряється, чи це справжня відмова, а не короткочасна мережева проблема.
Старий primary ізолюється або вимикається.
Обирається standby з найменшим replication lag.
Виконується promotion.
Перевіряється роль нового primary.
Write endpoint перемикається на новий primary.
Застосунок перевіряє нові з'єднання та транзакції.
Старий primary перебудовується як standby.
Перевіряються метрики та резервні копії.
Кожен крок має бути перевірений у тестовому середовищі. Документ, який ніколи не виконували на практиці, не є надійною процедурою відновлення.
Standby без маршрутизації клієнтів, моніторингу та процедури promotion не забезпечує швидке відновлення.
Відключений standby може утримувати WAL доти, доки на primary не закінчиться диск.
Це одна з найнебезпечніших помилок: вона може призвести до split-brain і розбіжності даних.
Перевіряйте роль через pg_is_in_recovery(), стан реплікації та актуальність WAL.
Після failover частина останніх транзакцій може бути відсутня на асинхронному standby.
Фізична streaming replication у типовій primary/standby-архітектурі не є multi-primary. Standby не призначений для запису.
Старий primary не можна безпечно повернути в кластер простим запуском. Його потрібно синхронізувати з новою timeline.
Відмова, яку ніколи не симулювали, часто виявляє проблеми з DNS, connection pool, правами доступу, слотами або автоматизацією.
PostgreSQL HA зазвичай будується на схемі primary/standby з фізичною streaming replication.
Асинхронна реплікація зменшує latency, але може втратити останні підтверджені транзакції.
Синхронна реплікація зменшує RPO, але може збільшити latency та вплинути на доступність записів.
Replication slots захищають standby від втрати WAL, але можуть заповнити диск.
Failover включає не лише pg_promote(), а й fencing, маршрутизацію клієнтів і відновлення старого primary.
Split-brain потрібно запобігати на рівні інфраструктури та автоматизації.
Standby не замінює резервні копії.
Надійність HA визначається не кількістю серверів, а перевіреною процедурою виявлення відмови, перемикання та відновлення.