Когда речь заходит о High Availability, обычно представляешь классическую схему: три сервера, балансировщик, кластер PostgreSQL, распределённое хранилище, мониторинг и ещё с десяток компонентов, о |
||
|
МЕНЮ Главная страница Поиск Регистрация на сайте Помощь проекту Архив новостей ТЕМЫ Новости ИИ Голосовой помощник Разработка ИИГородские сумасшедшие ИИ в медицине ИИ проекты Искусственные нейросети Искусственный интеллект Слежка за людьми Угроза ИИ Атаки на ИИ Внедрение ИИИИ теория Компьютерные науки Машинное обуч. (Ошибки) Машинное обучение Машинный перевод Нейронные сети начинающим Психология ИИ Реализация ИИ Реализация нейросетей Создание беспилотных авто Трезво про ИИ Философия ИИ Big data Работа разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика
Генетические алгоритмы Капсульные нейросети Основы нейронных сетей Промпты. Генеративные запросы Распознавание лиц Распознавание образов Распознавание речи Творчество ИИ Техническое зрение Чат-боты Авторизация |
2026-09-13 11:47 Когда речь заходит о High Availability, обычно представляешь классическую схему: три сервера, балансировщик, кластер PostgreSQL, распределённое хранилище, мониторинг и ещё с десяток компонентов, о которых никто не вспоминает, пока всё работает на одной виртуалке. У нас исходная ситуация была куда прозаичнее: рабочая система на Totum, которую нужно перенести в новую инфраструктуру и одновременно избавиться от единственной точки отказа. Требования были жёсткими: падение одной основной VM не должно останавливать систему, PostgreSQL должен автоматически переключаться, приложение должно понимать, на какой ноде ему разрешено выполнять активные операции, пользовательские файлы должны оставаться доступными после переключения, переключение не должно требовать ручного изменения DNS, всё должно разворачиваться через Ansible, а покупать третью полноценную машину только ради кворума не хотелось. Последний пункт в итоге и породил архитектуру, которую между собой я называл «HA для бедных». Спойлер: третья машина всё-таки появилась. Но это маленький witness, который практически ничего не делает с точки зрения бизнес-нагрузки. Totum — self-hosted low-code платформа для создания внутренних web-приложений: ERP, учётных систем, личных кабинетов и других кастомных бизнес-систем. Приложения строятся вокруг таблиц и прикладной логики, а сама платформа может работать со сторонними системами через API и инициировать HTTP-запросы. В нашем случае это уже была не тестовая установка, а рабочая бизнес-система с PostgreSQL, пользовательскими файлами, интеграциями и большим количеством прикладной логики. Официальная документация Totum отдельно рекомендует перед промышленной эксплуатацией настраивать PHP-FPM, PostgreSQL, память, диски и индексы под реальную нагрузку. Но до оптимизации ещё нужно было решить более фундаментальную задачу: что произойдёт, если сервер просто умрёт? Упрощённо старая архитектура выглядела как одна VM, где крутились и приложение, и база, и файлы, и nginx, и PHP, и фоновые процессы. Падает VM — вместе с ней исчезает всё. Можно иметь backup, snapshot и прекрасную инструкцию восстановления, но это всё равно Disaster Recovery, а не High Availability. И здесь появляется первая классическая проблема распределённых систем: кто из двух главный? Для PostgreSQL мы выбрали Patroni. Но Patroni нужен Distributed Configuration Store, который позволит участникам договориться, кто лидер. В нашем случае таким хранилищем стал etcd. И для etcd две ноды — плохая идея. При двух участниках кворум равен двум. Потеряли одну машину — оставшаяся не имеет большинства. Получается парадокс: мы построили HA из двух серверов, но потеря одного сервера мешает кластеру нормально принимать решения. Можно поставить третью полноценную VM. Но зачем нам третья машина с такими же CPU/RAM, если приложение на ней работать не будет? Так появился witness. Финальная схема получилась такой: etcd-1 на Node A, etcd-2 на Node B, etcd-3 на Witness. Важно: witness не является PostgreSQL-репликой. На нём нет копии production-базы и нет Totum. Его задача значительно проще — быть третьим голосом в etcd. Если Node A исчезает, Node B + Witness дают 2 голоса из 3. Если исчезает Node B — кворум снова сохраняется. А маленькая VM обходится заметно дешевле полноценной третьей application/database-ноды. Это и есть HA с отдельным quorum witness. База работает на двух основных серверах, репликация асинхронная. У такого решения есть очевидный компромисс: при аварийной потере primary теоретически можно потерять небольшой объём последних транзакций, которые ещё не успели попасть на replica. Синхронная репликация уменьшила бы этот риск, но увеличила бы зависимость latency записи от второй ноды. Для нашей задачи был выбран asynchronous-вариант. Но переключить PostgreSQL недостаточно. Допустим, Patroni прекрасно сделал failover: Node A был PRIMARY, упал, Node B стал PRIMARY. С базой всё хорошо. А что делает приложение? Если nginx на обеих машинах принимает запросы, можно случайно получить две одновременно работающие application-ноды, каждая из которых считает себя активной. Для некоторых stateless-приложений это нормально. Для нашей конфигурации Totum — нет. Поэтому на обеих машинах работает небольшой role-check механизм. Он периодически спрашивает локальный Patroni, является ли текущая нода primary. PostgreSQL leader становится источником истины для application-role. Node A = ACTIVE, Node B = PASSIVE. Внешний L7-балансировщик знает обе Totum-ноды, но health-check смотрит специальный endpoint: ACTIVE отвечает HTTP 200, PASSIVE — HTTP 503. Если Patroni переключает primary, role-check меняет application-role, health-check видит новую картину и начинает отправлять пользовательский трафик на новую ноду. DNS при этом менять не надо. С базой всё относительно стандартно: PostgreSQL replication. Но у Totum есть ещё приложение и файлы. И они сами по себе через Patroni не реплицируются. Для этого использовали lsyncd. Главная проблема здесь — направление синхронизации. Если сегодня ACTIVE Node A, данные идут A ? B. После failover направление должно автоматически перевернуться: A ? B. Role-check управляет и этим. Самая опасная версия такой архитектуры выглядела бы как A ? B, где обе машины что-то пишут друг другу. Это прямой путь к конфликтам. Поэтому мы сделали fencing: принимать изменения должна только PASSIVE-нода. Если сервер становится ACTIVE, входящую синхронизацию нужно ограничить. Иначе можно получить неприятный сценарий: A вернулся со старым состоянием, и старые данные поехали поверх новых. Мы синхронизировали application files и необходимые каталоги Totum, но исключили то, что должно быть локальным для каждой машины. Конкретный список зависит от установки. Главный принцип: не надо превращать lsyncd в распределённую файловую систему. Синхронизировать стоит только то, что действительно должно переехать вместе с application-role. Для приложения конфигурацию сделали одинаковой на обеих машинах: Totum всегда обращается к PostgreSQL на той же VM. Не нужно писать отдельную логику выбора DB host. Patroni уже гарантирует, какая локальная PostgreSQL-инстанция сейчас primary. Практически всю инфраструктуру в итоге собрали через Ansible. Идея была принципиальной: если после аварии для восстановления сервера нужно вспоминать историю shell-команд из Telegram, это ещё не инфраструктура. Но на production находилась конкретная версия Totum, а application-role в Ansible могла содержать другую Git-версию. Поэтому большой site.yml перестал восприниматься как кнопка «починить всё». Изменения применяли отдельными ролями или узкими playbook'ами. Не запускайте весь playbook просто потому, что можете. Самая неприятная часть любой HA-архитектуры — не построить её с нуля, а перетащить туда существующий production. Сначала подняли новую инфраструктуру параллельно старой и проверили. После этого перенесли production-базу и файлы. Старый сервер некоторое время оставался rollback-вариантом. И только после проверок переключили пользовательский трафик на новый Load Balancer. HA нельзя проверить командой «вроде работает». Нужно реально потерять ноду и убедиться, что система восстанавливается в ожидаемое состояние. До: Node A = ACTIVE, Node B = PASSIVE. Ожидаем: Node A = DOWN, Node B = PostgreSQL PRIMARY, Node B = Totum ACTIVE, Load Balancer ? Node B. Возвращаем A: Node A = PostgreSQL REPLICA, Node A = Totum PASSIVE, Node B остаётся ACTIVE. Потом тест повторяется в обратную сторону. Только после таких проверок можно говорить, что failover существует не только на архитектурной диаграмме. После миграции возникла отдельная проблема. Снаружи всё выглядело прекрасно: CPU почти свободен, RAM хватает, диск не забит, PostgreSQL replication в норме, load average небольшой, login page открывается быстро. Но пользователи периодически говорили: «Totum умер». Интерфейс на несколько десятков секунд практически переставал отвечать. PHP-FPM оказался первой половиной проблемы. Во время деградации процессы PHP-FPM доходили до лимита. В slowlog одновременно появлялись длительные внешние HTTP-запросы, long polling, проверки изменений таблиц и уведомлений. Для FPM такой worker всё это время недоступен другим запросам. Если заняты все 14, новые запросы ждут, и UI выглядит так, будто умер целиком. После этого стало видно, где именно зависают worker'ы: внешние curl-вызовы через getFromScript, long polling и проверки уведомлений. Так мы получили уже не ощущение пользователя «что-то медленно», а конкретный stack trace. А потом мы начали измерять curl. Одного slowlog оказалось мало. Мы временно добавили instrumentation вокруг внешних HTTP-вызовов и логировали DNS, CONNECT, TLS, TTFB, TOTAL и HTTP code — без query string, cookies и токенов. Выяснилось, что соединение устанавливалось практически мгновенно. Задержка происходила после подключения — пока внешний application backend готовил ответ. Почему увеличение PHP-FPM всё-таки помогло? Внешний API от этого быстрее не стал. Но мы изменили pool: 14 ? 40 workers. После этого эффект был практически мгновенным: отдельный медленный запрос по-прежнему мог выполняться долго, но весь Totum больше не ложился вместе с ним. Это принципиальная разница между «мы ускорили запрос» и «мы локализовали влияние медленного запроса». На одном из этапов мы чуть не сделали неправильный вывод: посчитали процессы PHP-FPM и приняли количество процессов за количество занятых worker'ов. В dynamic mode это неверно: FPM специально держит idle workers. Поэтому включили штатный status endpoint и получили реальные показатели. Например, total=30 не означает загрузку 75%. Реальная текущая нагрузка в этом срезе была 3/40 = 7,5%, а максимум после restart — 10/40 = 25%. После изменения pool перестал быть bottleneck. На серверах уже был node_exporter с включённым textfile collector. Поэтому отдельный exporter для PHP-FPM оказался не нужен. Небольшой скрипт раз в 15 секунд забирает /fpm-status и пишет метрики в файл textfile collector. В Grafana добавили три ключевых графика. Теперь при следующей жалобе «Totum тормозит» вместо SSH + ps + гаданий достаточно открыть dashboard. Если FPM utilization близок к 100% и queue > 0 — смотрим pool. Если utilization низкий и queue = 0 — ищем конкретный slow request, SQL или внешний сервис. Что в сухом остатке? Для кворума не обязательно покупать третью такую же машину. Если две основные ноды достаточно мощные, третий etcd member может жить на маленьком witness. Главное понимать, что это не третья data replica и не запасной application server. HA базы ещё не означает HA приложения. Можно идеально переключить PostgreSQL и при этом получить split-brain на application layer. Файлы сложнее базы. PostgreSQL replication решает PostgreSQL. Всё остальное приходится проектировать отдельно. Low CPU ничего не говорит о latency приложения. Worker, который десятки секунд ждёт HTTP response, почти не использует CPU, но всё равно занимает слот FPM. Наблюдаемость надо строить до инцидента, а не во время него. Следующую подобную систему стоит сразу запускать с active/idle workers, queue, slow requests, HTTP latency, DB latency и replication lag. Не оптимизируйте приложение одним увеличением ресурсов. 14 ? 40 workers решило systemic failure, но не сделало внешний API быстрее. Это был правильный mitigation, а не магическое лечение всей производительности. Эксплуатация должна быть простой. Если для обычного reboot каждый раз нужен автор архитектуры, работа ещё не закончена. Мы начинали с двух серверов и половинки третьего, а в итоге получили отказоустойчивую систему, которую можно восстанавливать и обслуживать без героизма. А как вы строите HA для своих Телеграм: t.me/ainewsline Источник: vk.com Комментарии: |
|