Типовые причины сбоев (и почему это чаще не "баг")

Когда сервис падает, первая реакция неинженерной аудитории часто звучит одинаково: "программисты написали плохой код и плохо протестировали его перед выкаткой на прод". Это настолько естественная и распространенная мысль, что она проникает в корпоративную культуру, формирует политику найма и увольнений, создает атмосферу страха и поиска виноватых. И эта причина почти всегда не имеет отношения к реальности.

Конечно, баги существовали, существуют и будут существовать. Ошибки в коде случаются, и иногда они приводят к катастрофическим последствиям. Но в зрелых системах, прошедших через кодревью, тестирование и этапные развертывания, чисто программная ошибка как причина масштабного сбоя - явление очень редкое. Гораздо чаще за падением сервиса стоит сложное переплетение факторов и случайных событий, каждый из которых по отдельности мог бы и не привести к катастрофе.

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

В прошлой главе "Почему большинство инцидентов вызвано изменениями?" я уже писал, что основная причина инцидентов - это изменения. Это аксиома SRE: любое изменение в работающей системе несет риск. Новый код, обновление конфигурации, включение фичи, смена версии библиотеки и т.п. - все это потенциальные источники проблем. Примерно семьдесят процентов сбоев так или иначе связаны с изменениями. Но остаются тридцать процентов, и там начинается самое интересное.

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

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

Проблемы масштабирования часто наступают незаметно. Они не приходят со звонком и красным алертом. Просто время ответа начинает понемногу расти по какой-то непонятной причине. Потом еще немного. Потом еще. А потом система падает, потому что какой-то компонент не выдержал. И виноват в этом не разработчик этого кода, а физика и математика.

Третья категория это зависимости. Современный софт это гигантская паутина взаимосвязей. Ваш сервис вызывает API другого сервиса, тот обращается к базе данных, база данных реплицируется в другой датацентр, все это работает на облачной инфраструктуре, которая сама зависит от тысяч других систем.

Когда падает зависимость, падаете и вы. И в этом нет вашей вины (и вины разработчиков тоже).

Такие инциденты особенно обидны, потому что вы не можете на них повлиять напрямую. AWS теряет доступность в целом регионе и и тысячи сервисов по всему миру ложатся пачкой. Провайдер CDN ловит инцидент и ваши картинки перестают загружаться. Сторонний API, который вы используете для платежей, отвечает ошибками и ваши пользователи не могут купить ваш продукт.

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

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

Одна неверная запятая в JSON-файле может положить всю систему. Неправильно указанный путь к сертификату и TLS перестает работать. Слишком агрессивный таймаут и начинают падать запросы, которые просто чуть дольше обрабатываются. Слишком мягкий таймаут и система зависает в ожидании ответа.

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

Человеческий фактор это отдельная большая тема. Но важно понимать, что в культуре SRE "человеческий фактор" - это не про "человек ошибся, уволить его". Это про "почему система позволила человеку ошибиться с такими последствиями".

Опытный инженер, работающий в стрессовой ситуации, может перепутать продакшен и тестовый сервер. Может выполнить не ту команду. Может не заметить предупреждение в логах, потому что предупреждений слишком много. Может ошибиться в расчетах, когда вручную масштабирует кластер. Не существует таких инженеров, которые не ошибаются.

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

Хорошая система проектируется в расчете на то, что люди устают, отвлекаются, ошибаются. Она защищает их от них самих. Плохая система рассчитывает на идеальных, вечно внимательных операторов. Повторюсь - таких не существует.

И наконец, пятая категория это какие-то внешние события. Это то, что вообще не зависит от вас, вашего кода, ваших серверов и ваших инженеров.

Ураган, отключающий электричество в целом регионе. Пожар в дата-центре. DDoS-атака, которая переполняет каналы. Сбой у магистрального интернет-провайдера. Авария на подстанции. Солнечная вспышка, которая создает помехи в спутниковой связи. Ракета, прилетевшая в ЦОД. Звучит как теория заговора, но такие инциденты случаются регулярно, просто вы о них очень редко слышите, потому что хорошие инженеры строят защиту и от них.

Защита от внешних событий это геораспределение, резервирование каналов связи, продуманная стратегия disaster recovery. Это дорого, сложно и часто кажется избыточным и на это никогда нет денег и человеко-часов. Но все это до тех пор, пока не случится то самое внешнее событие.

Важно понимать, что в реальном инциденте редко работает только одна причина. Обычно это цепочка. Изменение выявило проблему масштабирования, которая привела к перегрузке зависимости, на которую неправильно среагировал оператор, потому что конфигурация алертов была сбита. И каждый элемент этой цепочки по отдельности мог бы не убить систему, но вместе они создают идеальный шторм.

Именно поэтому поиск одного виноватого это не просто несправедливо, это вредно для будущей надежности. Если вы уволите инженера, который ошибся, вы ничего не измените в системе. Она останется такой же хрупкой. Следующий инженер ошибется в том же месте, потому что интерфейс все так же не защищает от ошибок. И вы уволите еще одного. И еще.

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

Поэтому когда в следующий раз случится сбой, не спешите искать крайнего. Спросите себя: что в нашей системе позволило этому сбою произойти? И готовьтесь к тому, что ответ будет сложным и неудобным. Зато он будет правдивым.

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