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 нас интересуют две ключевые бизнес-метрики:
High Availability архитектура строится для того, чтобы свести RTO к секундам, а RPO — к нулю или миллисекундам.
Анатомия отказов: где ломаются системы
Главный враг высокой доступности — это SPOF (Single Point of Failure, единая точка отказа). Это любой компонент системы, выход которого из строя приводит к остановке всего сервиса. Если у вас работает один сервер PostgreSQL, он является SPOF.
Отказы в распределенных системах делятся на несколько категорий: * Аппаратные сбои: сгорел блок питания, вышел из строя SSD-накопитель, деградировала оперативная память. * Программные ошибки: падение процесса базы данных из-за бага (например, утечки памяти) или исчерпания ресурсов (OOM Killer убил процесс PostgreSQL). * Сетевые аномалии: самый коварный тип отказов. Сеть может не просто пропасть, она может начать терять пакеты, увеличивать задержку (latency) или разделить инфраструктуру на две изолированные части. * Человеческий фактор: случайное удаление директории с данными, применение некорректной конфигурации.
Чтобы устранить SPOF, инженеры применяют избыточность (redundancy). Если один сервер может сгореть, нужно поставить два. Но как сделать так, чтобы два сервера работали с одними и теми же данными?
Репликация: технологический фундамент избыточности
Для обеспечения избыточности в базах данных используется репликация — процесс непрерывного копирования данных с одного узла на другие. В классической архитектуре выделяют две роли серверов:
Репликация работает не путем копирования файлов базы данных, а через передачу журнала транзакций (в 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. Его задачи:
Жизненный цикл отказа: как работает автоматический Failover
Связка Patroni и etcd превращает кластер в самовосстанавливающийся механизм. Механика удержания лидерства построена на концепции TTL (Time To Live — время жизни).
Когда Patroni-агент на Primary-сервере захватывает Leader Lock в etcd, этот замок выдается не навсегда, а, например, на 30 секунд. Чтобы оставаться лидером, Patroni должен каждые 10 секунд отправлять в etcd запрос: «Я жив, продли мой замок еще на 30 секунд». Это называется heartbeat (сердцебиение).
!Процесс автоматического Failover
Что происходит при аварии:
Весь этот процесс занимает от 10 до 40 секунд. Инженеры могут спать спокойно — система восстановила свою работоспособность сама. Риск Split-Brain исключен: даже если старый Primary был просто изолирован сетью, он не сможет продлить свой замок в etcd. Когда сеть восстановится, старый Patroni увидит, что замок занят другим узлом, и принудительно понизит свой локальный PostgreSQL до состояния реплики (или остановит его), защищая данные от расхождения.
Переход от одиночных инстансов баз данных к управляемым кластерам — это смена парадигмы. Мы перестаем пытаться сделать один сервер бессмертным. Вместо этого мы создаем архитектуру, в которой смерть любого компонента является ожидаемым событием, автоматически обрабатываемым программными агентами. Репликация дает нам физическую возможность не потерять данные, а распределенный консенсус дает уверенность, что переключение ролей пройдет безопасно.