Введение в High Availability и архитектуру Patroni: фундамент отказоустойчивых систем

Курс закладывает концептуальный фундамент построения высокодоступных систем на базе PostgreSQL. Вы изучите принципы работы репликации, роль распределенного консенсуса и место Patroni в современном стеке автоматизации баз данных.

1. Концепция High Availability и анатомия отказов в распределенных системах

Концепция High Availability и анатомия отказов в распределенных системах

В 2017 году опечатка одного инженера при вводе команды в консоли привела к отключению биллинга, хранилищ и десятков тысяч сайтов, зависящих от Amazon S3, на четыре часа. Убытки компаний составили сотни миллионов долларов. Этот инцидент в очередной раз доказал суровое правило IT: любая система рано или поздно упадет. Вопрос лишь в том, заметит ли это падение конечный пользователь. Когда речь идет о базах данных, хранящих критическую информацию (транзакции, профили, балансы), мы не можем полагаться на надежность одного, пусть и очень мощного, сервера. Нам нужна архитектура, которая воспринимает отказ оборудования не как катастрофу, а как штатную ситуацию.

Иллюзия абсолютной надежности и метрики доступности

High Availability (HA, высокая доступность) — это свойство системы, гарантирующее определенный уровень операционной производительности (обычно время безотказной работы) в течение заданного периода.

Доступность измеряется в процентах и вычисляется по формуле:

Где: * — итоговая доступность системы (Availability). * — суммарное время, когда система корректно обрабатывала запросы пользователей. * — суммарное время простоя, когда система была недоступна или возвращала ошибки.

В индустрии уровень доступности принято измерять «девятками». Две девятки (99%) означают, что система может лежать 3.65 дня в году. Для современного бизнеса это неприемлемо. Три девятки (99.9%) допускают простой около 8.7 часов в год. Это стандарт для многих некритичных внутренних сервисов. Четыре девятки (99.99%) — это всего 52 минуты простоя в год. Пять девяток (99.999%) — около 5 минут в год. Это уровень телекома, банковских процессингов и критической инфраструктуры.

