❓Как проектировать системы, которые будут толерантными для различного вида отказов и ошибок?

Что такое отказоустойчивость и стабильность?

Под отказоустойчивостью будем понимать свойство системы, которое позволяет максимально сохранять работоспособность при отказе отдельных конкретных компонентов системы либо связанных систем и восстанавливать работоспособность системы при восстановлении отказавших компонентов или связанных систем. Давайте рассмотрим подробнее эти 2 момента:

  1. Деградация работоспособности системы должна быть прямо пропорциональна "величине" отказа. То есть, если упал сервис, отвечающий за некую некритичную функциональность — вся система не должна при этом падать. Да, небольшой кусочек не работает, но это не влияет на стабильность остальной части функционала.

  2. Стабильность системы предполагает самостоятельного восстановления работоспособности после сбоя как компонентов системы, так и всей системы в целом. К примеру, если пропадала сеть на некоторое время — то у стабильных систем после восстановления подключения все компоненты продолжат работать и данные вернутся в консистентное состояние без ручного вмешательства со стороны команды эксплуатации.

Наглядно разницу между отказоустойчивыми и неотказоустойчивыми системами представим следующим образом:

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

❓Но как же определить является ли наша система отказоустойчивой и стабильной? И на сколько? Как в идеале замерить эти свойства, чтобы двигаться к цели согласно некой метрике, а не наощупь?

Единственный способ спроектировать систему, которая будет устойчива к сбоям — проводить стресс-тесты (resilience testing / chaos engineering), по результатам тестов делать выводы, менять архитектуру и подходы к написанию кода, снова запускать тест... и так до бесконечности :)

Количество пройденных тестов и будет метрикой отказоустойчивости отдельных компонентов и системы в целом. Только на основе метрики делаем вывод о повышении или понижении стабильности системы.

Создание таких тестов — уже непростая инженерная задача. Для начала необходимо описать и автоматизировать сценарии отказа (легла сеть, закончилось место на диске, внешний сервис отвечает ошибками, сеть тормозит или доставляет только 50% пакетов, выключилось электричество на конкретном физическом хосте или во всё дата-центре и т.д.) — а потом запускать их в произвольном порядке, да и ещё и в случайных комбинациях друг с другом ????????. Тесты позволят нам и получить измеримую метрику (прошли 10 тестов из 50), и понять какие отказы наиболее критичны, т.к. сразу же приводят к краху всей системы.

Принципы построения отказоустойчивых систем

Отсутствие единой точки отказа (No SPoF (Single Point of Failure))

Принцип применим ко всем уровням эксплуатации — от точки деплоя (любой Nginx или микросервис в системе) до уровня дата центров. Рассмотрим этот принцип на примерах репликации и балансировки небольших компонентов системы:

❓Как проектировать системы, которые будут толерантными для различного вида отказов и ошибок? Что такое отказоустойчивость и стабильность?-2

Для его реализации сервисы должны быть готовы к запуску в нескольких экземплярах — как минимум не должны хранить состояния (быть stateless-сервисами). Давайте рассмотрим обратный пример, когда у нас сервис хранит состояние и неготов к горизонтальному масштабированию. Пусть в нашей схеме сессия пользоавтеля хранится на самом бэкендовском сервисе:

Пока Backend работал в единственном экземпляре — всё было хорошо, но после горизонтального масштабирования до двух экземпляров в случае простой балансировки запрос авторизованного в одном сервисе пользователя рано или поздно попадёт на Backend, где нет нужной сессии.

В данном случае для подготовки сервиса к горизонтальному масштабированию можем, к примеру, вынести сессии в отдельное хранилище, например Redis:

❓Но что же делать с компонентами, которые хранят состояния (т.н. stateful-сервисы, у нас это БД и Redis) или не могут быть запущены во множеством экземпляре (к примеру, если сеть поддерживает направление траффика только на один Balancer)?

