Резервирование и единые точки отказа
Единая точка отказа — любой компонент, чья смерть валит всю систему. Резервирование убирает её — N+1, N+2, active-active, active-passive — но лишь если копии отказывают независимо. Общие зависимости превращают «зарезервированные» дизайны в один большой радиус поражения.
Инженерная команда держала три app-сервера за балансировщиком и звала этот слой «зарезервированным». Он и был — для app-серверов. Потом одним тихим вторником весь сайт лёг на сорок минут. Причиной был не app-сервер; это был единственный primary PostgreSQL, с которым говорили все три, перезагрузившийся ради патча ядра. Резервирование было настоящим, но не в том месте: команда строила втройне дешёвый stateless-слой и оставила единственный реально важный компонент стоять в одиночку. Резервирование, которого у тебя нет там, где живёт единая точка отказа, — это украшение.
Что такое единая точка отказа на самом деле
Единая точка отказа (single point of failure, SPOF) — любой компонент, чей индивидуальный отказ валит всю систему. Это несущая стена: выбей её — и всё, что выше, обрушится. Дисциплина построения под доступность по сути — это дисциплина выслеживания SPOF и их устранения, по одной, потому что обычно их больше одной и они прячутся.
Находят их мысленным экспериментом над каждым прямоугольником и линией на диаграмме архитектуры: «Если эта одна вещь умрёт прямо сейчас, система продолжит обслуживать?» База данных из вступления провалила тест — убей её, и всё встанет. Так же и единственный балансировщик перед «зарезервированным» флотом, единственный NAT-шлюз, через который течёт весь исходящий трафик, единственный DNS-провайдер, единственный CI-сервер, хранящий единственную копию ключей деплоя, одинокий дежурный инженер, который знает, как работает легаси-джоб биллинга. SPOF — это не только машины; это любая единичная зависимость, включая человека или вендора.
Резервирование: N, N+1, N+2
SPOF устраняют резервированием — запуская больше одной копии вещи, чтобы система пережила потерю одной. Стандартный словарь заимствован из энергетики дата-центров:
- N — ровно та ёмкость, что нужна под пиковую нагрузку, без запаса. Если спрос — 4 единицы и каждый ящик обслуживает 1, ты держишь 4. Потеряешь любой — и ты уже под ёмкостью. N — не зарезервировано.
- N+1 — один запасной сверх нужды. Держишь 5 ящиков под спрос в 4 единицы; теряешь один, и оставшиеся 4 всё ещё несут пик. Это базовая планка «зарезервированности», и она терпит ровно один отказ за раз.
- N+2 — два запасных, чтобы пережить отказ пока другой узел уже лежит (например, один на обслуживании, а второй умер). Высшие уровни доступности и всё с медленной заменой хотят N+2.
Тонкость, которую упускают джуны: резервирование надо размерять под пик, а не среднее. «У нас два сервера, значит мы зарезервированы» ложно, если один сервер не несёт пик в одиночку: потеря одного перегружает выжившего, что сваливается в коллапс очередей из раздела масштабируемости, и твоя «зарезервированная» пара отказывает как единое целое. Настоящий N+1 значит, что каждое выжившее подмножество всё ещё держит SLO.
Active-active против active-passive
Есть два способа расставить зарезервированные копии, и выбор меняет стоимость на скорость восстановления:
- Active-active — все копии обслуживают трафик одновременно, за балансировщиком. Теряешь одну — другие мгновенно впитывают её долю (ты под это размерял через N+1). Никакой задержки «переключения», и ты непрерывно гоняешь каждую реплику, так что знаешь — они все работают. Цена: каждая копия должна быть размерена и оплачена всегда, а stateful active-active нуждается в разрешении конфликтов, ведь две копии могут принимать записи одновременно.
- Active-passive — одна копия обслуживает; резерв простаивает (или тёплый) и берёт на себя, только когда активная умрёт. Дешевле для stateful-систем (один писатель, нет конфликтов) и проще для рассуждения, но failover берёт время, и резерв, который никогда не гоняли, может оказаться тихо сломанным, когда наконец понадобится — классический сбой «бэкап не сработал».
Ловушка: фальшивое резервирование через общую зависимость
Вот режим отказа, что превращает уверенно «зарезервированные» архитектуры в сбои: две копии, выглядящие независимыми, но делящие скрытую зависимость, вообще не зарезервированы. Если две реплики БД живут на одном физическом хосте, одном массиве хранения, в одной стойке, на одной линии питания или в одной зоне доступности, то единственное событие — тот хост, тот массив, та стойка, тот фид питания, та AZ — кладёт обе разом. Ты заплатил за две копии всего и всё ещё имеешь единую точку отказа, просто менее очевидную.
Это коррелированный отказ (correlated failure), и это центральная причина, почему математика доступности на независимости («каждая реплика 99,9%, значит две дают 99,9999%») оптимистична до опасности. Умножение держится, только если отказы независимы. В момент, когда они делят причину, твоя реальная доступность управляется этой общей вещью, а не числом реплик. Весь смысл облачных зон доступности (availability zones, AZ) — дать тебе домены размещения, спроектированные отказывать независимо: раздельные питание, охлаждение и сеть, достаточно далеко друг от друга, чтобы одно наводнение/пожар/сбой питания не унесло обе — так что разнос реплик по AZ делает их отказы по-настоящему некоррелированными.
▸Почему это работает
Почему коррелированный отказ — тихий убийца, а не очевидный баг? Потому что всё работает идеально в нормальной эксплуатации и в большинстве тестов — обе реплики подняты, трафик течёт, дашборд зелёный. Общая зависимость проявляется только во время редкого события, что бьёт по ней (выбило PDU в стойке, AZ потеряла питание), и в этот момент обе копии исчезают в одно мгновение, а твоему failover некуда идти. Корреляцию нельзя увидеть, наблюдая здоровый трафик; её находят, трассируя граф физических и логических зависимостей каждой реплики — хост, стойка, питание, сеть, регион, control plane, даже пайплайн деплоя — и спрашивая, какое единственное событие стоит выше более чем одной «независимой» копии. Что угодно общее — это корреляция, а корреляция — это SPOF в маскировке.
Радиус поражения: ограничь отказ, который не предотвратить
Ты никогда не устранишь каждый отказ, поэтому вторая дисциплина — ограничить, сколько может унести один отказ — его радиус поражения (blast radius). Вопрос дизайна смещается с «это упадёт?» (упадёт) на «когда упадёт, сколько уйдёт вместе с ним?». Помогает разбиение: шардь пользователей по ячейкам, чтобы отказ одной ячейки задел 1/N пользователей вместо всех; изолируй тенантов, чтобы шумный или сломанный не заморил остальных; деплой регион за регионом, чтобы плохой релиз поймать до глобального. Система с малыми радиусами поражения деградирует ломтиками; система с одним гигантским общим всем отказывает разом. Снижение радиуса поражения не повышает вероятность остаться поднятым для отдельного пользователя, но резко снижает тяжесть отказов, что всё же случаются — а тяжесть и есть то, что всплывает в постмортеме.
▸Частая ошибка
Частая и дорогая ошибка: добавить резервирование к компоненту, который легко продублировать, оставив трудный в одиночку — ровно команда из вступления, строящая втройне stateless app-серверы за единственным балансировщиком, говорящим с единственной БД. Stateless-слои тривиально сделать зарезервированными (просто добавь узлы), так что команды льют усилия туда и чувствуют себя в безопасности, пока stateful-слой и общая инфраструктура — primary БД, единственный балансировщик, единственный брокер сообщений, одинокий путь DNS или NAT — тихо остаются SPOF. Всегда располагай резервирование в SPOF, а не там, где удобно. Строить втройне дешёвый слой, пока дорогой стоит один, покупает тебе зелёный дашборд и сорокаминутный сбой.
Сервису нужно 4 сервера, чтобы нести пик, и он держит ровно 4 («у нас несколько серверов, значит мы зарезервированы»). Один сервер умирает в пик. Что реально происходит?
Ты держишь две реплики БД ради резервирования, но обе в одной зоне доступности. AZ теряет питание. Что это вскрывает о дизайне?
Две реплики, делящие скрытую зависимость — один хост, стойка, фид питания или зона доступности — отказывают вместе, когда эта зависимость умирает. Это называют _______ отказом, и он делает математику доступности на независимости опасно оптимистичной.
- 01Что такое единая точка отказа и как их находить?
- 02Объясни N, N+1, N+2 и active-active против active-passive.
- 03Почему «фальшивое резервирование» через общую зависимость опасно и чем помогают AZ?
Единая точка отказа — любой компонент, чья индивидуальная потеря валит всю систему, и ты выслеживаешь их, спрашивая о каждом прямоугольнике и линии: «если это умрёт сейчас, продолжим обслуживать?» — это не только серверы, но и одинокий балансировщик, DNS-провайдер, primary БД и даже единственный дежурный. SPOF убирают резервированием, размеренным под пик, а не среднее: N — без запаса (не зарезервировано), N+1 переживает одну потерю, N+2 переживает потерю, пока другой узел уже лежит. Расставь копии active-active (все обслуживают, мгновенный failover, но платишь за каждую копию и решаешь конфликты записи) или active-passive (одна обслуживает, резерв ждёт — дешевле и прост по записи, но переключение берёт время, а негоняный резерв может быть тихо сломан). Смертельная ловушка — фальшивое резервирование: копии, делящие скрытую зависимость — один хост, стойка, питание или зона доступности — отказывают вместе как коррелированный отказ, что делает математику доступности на независимости дико оптимистичной. Зоны доступности существуют ровно ради доменов отказа, спроектированных отказывать независимо, так что разноси реплики по ним. А раз не предотвратить каждый отказ, ограничь те, что прорвутся, сужая радиус поражения — разбивай пользователей, изолируй тенантов, деплой регион за регионом — чтобы система деградировала ломтиками, а не темнела разом, как команда из вступления, что строила втройне дешёвый слой и оставила одну БД в одиночку. Теперь, когда рисуешь диаграмму архитектуры, спрашивай о каждом прямоугольнике: «если это умрёт прямо сейчас — продолжим обслуживать?» — а потом уточни, не делят ли реплики, на которые ты рассчитываешь, один хост, стойку или зону доступности.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.