Чтобы достичь хотя бы четырех девяток, недостаточно просто делать резервные копии (бэкапы). Бэкап защищает от потери данных, но восстановление базы из бэкапа размером в несколько терабайт займет часы. В контексте HA нас интересуют две ключевые бизнес-метрики:

  • RPO (Recovery Point Objective) — максимальный объем данных, который бизнес готов потерять при аварии. Измеряется во времени (например, «мы готовы потерять транзакции за последние 5 секунд»).
  • RTO (Recovery Time Objective) — максимальное время, необходимое на восстановление работоспособности системы.
  • High Availability архитектура строится для того, чтобы свести RTO к секундам, а RPO — к нулю или миллисекундам.

    Анатомия отказов: где ломаются системы

    Главный враг высокой доступности — это SPOF (Single Point of Failure, единая точка отказа). Это любой компонент системы, выход которого из строя приводит к остановке всего сервиса. Если у вас работает один сервер PostgreSQL, он является SPOF.

    Отказы в распределенных системах делятся на несколько категорий: * Аппаратные сбои: сгорел блок питания, вышел из строя SSD-накопитель, деградировала оперативная память. * Программные ошибки: падение процесса базы данных из-за бага (например, утечки памяти) или исчерпания ресурсов (OOM Killer убил процесс PostgreSQL). * Сетевые аномалии: самый коварный тип отказов. Сеть может не просто пропасть, она может начать терять пакеты, увеличивать задержку (latency) или разделить инфраструктуру на две изолированные части. * Человеческий фактор: случайное удаление директории с данными, применение некорректной конфигурации.

    Чтобы устранить SPOF, инженеры применяют избыточность (redundancy). Если один сервер может сгореть, нужно поставить два. Но как сделать так, чтобы два сервера работали с одними и теми же данными?

    Репликация: технологический фундамент избыточности

    Для обеспечения избыточности в базах данных используется репликация — процесс непрерывного копирования данных с одного узла на другие. В классической архитектуре выделяют две роли серверов:

  • Primary (Master, Лидер): узел, который принимает как запросы на чтение, так и запросы на запись (INSERT, UPDATE, DELETE).
  • Replica (Standby, Ведомый): узел, который является точной копией Primary. Он постоянно получает изменения от лидера и применяет их у себя. Обычно реплики доступны только для чтения (Read-Only).
  • Репликация работает не путем копирования файлов базы данных, а через передачу журнала транзакций (в PostgreSQL он называется WAL — Write-Ahead Log). Когда приложение пишет данные в Primary, база сначала записывает информацию об этом изменении в WAL. Этот же поток WAL по сети отправляется на реплику. Реплика читает WAL и воспроизводит те же самые операции на своем диске.

    Если Primary-сервер внезапно сгорает, у нас остается Replica, на которой есть (почти) все те же самые данные. Мы можем перенаправить приложение на реплику, и работа продолжится. Этот процесс переключения называется Failover.

    Кошмар распределенных систем: Split-Brain

    Кажется, что проблема решена: давайте напишем скрипт, который будет пинговать Primary-сервер каждую секунду. Если Primary не отвечает, скрипт повышает реплику до статуса нового Primary и перенаправляет на нее трафик.

    В реальности такой скрипт приведет к катастрофе, которая называется Split-Brain (расщепление мозга).

    !Схема возникновения Split-Brain

    Представьте ситуацию: Primary-сервер физически исправен, база данных работает отлично, но сетевой коммутатор между Primary и Replica вышел из строя. Скрипт мониторинга, находящийся в одной сети с репликой, не может достучаться до Primary. Он решает, что лидер мертв, и повышает реплику до нового Primary. Однако старый Primary все еще жив и доступен для части серверов приложений.

    В этот момент в кластере появляются два Primary-сервера. Оба начинают принимать запросы на запись от разных частей приложения. Данные начинают расходиться: пользователь A регистрируется на старом лидере, а пользователь B — на новом. Когда сеть восстановится, объединить эти данные автоматически будет невозможно. База данных необратимо повреждена логически.

    Split-Brain доказывает фундаментальное правило: узел не может сам достоверно определить, изолирован ли он от сети, или упали все остальные. Ему нужен внешний арбитр.

    Эволюция: от ручных скриптов к Patroni и etcd

    Из-за риска Split-Brain долгое время Failover выполняли вручную. Администратор получал алерт посреди ночи, подключался к серверам, лично убеждался, что старый Primary действительно мертв (например, принудительно выключал его по питанию через IPMI), и только после этого повышал реплику. RTO при таком подходе составлял от 15 минут до нескольких часов.

    Для автоматического и безопасного Failover индустрия пришла к использованию специализированных кластерных менеджеров и систем распределенного консенсуса. В экосистеме PostgreSQL стандартом де-факто стала связка Patroni и etcd.

    !Архитектура кластера Patroni + etcd

    DCS (etcd): Хранитель истины

    Чтобы избежать Split-Brain, системе нужен сторонний наблюдатель, который будет хранить "единую версию правды" о том, кто сейчас является лидером. Эту роль выполняет DCS (Distributed Configuration Store). Самый популярный DCS — это etcd (разработка команды CoreOS, ныне стандарт в Kubernetes).

    etcd — это высоконадежное хранилище пар "ключ-значение". Оно само по себе разворачивается в виде кластера (обычно из 3 или 5 узлов), чтобы не быть SPOF. etcd использует математический алгоритм консенсуса, который гарантирует, что данные в нем всегда консистентны, даже если часть узлов сети выйдет из строя.

    В контексте нашей базы данных etcd хранит критически важный ключ — Leader Lock (блокировку лидера). В этом ключе записано имя текущего Primary-сервера PostgreSQL. Правило жесткое: серверов PostgreSQL может быть много, но Primary может быть только тот, чье имя сейчас записано в Leader Lock внутри etcd.

    Patroni: Интеллектуальный агент

    PostgreSQL сам по себе ничего не знает про etcd и не умеет с ним общаться. Ему нужен "переводчик" и "менеджер". Эту роль берет на себя Patroni — демон (фоновый процесс), написанный на Python.

    Patroni устанавливается на каждый сервер рядом с PostgreSQL. Его задачи:

  • Управлять локальным процессом PostgreSQL (запускать, останавливать, менять конфигурацию).
  • Постоянно общаться с etcd.
  • Оценивать состояние локальной базы (насколько она отстает от лидера).
  • Жизненный цикл отказа: как работает автоматический Failover

    Связка Patroni и etcd превращает кластер в самовосстанавливающийся механизм. Механика удержания лидерства построена на концепции TTL (Time To Live — время жизни).

    Когда Patroni-агент на Primary-сервере захватывает Leader Lock в etcd, этот замок выдается не навсегда, а, например, на 30 секунд. Чтобы оставаться лидером, Patroni должен каждые 10 секунд отправлять в etcd запрос: «Я жив, продли мой замок еще на 30 секунд». Это называется heartbeat (сердцебиение).

    !Процесс автоматического Failover

    Что происходит при аварии:

  • Отказ: Сервер Primary внезапно выключается (сгорел блок питания).
  • Истечение TTL: Patroni на упавшем сервере перестает отправлять heartbeat. Через 30 секунд Leader Lock в etcd исчезает (истекает TTL). Замок становится свободным.
  • Гонка реплик: Patroni-агенты на всех оставшихся репликах постоянно мониторят etcd. Они видят, что замок освободился.
  • Выбор лучшего: Прежде чем попытаться захватить замок, агенты общаются между собой (через тот же etcd), чтобы выяснить, кто из них имеет самые свежие данные (кто успел скачать больше WAL-журналов до падения лидера).
  • Захват лидерства: Самая актуальная реплика отправляет запрос в etcd на захват Leader Lock. etcd гарантирует, что замок получит только один узел.
  • Промоушен (Promotion): Агент Patroni, получивший замок, отдает команду своему локальному PostgreSQL: «Перестань быть репликой, откройся на запись, теперь ты Primary».
  • Переконфигурация: Остальные реплики видят в etcd нового лидера и автоматически перенастраиваются на скачивание WAL-журналов с него.
  • Весь этот процесс занимает от 10 до 40 секунд. Инженеры могут спать спокойно — система восстановила свою работоспособность сама. Риск Split-Brain исключен: даже если старый Primary был просто изолирован сетью, он не сможет продлить свой замок в etcd. Когда сеть восстановится, старый Patroni увидит, что замок занят другим узлом, и принудительно понизит свой локальный PostgreSQL до состояния реплики (или остановит его), защищая данные от расхождения.

    Переход от одиночных инстансов баз данных к управляемым кластерам — это смена парадигмы. Мы перестаем пытаться сделать один сервер бессмертным. Вместо этого мы создаем архитектуру, в которой смерть любого компонента является ожидаемым событием, автоматически обрабатываемым программными агентами. Репликация дает нам физическую возможность не потерять данные, а распределенный консенсус дает уверенность, что переключение ролей пройдет безопасно.

    2. Механизмы репликации PostgreSQL как технологический фундамент кластеризации

    Если в дата-центре происходит внезапное отключение питания, единственное, что отделяет бизнес от безвозвратной потери данных — это поток байтов, отправленный по сети за несколько миллисекунд до сбоя. Кластерные менеджеры, распределенные хранилища конфигураций и алгоритмы консенсуса бессильны, если сама база данных не умеет надежно, быстро и непрерывно передавать свои изменения на резервные узлы. В основе любой отказоустойчивой архитектуры PostgreSQL лежит встроенный механизм репликации, физически копирующий состояние системы.

    WAL и LSN: система координат базы данных

    Фундаментом репликации PostgreSQL является журнал предзаписи — WAL (Write-Ahead Log). Любое изменение данных сначала записывается в этот журнал, и только потом применяется к файлам самих таблиц на диске. Это гарантирует, что при сбое сервера базу можно восстановить, последовательно «проиграв» записи из WAL.

    В контексте репликации WAL выполняет роль транспортного протокола. Primary-узел не отправляет на Replica-узлы SQL-запросы (например, UPDATE users SET balance = 100). Вместо этого он отправляет бинарные изменения блоков данных на диске.

    Чтобы узлы понимали, какие именно данные передаются, используется строгая система координат — LSN (Log Sequence Number). LSN — это 64-битное целое число, указывающее смещение (в байтах) внутри потока WAL с момента создания кластера базы данных. Обычно LSN записывается в виде двух шестнадцатеричных чисел, разделенных слешем, например: 16/B374D848.

    Математически процесс синхронизации можно выразить через разницу этих указателей. Если LSN на первичном узле равен , а LSN, примененный на реплике, равен , то объем отставания (lag) в байтах вычисляется как:

    Чем больше значение , тем больше данных находится в зоне риска при внезапном отказе Primary-узла. Управление этим отставанием — главная задача при проектировании HA-архитектуры.

    !Архитектура потоковой физической репликации PostgreSQL

    Физическая против логической репликации

    PostgreSQL поддерживает два принципиально разных подхода к репликации: логическую и физическую. Для построения отказоустойчивых кластеров (в том числе под управлением Patroni) используется исключительно физическая (потоковая) репликация.

    Логическая репликация работает на уровне объектов базы данных. Она перехватывает изменения, декодирует их обратно в формат, близкий к SQL, и отправляет подписчику. Это позволяет реплицировать отдельные таблицы, отправлять данные в базы других мажорных версий или даже изменять структуру данных на лету. Однако логическая репликация не копирует служебные данные (например, последовательности sequences или системные каталоги целиком) и требует больше ресурсов процессора на декодирование.

    Физическая потоковая репликация (Streaming Replication) работает на уровне дисковых блоков. Replica получает точную побайтовую копию Primary-узла. На реплике нельзя создать дополнительные таблицы или индексы — она находится в режиме Read-Only и является абсолютным зеркалом мастера. Именно эта идентичность позволяет реплике мгновенно стать новым Primary при сбое, так как структура файлов на диске полностью готова к приему новых транзакций.

    Асинхронная репликация: скорость ценой риска

    По умолчанию PostgreSQL использует асинхронную репликацию. Механика работы следующая:

  • Клиент отправляет транзакцию на Primary.
  • Primary записывает изменения в свой локальный WAL.
  • Primary немедленно отвечает клиенту: «Транзакция успешно зафиксирована» (Commit OK).
  • В фоновом режиме процесс walsender отправляет новые блоки WAL по сети на Replica.
  • Процесс walreceiver на реплике принимает данные, записывает их в локальный WAL и применяет к страницам данных.
  • Главное преимущество асинхронного режима — нулевое влияние на производительность записи. Primary не ждет ответа от сети или дисков реплики. База данных работает так же быстро, как если бы репликации не было вообще.

    Критический недостаток — окно потери данных. Поскольку клиент получает подтверждение до того, как данные покинули Primary, внезапное уничтожение Primary-сервера приведет к потере последних транзакций. Текущий превращается в реальные потерянные данные. В терминах отказоустойчивости это означает, что RPO (Recovery Point Objective) всегда строго больше нуля: .

    Этот режим подходит для систем, где кратковременная потеря части данных допустима. Например, в высоконагруженном e-commerce каталоге потеря нескольких логов просмотров товаров при падении сервера не является критичной для бизнеса, тогда как задержка при записи каждого клика обрушит производительность.

    Синхронная репликация: гарантия ценой задержки

    Для финансовых систем, биллингов и критичных реестров потеря подтвержденной транзакции недопустима (). В этом случае применяется синхронная репликация.

    При синхронной фиксации Primary-узел, записав WAL локально, приостанавливает сессию клиента и ждет подтверждения от Replica. Только после того, как реплика сообщит о сохранении данных, клиент получит ответ об успешном завершении транзакции.

    !Таймлайн транзакции: асинхронный и синхронные режимы

    PostgreSQL позволяет гранулярно настраивать, что именно считается «сохранением» на реплике, через параметр synchronous_commit. Существует несколько уровней строгости:

  • remote_write: Реплика подтверждает транзакцию, как только данные получены по сети и переданы операционной системе (в кэш). Это защищает от падения самого процесса PostgreSQL на реплике, но данные могут быть потеряны при внезапном отключении питания сервера-реплики до сброса кэша на диск.
  • on (синхронный режим по умолчанию): Реплика подтверждает транзакцию только после того, как данные физически записаны (fsync) в файл WAL на ее диске. Это гарантирует сохранность данных при отключении питания реплики.
  • remote_apply: Самый строгий уровень. Реплика подтверждает транзакцию только после того, как данные не просто записаны в WAL, но и применены к файлам таблиц. Это гарантирует, что сразу после фиксации транзакции на Primary, любой клиент, читающий с Replica, гарантированно увидит новые данные (отсутствие эффекта «грязного» или устаревшего чтения).
  • Расплата за синхронность — задержка (latency). Время выполнения каждой транзакции на запись увеличивается на время сетевого пути туда и обратно (RTT) плюс время записи на диск реплики. Если реплика недоступна или сеть разорвана, транзакции на Primary зависнут в ожидании подтверждения. Иными словами, система жертвует доступностью (Availability) ради консистентности (Consistency).

    Кворумная фиксация

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

    Администратор может указать правило, например, ANY 2 (node_B, node_C, node_D). Это означает, что Primary будет ждать подтверждения от любых двух реплик из списка. Если node_B недоступна, транзакция успешно зафиксируется, как только ответят node_C и node_D. Это позволяет сочетать нулевую потерю данных с высокой доступностью кластера на запись.

    Слоты репликации: страховка от амнезии

    В распределенной системе сетевые разрывы неизбежны. Если Replica теряет связь с Primary на несколько минут, она перестает получать WAL. В это время Primary продолжает обслуживать клиентов и генерировать новые WAL-файлы.

    По умолчанию PostgreSQL хранит старые WAL-файлы только до тех пор, пока они нужны ему самому для восстановления после сбоя, или в пределах лимита, заданного параметром wal_keep_size. Если реплика отсутствует слишком долго, Primary удалит старые WAL-файлы, чтобы не исчерпать место на диске. Когда реплика вернется, она запросит LSN, которого на Primary уже нет. Репликация сломается окончательно, и потребуется полное пересоздание реплики (pg_basebackup), что в базах терабайтного размера может занять часы.

    Для решения этой проблемы используются слоты репликации (Replication Slots). Слот — это механизм, который жестко привязывает удержание WAL на Primary к прогрессу конкретной реплики.

    Когда реплика подключается через слот, Primary запоминает ее текущий LSN. Если реплика отключается, Primary начинает бесконечно накапливать WAL-файлы, запрещая их удаление, пока реплика не вернется и не подтвердит их получение.

    Этот механизм дает стопроцентную гарантию, что реплика всегда сможет догнать мастера после сетевого сбоя. Однако он порождает один из самых опасных сценариев в эксплуатации баз данных: если реплика «умерла» навсегда и администратор забыл удалить ее слот, Primary будет копить WAL-файлы до тех пор, пока на сервере не закончится свободное место на диске. Как только диск заполнится на 100%, Primary упадет, и кластер полностью остановится. В отказоустойчивых системах всегда приходится балансировать между риском рассинхронизации реплики и риском падения мастера из-за переполнения диска.

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