Хранилища обычно реплицируются по схеме мастер-реплика (естественно, в нашем примере все сессии пользователей будут хранится на всех Redis'ах). В случае отказа мастера — реплика автоматически становится новым мастером и все потребители переключаются на неё. Такая же схема используется и для stateless-сервисов, которые не могут быть запущены в нескольких экземплярах: поднимаются несколько экземпляров stateless-сервисов, но траффик идёт только на один из них. В случае отказа главного экземпляра, происходит автоматическое переключение на запасной.

Ещё один пример stateless-сервиса, реплецированного но работающего в единственном экземпляре — это сервис последовательной синхронной записи (Writer):

В случае необходимости обеспечить последовательную синхронную запись — не удастся запустить сразу два сервиса Writer'а. Для отказоустойчивости, поднимаем запосной Writer и настраиваем балансировщик на автоматическое переключение в случае сбоя.
В случае необходимости обеспечить последовательную синхронную запись — не удастся запустить сразу два сервиса Writer'а. Для отказоустойчивости, поднимаем запосной Writer и настраиваем балансировщик на автоматическое переключение в случае сбоя.

Проектирование с учётом отказов (Design for Failure)

В любой системе происходят отказы и падения, поэтому важно уже на этапе проектирования архитектуры держать в уме, моделировать и закладывать обработку нештатных ситуаций. Рассмотрим основные базовые идеи концепции:

???? Постепенная деградация (Graceful degradation) — это возможность частично деградировать функционала системы в случае отсутствия/неработоспособности её компонентов. Рассмотрим на схеме вариант построения системы с последовательной связанностью и полной деградацией при отказе:

В случае отказа компонента с кружочком — перестают работать все зависимые компоненты по цепочке.
В случае отказа компонента с кружочком — перестают работать все зависимые компоненты по цепочке.

Если же мы чуть иначе перераспределим зависимости, то получим лишь частичную потерю функциональности:

В случае отказа компонента с кружочком — в треугольнике пропадёт только часть
В случае отказа компонента с кружочком — в треугольнике пропадёт только часть

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

Данные на странице товара подгружаются и отображаются независимо друг от друга
Данные на странице товара подгружаются и отображаются независимо друг от друга

В худшем варианте, если некий BFF или API Gateway дожидается синхронного ответа от каждого компонента и только потом отдаёт результирующий html — страница отобразится пользователю только после отрабатывания самого медленного компонента (или суммы времени всех, если запросы строятся ещё и последовательно, а не параллельно). При падении одного из компонентов (например, не самого важного раздела с похожими товарами) в нетолерантном к отказам варианте проектирования пользователь бы в принципе ничего не увидел кроме страницы с ошибкой.

ℹ️ Помимо отложенной доставки или потери данных, в случае их критичности — можем деградировать в их актуальности взамен полного отсутствия при помощи кэширования. К примеру, если источник актуальных данных недоступен — берём предсохранённые данные из кэша: в зависимости от сценария, тут может быть как частичная деградация (актуальные данные лучше старых, но старые лучше их отсутствия) так и отсутствие деградации вовсе (пусть в 99% случаев закэшированные данные соответствуют актуальным).

???? Умри быстро (Fail fast) — быстрое выявление проблемного сервиса/запроса и их отстрел (самостоятельный или внешний). Схожий принцип — "мёртвые программы не лгут". На первый взгляд кажется нелогичным, что сервису лучше "умереть" (и в идеале перезапуститься), чем даже на относительно недолго зависнуть — давайте разбираться на примере. Вернёмся к нашему stateless-сервису записи, который должен работать только в единственном экземпляре:

❓Как проектировать системы, которые будут толерантными для различного вида отказов и ошибок? Что такое отказоустойчивость и стабильность?-9

Если основной работающий экземпляр сервиса Writer зависнет, но не упадёт или выдаст ошибку — то при отсутствии таймаута на балансере не произойдёт автоматического переключения на запасной экземпляр сервиса и система свалится в отказ. Лучшим вариантом в большинстве случаев будет упасть или выдать ошибку, извещая тем самым балансер и потребителя о неуспехе.

???? История из жизни. После очередного релиза резко утяжелился запрос в БД для далеко не самой основной функциональности. Когда один из пользователей таки добрался до данной функциональности — уходил тяжелый запрос в БД, с которым БД честно пыталась справится на протяжении долгих минут, потребляя много ресурсов железа. Для пользователя это выглядело так, что при заходе на страницу ничего не происходит. Что при этом мы обычно делаем? Правильно! Пробуем ещё раз ????и ещё, и ещё. Каждый раз при этом на БД падали ещё и ещё точно такие же тяжёлые запросы при том что исполнение предыдущих никто не отменял. Достаточно быстро серверная часть стала настолько загруженной параллельным выполнением одного и того же тяжелого запроса, что тормозить и неотвечать стали любые, даже лёгкие запросы. Собственно в этот момент и началась паника, и команде эксплуатации открылась картина, что тормозит вообще всё — пойди ещё разберись что являлось первопричиной проблемы..

???? Мораль: реализация быстрой смерти (например, по таймауту) спасла бы систему от лавинообразной деградации. Попытка выполнить долгий тяжелый запрос должна обрубаться и по таймауту, и по отмене с вызывающей стороны (уход или обновление страницы до ожидания её загрузки). Для пользователя сообщение об ошибке так же более прозрачно, чем зависание в виде бесконечной загрузки.

Разделение ресурсов (resources segregation)

Рассмотирм два типа разделения:

  1. Квотирование для каждого потребителя. Примером тут может быть ограничение количество запросов к API за единицу времени для каждого потребителя (по его API-ключу) — допустим сервис позволяет каждому из потребителей с типом ключа public совершать не больше 100 запросов к API в минуту, а с типом internal — до 1000. Квотирование позволяет не допустить отказа сервиса для всех потребителей из-за умышленного или неумышленного DDoS'а со стороны единственного потребителя.

  2. Разделение по доступности физических ресурсов (CPU, RAM, ...). Суть в строгом лимитировании максимально возможного потребления ресурсом одним сервисом (контейнером). Оркестратор контейнеров (например, Kubernetes) не должен безгранично выдавать запрашиваемые ресурсы конкретному контейнеру, даже если на физическом хосте есть свободные ядра процессора/оперативная память и т.д. Проблема перегрузки в единственном контейнере никак не должна влиять на соседние контейнеры и весь хост в целом.

В случае пробоины (утечки памяти) в одном из отсеков (сервисов) мы должны ограничить проблему объёмом единственного отсека (максимальной выделенной памятью для сервиса).
В случае пробоины (утечки памяти) в одном из отсеков (сервисов) мы должны ограничить проблему объёмом единственного отсека (максимальной выделенной памятью для сервиса).

Умная балансировка (Weighted load balancing)

Согласно этому принципу нагрузка балансируется в зависимости от загруженности экземпляра сервиса и/или его времени ответа. Например, чем сильнее время ответа экземпляра сервиса превышает заданные показатели в SLO — тем меньше трафика посылает на него балансировщик. И наоборот — сервис справился со своей нагрузкой — пускаем на него больше трафика. Ранжирование должно быть динамическим и может учитывать разные факторы (время ответа, загрузка CPU, количество "тяжелых" запросов в обработке, сравнительные показатели между экземплярами под балансировкой и т.д.). Подробно принцип описан в гугловской книге по SRE.

Ассинхронное взаимодействие

Суть принципа в замене синхронных (читай менее отказоустойчивых) зависимостей (в первую очередь между системами) на асинхронное. Рассмотрим на всё том же примере отображения срока доставки на странице товара интернет-магазина.

В случае октаза системы, отвечающей за предоставление срока доставки — пользователь в лучшем случае этот срок не увидит на странице товара
В случае октаза системы, отвечающей за предоставление срока доставки — пользователь в лучшем случае этот срок не увидит на странице товара

Повысить отказоустойчивость и снизить уровень внешней связанности позволит переход на ассинхронную зависимость через очередь сообщений:

В случае отказа сроков доставки функциональность системы страницы товара никак не пострадает — максимум, не будет получать обновления по срокам доставки, которые меняются нечасто.
В случае отказа сроков доставки функциональность системы страницы товара никак не пострадает — максимум, не будет получать обновления по срокам доставки, которые меняются нечасто.

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

Шаблоны и практики

Автоматический выключатель (Circuit Breaker)

Этот паттерн обычно используют взамен классической стратегии простых повторов (Retry) запроса в случае неуспеха, ставшей антипаттерном. Простой повтор запроса имеет смысл далеко не во всех случаях: к примеру, если БД отвечает ошибкой о несовпадении типа поля — повторный аналогичный запрос её не исправит. Автоматические повторы могут пригодится, к примеру, в случае проблем с сетью — в идеале их переложить на уровень инфраструктуры и в любом случае ограничить по времени и количеству. Но вернёмся к Circuit Breaker'у:

В синхронную связь между компонентами системы мы добавляем контроллер-предохранитель, работающий как proxy, если всё хорошо. При возникновении проблем связь на время "выключается".
В синхронную связь между компонентами системы мы добавляем контроллер-предохранитель, работающий как proxy, если всё хорошо. При возникновении проблем связь на время "выключается".

Тем самым Circuit Breaker "из коробки" реализует идею Fail fast сразу же отвечая потребителям о проблеме (после детекции проблемы новые запросы сразу же будут падать с ошибкой даже без обращения к проблемному сервису). Не менее важно, что Circuit Breaker таким образом не допускает DDoS'а сервиса, которому итак плохо (в случае слепых повторов запросов от потребителей проблемный сервис так и не сможет подняться под напором нагрузки, либо будет делать это значительно дольше). Для возобновления работоспособности Circuit Breaker периодически пропускает часть трафика на проблемный сервис и в зависимости от результата либо оставляет связь "выключенной" для большинства запросов, либо её включает. Нет смысла пропускать 100 пришедших запросов на проблемный сервис — можно пропустить один в диагностических целях и в случае сохранения проблем ответить ошибкой уже всей сотне.

Толерантный читатель (Tolerant Reader)

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

Умный сервер / умный клиент

В силу особенностей конкретных архитектур и реализации интеграций в общем случае может не оказаться возможным встроить гибкую схему балансировки и Circuit Breaker'а. В этом случае к примеру на стороне сервера/сервиса можно реализовать самодиагностику (о перегруженности) и варианты мгновенного отказа (отказа новым запросам как в Circuit Breaker'е или даже полный рестарт самого себя). Балансировку же можно вынести на сторону клиента (Client Side Load Balancer), которую иногда целесообразно применять даже если возможна серверная балансировка.

Динамические параметры поведения системы

Система должна быть гибкой и подстраивающейся под текущую нагрузку.
В высоконагруженных частях системы лучше избегать жёстких значений в стратегиях повышения отказоустойчивости. К примеру, мы не фиксируем константой количество повторных запросов и/или частоту диагностических запросов Circuit Breaker'а, а ограничиваем 10ю процентами долю таких запросов в общем трафике. Таким образом, добиваемся схожего поведения системы в случае отказов как при высокой, так и при низкой нагрузке. Если сконфигурировать систему только под высокую нагрузку — можно, например, пропустить серьёзную проблему и не получить алертов, когда при низкой нагрузке всё работает, но 50% запросов приходится дублировать.

Нагрузочное и стресс тестирование

Если стресс-тестирование (Resilience testing), как и писал в начале статьи, даёт метрику отказоустойчивости и стабильности системы, то нагрузочное тестирование — показывает границы нашей отказоустойчивости. Т.е. система может быть стабильна, но не выдерживать высоких нагрузок, и наоборот — система держит высокие нагрузки, но не устойчива к отказам (например, к отказу оборудования). Хочется отдельно отметить, что нагрузочное тестирование — это не всегда только про высокий RPS и большое количество пользователей. Для многих систем не менее важно проводить нагрузочные тесты по объёму данных (к примеру: пользователей немного, но БД может вырасти до терабайтов; обычно файлы на входе не превышают пары мегабайт, но мы должны обрабатывать и гигабайтные).

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

Всем ❤️

Комментарии (15)


  1. itGuevara
    02.10.2023 16:59
    +2

    Но как же определить является ли наша система отказоустойчивой и стабильной? И на сколько? Как в идеале замерить эти свойства, чтобы двигаться к цели согласно некой метрике, а не наощупь?

    Единственный способ спроектировать систему, которая будет устойчива к сбоям — проводить стресс-тесты (resilience testing / chaos engineering), по результатам тестов делать выводы, менять архитектуру и подходы к написанию кода, снова запускать тест... и так до бесконечности :)
    Количество пройденных тестов и будет метрикой отказоустойчивости отдельных компонентов и системы в целом. Только на основе метрики делаем вывод о повышении или понижении стабильности системы.

    Разве отказоустойчивость (надежность) не считают количественно? Все эти "пять-шесть девяток" например. В статье "Проектирование отказоустойчивости IT-систем" нет ни одной цифры по надежности ... и тем более расчета.

    Проектируете что-то "очень надежное", а надежность этого "очень надежного" не считаете?


    1. razon Автор
      02.10.2023 16:59
      +1

      пять-шесть девяток – метрика на основе уже случившегося. Стресс-тесты позволят посчитать метрику "а что если" – сколько будет девяток (как быстро система поднимется), если умрёт тот или иной хост к примеру


      1. itGuevara
        02.10.2023 16:59

        пять-шесть девяток – метрика на основе уже случившегося.

        Есть подходы к проектированию систем с ровно заданным числом девяток (типа оптимизация, т.к. избыточная надежность стоит недешево), например, через алгоритм, какую конфигурацию кластера и с какими узлами (надежность узла) подобрать, чтобы получить, например, шесть девяток. Это Надежностное проектирование (через модели надежности), т.е. "задолго до "уже случившегося".

        В Вашем случае, когда "Стресс-тесты позволят посчитать метрику " - чему равны значения коэффициента готовности "отказоустойчивой ИТ-системы"? Какие значения метрики (сколько будет девяток)?

        Пример расчета с цифрами покажете?


        1. razon Автор
          02.10.2023 16:59
          +1

          Есть конкретный пример с прошлой недели – при сбое не переподнялись только системы, на которые поставили тулзу для аварийного автоматического переключения. По моделям и алгоритмам – у них выше показатели надёжности, по факту – оказалось что ниже, чем у систем, куда ещё не успели поставить тулзу.

          Правильность установки тулзы и ее работоспособность при разных условиях кроме как учебными сбоями не проверить


          1. itGuevara
            02.10.2023 16:59

            Т.е. Ваши измерения: менее надежно \ более надежно?


            1. razon Автор
              02.10.2023 16:59

              я указал в статье метрику — кол-во упавших тестов. Я не против классических моделей и метрик, я говорю о том, что только их недостаточно в общем случае


      1. Batalmv
        02.10.2023 16:59

        Нет, это расчетный показатель. Как и базовые RTO/RPO, которые я с немалым удивлением не нашел :)

        Вы собираете требования к системе, реализуете решение, чтобы они выполнялись, а потом тестами, вплоть до реального отключения проверяете

        И именно так, проектируете под требования!!


  1. Batalmv
    02.10.2023 16:59
    +2

    Про асинхрон. Смотрите. Разницу определяет суть взаимодействия. Условно, если пользователь дал запрос и ждет ответа - это синхронное взаимодействие.

    Вы можете напихать внутри 100500 очередей, но суть не изменится - если клиент за условные 10 секунд не дождался ответа -> fail. И иногда, это определено задачей. Вот просто по определению. Плюс сам по себе асинхрон не панацея. Система чудесно может лечь в обоих вариантах легко и непринужденно :)


    1. razon Автор
      02.10.2023 16:59

      тут уже на лицо архитектурные ошибки — если мы синхронное взаимодействие пользователя с системой реализуем на бэкенде через асинхронщину


      1. Batalmv
        02.10.2023 16:59

        Нет никакой ошибки. Так можно делать без проблем со своими плюсами/минусами. Асинхрон конвертирует нагрузку в очередь. Это дает следующие плюсы

        • Сохранность сообщений, если инициируется транзакционный запрос. Банально проще и комфортнее

        • Система не ляжет технически от повышенной нагрузки, так как забирает ровно столько, сколько может проглотить. Но с другой стороны может быть все зря, так как получатель уже ушел :)

        • Проще и естественнее оркестровать через сообщения. Это не единственный способ конечно, но с сообщениями это более природно, что ли

        Может быть пример, что под капотом компоненты перетирают "за жизнь" через MQ, а наружу торчит http-фасад.

        Короче, это очень в общем. Иногда проще взять http, просто потому, что на короткой дистанции более важно то, с чем умеет работать команда здесь и сейчас :)


        1. razon Автор
          02.10.2023 16:59

          тут вопрос во взаимодействии с пользователем, т.е. в UI/UX. Если мы понимаем, что за увлоный таймаут в 1 секунду мы не сможем пользователю сказать, что всё ок, транзакция выполнена или не ок, завершилась с ошибкой — то пользователю явно прозрачно об этом сообщаем. К примеру, секунду крутим лоадер, если за секунду не успели — говорим, что ещё обрабатывается, результат пришлём на почту, вкладку можно закрыть (или, "пожалуйста не закрывайте вкладку, обновим статус операции через 10 секунд"). Короче не кидаем пользователя в неопределенности с бесконечным лоадером. Но это уже несколько отдельная от отказоустойчивости тема :)


  1. Areso
    02.10.2023 16:59

    Позволю себе не согласиться с некоторыми примерами из статьи.

    Пример номер раз:
    у нас есть несколько экземпляров бэкенда, на каждом живут свои сессии. При утери одного бэкенд-экземпляра у нас остаются клиенты с куками (токенами), которые на оставшихся будут не валидны. Что делает автор? Вфигачивает туда Redis, да еще и в нескольких экземплярах. Что предлагаю я? "Разлогинить" пользователя, и попросить залогиниться заново.
    Конечно, конечное конкретное решение зависит от задачи и ограничений, поставленных бизнесом, но падения как такового в моём случае нет - просто у пользака внезапно протухла сессия. Бывает. Зато мы экономим денег (в некоторых случаях - много денег, потому что кластер Redis'ов не бесплатная игрушка).


    1. razon Автор
      02.10.2023 16:59

      естественно всё зависит от требований и ограничений. Для кого-то лишний Redis будет несопостовимо дешевле возможных потерь, а для кого-то — дороже )


  1. Areso
    02.10.2023 16:59
    +1

    Пример номер два:
    "умри быстро". Работаю в компании, в которой именно так поступили разработчики. "умри быстро". А знаете, что происходит, когда у вас контейнеры массово дохнут и потом вновь массово поднимаются? Они штурмуют базу. И чем хуже приложению, тем сильнее штурм базы. Более того, некоторые успевают не только закидать "коннектами" БД, но еще и закинуть туда непосредственно чего-либо внутрь. Например, тяжелых запросов. Идеальный шторм.

    Количество соединений растёт, пока база не упадёт или не упрётся в лимит коннектов, после которого у вас все новые контейнеры просто паникуют нон-стопом.

    Количество запросов растёт. Количество тредов на базе растёт, лаг ответа растёт, запросы не отвечают вовремя (в лимиты, прописанные разрабами на своей стороне), и контейнеры (даже живые) начинают схлапываться, потому что думают, что они умирают (раз не получили ответа от БД), оркестратор спаунит новые, которые уже ничего не получают от базы, кроме коннекта, и всё. Спираль смерти.

    У вас будет от 30 секунд до 3 минут, чтобы из неё выйти, или у вас, с высокой вероятностью, хлопнется база - а это уже серьёзный залёт, даже если вам есть куда переключиться после такого шторма.
    "умри быстро" надо применять с умом, нельзя этот совет применять бездумно. Это точно не серебряная пуля.


    1. razon Автор
      02.10.2023 16:59

      в вашем случае умирать быстро должны подключения и по цепочке запросы их инициализирующие, далее должен сработать circuit breaker. К сожалению, любой подход или паттерн можно понять и использовать искаженным образом — в общем случае, это не означает что принцип не верен