Идемпотентность: что произойдёт, если запрос придёт дважды
Пользователь нажал кнопку «Оплатить».
Ответ не пришёл.
Он нажал ещё раз.
А сеть решила тоже поучаствовать и повторила первый запрос.
Что должно произойти?
Правильный ответ обычно не такой:
«Будем надеяться, что второй запрос не успеет».
Для таких ситуаций существует идемпотентность.
Если упростить, идемпотентная операция при повторном выполнении приводит систему к тому же конечному состоянию, что и при первом.
Это особенно важно там, где повтор стоит дорого:
— списание денег;
— создание заказа;
— начисление бонусов;
— отправка сообщения;
— обработка события из очереди.
Например, клиент отправляет запрос на создание платежа вместе с
Сервис получает ключ, выполняет операцию и сохраняет результат.
Если запрос с тем же ключом приходит повторно, сервис не создаёт ещё один платёж, а возвращает результат уже выполненной операции.
Звучит просто.
Но дальше начинается архитектура.
Нужно решить:
— где хранить ключи;
— сколько времени их хранить;
— что делать, если первый запрос ещё выполняется;
— как проверить, что с тем же ключом пришёл действительно тот же запрос;
— какой ответ возвращать после частично выполненной операции;
— как пережить сбой между изменением данных и сохранением результата.
Например, недостаточно просто записать ключ после списания денег.
Сервис может успеть провести платёж, упасть до сохранения ключа, а после повтора провести платёж ещё раз.
Поэтому идемпотентность — это не заголовок в HTTP-запросе. Это согласованность ключа, состояния операции и её результата.
Особенно заметна эта проблема в распределённых системах.
Клиент повторяет операцию после тайм-аута.
Сеть повторно доставляет запрос.
Consumer получает одно событие несколько раз.
Формально хочется доставки «ровно один раз». Фактически обеспечить её по всей цепочке обычно намного сложнее, чем нарисовать на архитектурной диаграмме.
Поэтому хорошая система строится не вокруг надежды:
«Сообщение придёт один раз».
А вокруг гарантии:
«Если оно придёт ещё раз, ничего страшного не произойдёт».
Мы не пытаемся сделать распределённую систему идеальной.
Мы проектируем её так, чтобы ожидаемые сбои были безопасными.
Пользователь нажал кнопку «Оплатить».
Ответ не пришёл.
Он нажал ещё раз.
А сеть решила тоже поучаствовать и повторила первый запрос.
Что должно произойти?
Правильный ответ обычно не такой:
«Будем надеяться, что второй запрос не успеет».
Для таких ситуаций существует идемпотентность.
Если упростить, идемпотентная операция при повторном выполнении приводит систему к тому же конечному состоянию, что и при первом.
Это особенно важно там, где повтор стоит дорого:
— списание денег;
— создание заказа;
— начисление бонусов;
— отправка сообщения;
— обработка события из очереди.
Например, клиент отправляет запрос на создание платежа вместе с
Idempotency-Key.Сервис получает ключ, выполняет операцию и сохраняет результат.
Если запрос с тем же ключом приходит повторно, сервис не создаёт ещё один платёж, а возвращает результат уже выполненной операции.
Звучит просто.
Но дальше начинается архитектура.
Нужно решить:
— где хранить ключи;
— сколько времени их хранить;
— что делать, если первый запрос ещё выполняется;
— как проверить, что с тем же ключом пришёл действительно тот же запрос;
— какой ответ возвращать после частично выполненной операции;
— как пережить сбой между изменением данных и сохранением результата.
Например, недостаточно просто записать ключ после списания денег.
Сервис может успеть провести платёж, упасть до сохранения ключа, а после повтора провести платёж ещё раз.
Поэтому идемпотентность — это не заголовок в HTTP-запросе. Это согласованность ключа, состояния операции и её результата.
Особенно заметна эта проблема в распределённых системах.
Клиент повторяет операцию после тайм-аута.
Сеть повторно доставляет запрос.
Consumer получает одно событие несколько раз.
Формально хочется доставки «ровно один раз». Фактически обеспечить её по всей цепочке обычно намного сложнее, чем нарисовать на архитектурной диаграмме.
Поэтому хорошая система строится не вокруг надежды:
«Сообщение придёт один раз».
А вокруг гарантии:
«Если оно придёт ещё раз, ничего страшного не произойдёт».
Мы не пытаемся сделать распределённую систему идеальной.
Мы проектируем её так, чтобы ожидаемые сбои были безопасными.
👍1
AI может делать пять задач параллельно. Это не значит, что нужно запускать пять
AI-агенты почти убрали ограничение на скорость старта работы.
Человеку нужно разобраться в задаче, освободить время, переключить контекст. Агенту достаточно выдать ещё один промпт.
И вот уже через пару часов:
— открыто пять PR;
— три ждут ревью;
— два меняют одни и те же файлы;
— один устарел ещё до проверки;
— человек физически успевает нормально посмотреть только один.
Агент не ускорил поставку. Он просто быстрее создал очередь незавершённой работы.
Мы привыкли обсуждать WIP-лимиты как способ защитить разработчиков от многозадачности. С AI появляется другая причина: защищать нужно систему проверки.
У команды ограничена не только скорость написания кода. Ограничены ревью, тестирование, продуктовая проверка и способность принимать решения.
Поэтому количество одновременно запущенных агентов стоит считать не по доступным токенам и не по числу задач в бэклоге. Его нужно считать по пропускной способности самого узкого места.
Если команда может качественно проверить два изменения в день, десять параллельных агентов не дадут десятикратного ускорения. Они дадут восемь лишних PR в очереди.
Я бы начал с простых правил:
— не запускать новую задачу, пока предыдущая ждёт ревью;
— не давать агентам параллельно менять одну область кода;
— ограничивать число открытых AI-задач на разработчика или команду;
— следить не за количеством созданных PR, а за временем до проверки и мержа.
AI резко увеличивает мощность производства кода. Но скорость системы всё равно определяет этап, через который работа проходит медленнее всего.
WIP-лимит нужен не только людям. Он нужен системе проверки.
AI-агенты почти убрали ограничение на скорость старта работы.
Человеку нужно разобраться в задаче, освободить время, переключить контекст. Агенту достаточно выдать ещё один промпт.
И вот уже через пару часов:
— открыто пять PR;
— три ждут ревью;
— два меняют одни и те же файлы;
— один устарел ещё до проверки;
— человек физически успевает нормально посмотреть только один.
Агент не ускорил поставку. Он просто быстрее создал очередь незавершённой работы.
Мы привыкли обсуждать WIP-лимиты как способ защитить разработчиков от многозадачности. С AI появляется другая причина: защищать нужно систему проверки.
У команды ограничена не только скорость написания кода. Ограничены ревью, тестирование, продуктовая проверка и способность принимать решения.
Поэтому количество одновременно запущенных агентов стоит считать не по доступным токенам и не по числу задач в бэклоге. Его нужно считать по пропускной способности самого узкого места.
Если команда может качественно проверить два изменения в день, десять параллельных агентов не дадут десятикратного ускорения. Они дадут восемь лишних PR в очереди.
Я бы начал с простых правил:
— не запускать новую задачу, пока предыдущая ждёт ревью;
— не давать агентам параллельно менять одну область кода;
— ограничивать число открытых AI-задач на разработчика или команду;
— следить не за количеством созданных PR, а за временем до проверки и мержа.
AI резко увеличивает мощность производства кода. Но скорость системы всё равно определяет этап, через который работа проходит медленнее всего.
WIP-лимит нужен не только людям. Он нужен системе проверки.
👍1
Мы дали AI-агентам команду. Они изобрели проблемы командной работы
13 августа Anthropic опубликовала исследование о том, как группы AI-агентов взаимодействуют друг с другом.
В одном из экспериментов агенты 12 часов совместно разрабатывали браузерную игру. У каждого была своя виртуальная машина, общий форум и репозиторий.
Чем больше становилась команда, тем знакомее выглядел результат:
— конфликтующие PR;
— параллельные изменения одних файлов;
— брошенные ветки;
— низкая доля merge;
— проблемы интеграции;
— слабая координация.
У ранних моделей агенты активно работали с общим кодом, но плохо договаривались. PR конфликтовали и оставались без merge.
Более новые модели частично «решили» проблему очень человеческим способом: перестали мешать друг другу. Каждый агент сохранял почти полный ownership своих файлов, поэтому пересечений стало меньше.
Только Sonnet 5 смог одновременно поддерживать высокий уровень совместной работы с кодом и хороший throughput по PR.
Получается, что увеличение числа исполнителей само по себе не увеличивает скорость поставки.
Даже если исполнители кремниевые и не ходят на обед.
Мы уже знаем названия этих проблем:
— размытые границы ответственности;
— coordination overhead;
— конкуренция за общие ресурсы;
— слишком большой WIP;
— высокая стоимость интеграции.
Multi-agent системы не отменяют менеджмент. Они просто позволяют быстрее масштабировать не только выполнение работы, но и организационный бардак.
Похоже, AI-командам понадобятся те же механизмы, что и человеческим:
— понятный ownership;
— ограничение WIP;
— декомпозиция с минимальным пересечением;
— правила работы с общими ресурсами;
— явный порядок интеграции изменений.
Это хорошо продолжает мысль из прошлого поста: AI может выполнять пять задач параллельно. Но из этого не следует, что нужно одновременно запускать пять задач.
Параллельность увеличивает throughput только там, где система умеет переваривать результат.
Anthropic: Patterns and problems in emerging multiagent systems
13 августа Anthropic опубликовала исследование о том, как группы AI-агентов взаимодействуют друг с другом.
В одном из экспериментов агенты 12 часов совместно разрабатывали браузерную игру. У каждого была своя виртуальная машина, общий форум и репозиторий.
Чем больше становилась команда, тем знакомее выглядел результат:
— конфликтующие PR;
— параллельные изменения одних файлов;
— брошенные ветки;
— низкая доля merge;
— проблемы интеграции;
— слабая координация.
У ранних моделей агенты активно работали с общим кодом, но плохо договаривались. PR конфликтовали и оставались без merge.
Более новые модели частично «решили» проблему очень человеческим способом: перестали мешать друг другу. Каждый агент сохранял почти полный ownership своих файлов, поэтому пересечений стало меньше.
Только Sonnet 5 смог одновременно поддерживать высокий уровень совместной работы с кодом и хороший throughput по PR.
Получается, что увеличение числа исполнителей само по себе не увеличивает скорость поставки.
Даже если исполнители кремниевые и не ходят на обед.
Мы уже знаем названия этих проблем:
— размытые границы ответственности;
— coordination overhead;
— конкуренция за общие ресурсы;
— слишком большой WIP;
— высокая стоимость интеграции.
Multi-agent системы не отменяют менеджмент. Они просто позволяют быстрее масштабировать не только выполнение работы, но и организационный бардак.
Похоже, AI-командам понадобятся те же механизмы, что и человеческим:
— понятный ownership;
— ограничение WIP;
— декомпозиция с минимальным пересечением;
— правила работы с общими ресурсами;
— явный порядок интеграции изменений.
Это хорошо продолжает мысль из прошлого поста: AI может выполнять пять задач параллельно. Но из этого не следует, что нужно одновременно запускать пять задач.
Параллельность увеличивает throughput только там, где система умеет переваривать результат.
Anthropic: Patterns and problems in emerging multiagent systems
👍1
Система не обязана либо работать, либо лежать
У интернет-магазина сломался сервис рекомендаций.
Что должен увидеть пользователь?
Вариант А:
500 Internal Server Error
Вариант Б:
интернет-магазин без рекомендаций.
Почему-то распределённые системы регулярно выбирают вариант А. Одна необязательная зависимость не ответила — и ошибка отправилась вверх по цепочке, пока не уронила весь пользовательский сценарий.
Но не все функции продукта одинаково критичны.
Если второстепенный компонент недоступен, система может продолжить выполнять основную задачу в degraded mode — режиме ограниченной функциональности.
Например:
— нет рекомендаций — показываем популярные товары;
— нет актуальных остатков — используем последние известные данные с предупреждением, если бизнес допускает такой риск;
— не работает сервис уведомлений — сохраняем сообщения в очередь и отправляем позже;
— недоступна аналитика — не блокируем действия пользователя;
— не отвечает внешний партнёр — принимаем операцию и завершаем асинхронно, если это разрешено бизнес-процессом.
Технически здесь понадобятся таймауты, circuit breaker, очереди, кэш и fallback-сценарии.
Но сначала нужно ответить на продуктовый вопрос:
что действительно должно продолжать работать?
Для условного интернет-магазина приоритеты могут выглядеть так:
Tier 1 — авторизация, оформление заказа, платёж.
Tier 2 — каталог и поиск.
Tier 3 — рекомендации, отзывы и персонализация.
Это не универсальная классификация. В другом продукте поиск может быть главным сценарием, а авторизация вообще не требоваться для просмотра каталога.
Поэтому graceful degradation нельзя спроектировать силами backend-команды в вакууме. Нужна заранее согласованная иерархия функций:
— что нельзя потерять ни при каких условиях;
— что может работать с ограничениями;
— что можно временно отключить;
— где допустимы устаревшие данные;
— о каких ограничениях нужно сообщить пользователю.
Хорошая отказоустойчивость не означает «никогда не ломаться».
Она означает заранее понимать, что должно продолжить работать, когда что-то сломалось.
И degraded mode нужно проверять под реальными отказами: отключать зависимости, добавлять задержки, возвращать ошибки, останавливать обработчики очередей.
Иначе он существует только на архитектурной диаграмме. А диаграммы, как известно, падают заметно реже продакшена.
У интернет-магазина сломался сервис рекомендаций.
Что должен увидеть пользователь?
Вариант А:
500 Internal Server Error
Вариант Б:
интернет-магазин без рекомендаций.
Почему-то распределённые системы регулярно выбирают вариант А. Одна необязательная зависимость не ответила — и ошибка отправилась вверх по цепочке, пока не уронила весь пользовательский сценарий.
Но не все функции продукта одинаково критичны.
Если второстепенный компонент недоступен, система может продолжить выполнять основную задачу в degraded mode — режиме ограниченной функциональности.
Например:
— нет рекомендаций — показываем популярные товары;
— нет актуальных остатков — используем последние известные данные с предупреждением, если бизнес допускает такой риск;
— не работает сервис уведомлений — сохраняем сообщения в очередь и отправляем позже;
— недоступна аналитика — не блокируем действия пользователя;
— не отвечает внешний партнёр — принимаем операцию и завершаем асинхронно, если это разрешено бизнес-процессом.
Технически здесь понадобятся таймауты, circuit breaker, очереди, кэш и fallback-сценарии.
Но сначала нужно ответить на продуктовый вопрос:
что действительно должно продолжать работать?
Для условного интернет-магазина приоритеты могут выглядеть так:
Tier 1 — авторизация, оформление заказа, платёж.
Tier 2 — каталог и поиск.
Tier 3 — рекомендации, отзывы и персонализация.
Это не универсальная классификация. В другом продукте поиск может быть главным сценарием, а авторизация вообще не требоваться для просмотра каталога.
Поэтому graceful degradation нельзя спроектировать силами backend-команды в вакууме. Нужна заранее согласованная иерархия функций:
— что нельзя потерять ни при каких условиях;
— что может работать с ограничениями;
— что можно временно отключить;
— где допустимы устаревшие данные;
— о каких ограничениях нужно сообщить пользователю.
Хорошая отказоустойчивость не означает «никогда не ломаться».
Она означает заранее понимать, что должно продолжить работать, когда что-то сломалось.
И degraded mode нужно проверять под реальными отказами: отключать зависимости, добавлять задержки, возвращать ошибки, останавливать обработчики очередей.
Иначе он существует только на архитектурной диаграмме. А диаграммы, как известно, падают заметно реже продакшена.
Bus Factor: что произойдёт, если самый важный разработчик завтра исчезнет
В команде есть разработчик, который знает всё про платежи.
Любой сложный баг идёт к нему.
Любое изменение API ревьюит он.
На любой вопрос ответ: «Спроси Васю».
Кажется, у команды есть сильный эксперт.
На самом деле у неё ещё есть single point of failure.
Bus Factor показывает, сколько людей должно внезапно выпасть из работы, чтобы команда потеряла критичные знания и не смогла нормально продолжать проект.
Если без Васи нельзя безопасно изменить платежи, разобраться с инцидентом или выпустить релиз, Bus Factor этой области равен единице.
Сам по себе сильный эксперт — не проблема. Наоборот, глубокая экспертиза нужна команде.
Проблема начинается, когда знания эксперта не превращаются в знания системы.
Признаки обычно хорошо заметны:
— только один человек понимает, почему архитектура устроена именно так;
— его approval фактически обязателен для каждого изменения;
— во время его отпуска задачи встают или откладываются;
— документация заканчивается на «там всё понятно из кода»;
— остальные разработчики избегают области, потому что дешевле дождаться эксперта;
— после инцидента объяснение остаётся в созвоне и нигде не фиксируется.
Так появляется knowledge silo — область знаний, доступная одному человеку или узкой группе.
При этом формально ownership есть. Фактически это не владение системой, а монополия на её обслуживание.
Что можно сделать?
Разделять code ownership.
У критичной области должен быть основной владелец, но не единственный человек, способный внести изменение. Полезная проверка: кто примет решение, если владелец недоступен две недели?
Парно решать сложные задачи.
Не передавать эксперту очередную проблему целиком, а подключать второго разработчика к анализу, проектированию и реализации. Наблюдение за готовым решением передаёт гораздо меньше знаний, чем совместный поиск.
Ротировать ревьюеров.
Если все изменения годами проверяет один человек, команда тренирует зависимость от него. Сначала можно добавить второго ревьюера, затем постепенно передавать ему часть решений.
Использовать shadowing.
Разработчик сначала наблюдает за работой эксперта, потом выполняет похожую задачу вместе с ним, а затем делает её самостоятельно под наблюдением. Просто посидеть на одном созвоне рядом недостаточно.
Документировать не только “как”, но и “почему”.
Схему компонентов обычно восстановить можно. Гораздо сложнее понять, почему выбрали такой контракт, какие ограничения нельзя нарушать и где уже пробовали «простое решение», которое не сработало.
При этом «пусть Вася обучит всех» — тоже не стратегия.
Во-первых, Вася становится ещё большим bottleneck: теперь он одновременно разрабатывает, ревьюит, отвечает на вопросы и ведёт внутренний университет.
Во-вторых, знания без практики быстро превращаются в заметки, которые все читали и никто не может применить во время инцидента.
Передача экспертизы должна быть частью обычной работы: реальные задачи, совместные решения, ротация ответственности и проверка, что команда может действовать без подсказки.
И важное ограничение: снижать Bus Factor — не значит делать всех взаимозаменяемыми.
Не нужно, чтобы каждый разработчик знал всё про каждый сервис. Это дорого и почти невозможно.
Цель проще: критичная область не должна зависеть от доступности одного человека.
Сильный эксперт усиливает команду, когда рядом с ним растут другие владельцы. Если же без него останавливается работа, это уже не только экспертиза.
Это организационный риск.
В какой области вашей системы ответ на большинство вопросов до сих пор звучит как «спроси Васю»?
В команде есть разработчик, который знает всё про платежи.
Любой сложный баг идёт к нему.
Любое изменение API ревьюит он.
На любой вопрос ответ: «Спроси Васю».
Кажется, у команды есть сильный эксперт.
На самом деле у неё ещё есть single point of failure.
Bus Factor показывает, сколько людей должно внезапно выпасть из работы, чтобы команда потеряла критичные знания и не смогла нормально продолжать проект.
Если без Васи нельзя безопасно изменить платежи, разобраться с инцидентом или выпустить релиз, Bus Factor этой области равен единице.
Сам по себе сильный эксперт — не проблема. Наоборот, глубокая экспертиза нужна команде.
Проблема начинается, когда знания эксперта не превращаются в знания системы.
Признаки обычно хорошо заметны:
— только один человек понимает, почему архитектура устроена именно так;
— его approval фактически обязателен для каждого изменения;
— во время его отпуска задачи встают или откладываются;
— документация заканчивается на «там всё понятно из кода»;
— остальные разработчики избегают области, потому что дешевле дождаться эксперта;
— после инцидента объяснение остаётся в созвоне и нигде не фиксируется.
Так появляется knowledge silo — область знаний, доступная одному человеку или узкой группе.
При этом формально ownership есть. Фактически это не владение системой, а монополия на её обслуживание.
Что можно сделать?
Разделять code ownership.
У критичной области должен быть основной владелец, но не единственный человек, способный внести изменение. Полезная проверка: кто примет решение, если владелец недоступен две недели?
Парно решать сложные задачи.
Не передавать эксперту очередную проблему целиком, а подключать второго разработчика к анализу, проектированию и реализации. Наблюдение за готовым решением передаёт гораздо меньше знаний, чем совместный поиск.
Ротировать ревьюеров.
Если все изменения годами проверяет один человек, команда тренирует зависимость от него. Сначала можно добавить второго ревьюера, затем постепенно передавать ему часть решений.
Использовать shadowing.
Разработчик сначала наблюдает за работой эксперта, потом выполняет похожую задачу вместе с ним, а затем делает её самостоятельно под наблюдением. Просто посидеть на одном созвоне рядом недостаточно.
Документировать не только “как”, но и “почему”.
Схему компонентов обычно восстановить можно. Гораздо сложнее понять, почему выбрали такой контракт, какие ограничения нельзя нарушать и где уже пробовали «простое решение», которое не сработало.
При этом «пусть Вася обучит всех» — тоже не стратегия.
Во-первых, Вася становится ещё большим bottleneck: теперь он одновременно разрабатывает, ревьюит, отвечает на вопросы и ведёт внутренний университет.
Во-вторых, знания без практики быстро превращаются в заметки, которые все читали и никто не может применить во время инцидента.
Передача экспертизы должна быть частью обычной работы: реальные задачи, совместные решения, ротация ответственности и проверка, что команда может действовать без подсказки.
И важное ограничение: снижать Bus Factor — не значит делать всех взаимозаменяемыми.
Не нужно, чтобы каждый разработчик знал всё про каждый сервис. Это дорого и почти невозможно.
Цель проще: критичная область не должна зависеть от доступности одного человека.
Сильный эксперт усиливает команду, когда рядом с ним растут другие владельцы. Если же без него останавливается работа, это уже не только экспертиза.
Это организационный риск.
В какой области вашей системы ответ на большинство вопросов до сих пор звучит как «спроси Васю»?
Метрики без насилия
99,99% availability не всегда лучше 99,9%
Хотим availability 99,99%.
Звучит очевидно: чем больше девяток, тем надёжнее система. Тогда почему бы сразу не потребовать 99,99999%?
Потому что каждая следующая девятка стоит денег, времени и инженерных ограничений.
Она требует больше резервирования, более сложного мониторинга, быстрых механизмов восстановления и осторожных релизов. Иногда это оправданно. Иногда мы просто строим космический корабль для поездки в соседний магазин.
Надёжность должна определяться требованиями продукта, а не инженерным перфекционизмом.
Для этого обычно используют три понятия.
SLI — что именно измеряем.
Например, долю запросов, завершившихся успешно.
SLO — какой уровень хотим обеспечить.
Например: 99,9% успешных запросов за 30 дней.
Error budget — сколько ошибок можем допустить, не нарушив SLO.
Для SLO по времени доступности разница хорошо видна на цифрах:
— 99,9% оставляет около 43 минут недоступности за 30 дней;
— 99,99% — около 4 минут;
— 99,999% — примерно 26 секунд.
Перейти от 43 минут к четырём — не просто поправить число в документе. Системе может понадобиться другая архитектура, другой процесс релизов и совсем другая стоимость эксплуатации.
Но самое полезное в error budget даже не расчёт допустимых отказов.
Он связывает две конфликтующие силы.
Разработка хочет быстрее выпускать изменения, экспериментировать и проверять гипотезы.
Reliability требует стабильности, дополнительных проверок и меньшего количества рискованных изменений.
Без общей метрики спор быстро превращается в:
— Нам надо быстрее.
— Нам надо надёжнее.
С error budget появляется измеримая договорённость.
Если бюджет почти не расходуется, команда может позволить себе больше изменений.
Если он быстро сгорает, уменьшаем риск: замедляем релизы, разбираемся с причинами отказов и вкладываемся в восстановление.
Важное ограничение: error budget нельзя превращать в KPI разработчика.
Его задача — не найти человека, который «потратил наши минуты недоступности». Это не табель инженерной виновности.
Error budget ограничивает всю систему delivery: архитектуру, тестирование, релизы, мониторинг и восстановление после сбоев.
Надёжность тоже имеет стоимость.
Поэтому правильный вопрос звучит не так:
«Как сделать систему максимально надёжной?»
А так:
«Какой уровень надёжности нужен бизнесу и какой риск он готов принять?»
99,99% availability не всегда лучше 99,9%
Хотим availability 99,99%.
Звучит очевидно: чем больше девяток, тем надёжнее система. Тогда почему бы сразу не потребовать 99,99999%?
Потому что каждая следующая девятка стоит денег, времени и инженерных ограничений.
Она требует больше резервирования, более сложного мониторинга, быстрых механизмов восстановления и осторожных релизов. Иногда это оправданно. Иногда мы просто строим космический корабль для поездки в соседний магазин.
Надёжность должна определяться требованиями продукта, а не инженерным перфекционизмом.
Для этого обычно используют три понятия.
SLI — что именно измеряем.
Например, долю запросов, завершившихся успешно.
SLO — какой уровень хотим обеспечить.
Например: 99,9% успешных запросов за 30 дней.
Error budget — сколько ошибок можем допустить, не нарушив SLO.
Для SLO по времени доступности разница хорошо видна на цифрах:
— 99,9% оставляет около 43 минут недоступности за 30 дней;
— 99,99% — около 4 минут;
— 99,999% — примерно 26 секунд.
Перейти от 43 минут к четырём — не просто поправить число в документе. Системе может понадобиться другая архитектура, другой процесс релизов и совсем другая стоимость эксплуатации.
Но самое полезное в error budget даже не расчёт допустимых отказов.
Он связывает две конфликтующие силы.
Разработка хочет быстрее выпускать изменения, экспериментировать и проверять гипотезы.
Reliability требует стабильности, дополнительных проверок и меньшего количества рискованных изменений.
Без общей метрики спор быстро превращается в:
— Нам надо быстрее.
— Нам надо надёжнее.
С error budget появляется измеримая договорённость.
Если бюджет почти не расходуется, команда может позволить себе больше изменений.
Если он быстро сгорает, уменьшаем риск: замедляем релизы, разбираемся с причинами отказов и вкладываемся в восстановление.
Важное ограничение: error budget нельзя превращать в KPI разработчика.
Его задача — не найти человека, который «потратил наши минуты недоступности». Это не табель инженерной виновности.
Error budget ограничивает всю систему delivery: архитектуру, тестирование, релизы, мониторинг и восстановление после сбоев.
Надёжность тоже имеет стоимость.
Поэтому правильный вопрос звучит не так:
«Как сделать систему максимально надёжной?»
А так:
«Какой уровень надёжности нужен бизнесу и какой риск он готов принять?»
Retry не чинит ошибку. Иногда он делает её в сто раз больше
Сервис B не отвечает.
Сервис A делает retry.
Потом ещё один.
И ещё один.
Таких экземпляров A — сто. У каждого свой поток запросов и своё желание довести дело до конца.
B начинает оживать, но вместе с новыми запросами получает волну повторных.
Теперь он снова лежит.
Формально мы добавили отказоустойчивость. Фактически — организовали дополнительную нагрузку на сервис, который не справлялся даже с обычной.
Зачем тогда нужны retry?
Для временных сбоев. Соединение оборвалось, инстанс перезапустился, балансировщик отправил запрос на уже недоступную машину. Следующая попытка действительно может сработать.
Но если запрос не проходит валидацию, повтор не исправит данные. Если нет прав, настойчивость их не добавит. Если сервис перегружен, немедленный повтор может только ухудшить ситуацию.
Retry нужен не для гарантии успеха. Он нужен для обработки временных сбоев.
Поэтому «повторить при любой ошибке» — плохая политика. Нужны конкретные правила.
1. Разделить ошибки
Сетевой сбой, timeout, некоторые 5xx — кандидаты на повтор, а не автоматическое разрешение.
Для 429 нужно учитывать ограничение частоты и
Смотреть нужно не только на код ответа, но и на контракт операции: безопасно ли вообще выполнить её ещё раз?
2. Увеличивать паузу
Exponential backoff — это растущая задержка между попытками. После каждого сбоя ждём дольше, но не выше заданного предела.
Смысл простой: дать зависимости время восстановиться, а не проверять её здоровье непрерывным потоком запросов.
3. Добавить случайность
Если все экземпляры A получили ошибку одновременно и ждут одинаковое время, повторять они тоже придут одновременно.
Backoff без случайности может просто перенести пик нагрузки.
Jitter добавляет случайный разброс задержек, чтобы клиенты возвращались не строем.
Иначе возникает retry storm: ошибки вызывают повторы, повторы увеличивают нагрузку, нагрузка вызывает новые ошибки.
4. Не забыть про идемпотентность
Здесь возвращаемся к предыдущей теме.
Timeout означает, что клиент не дождался ответа. Он не означает, что сервер ничего не сделал.
Сервис мог списать деньги, сохранить заказ и потерять соединение до отправки ответа. Повторяем запрос — рискуем повторить действие.
Для такой операции нужен механизм идемпотентности. Например, ключ, по которому сервер распознаёт повтор и возвращает результат уже выполненной операции, не выполняя её заново.
Важно: ключ должен оставаться тем же на всех попытках. Новый ключ на каждый retry — это уже новые операции.
5. Ограничить попытки и время
У повторов должен быть конец: лимит попыток и общий дедлайн, включая ожидание между ними.
И стоит проверить, кто именно повторяет запрос. Если это одновременно делают приложение, SDK и прокси, число реальных обращений может оказаться сильно больше, чем ожидает разработчик.
Даже backoff и jitter не делают бесконечные повторы безопасными.
Хорошая retry-политика отвечает на четыре вопроса: что повторяем, безопасно ли это, сколько ждём и когда останавливаемся.
Но есть следующий уровень проблемы.
Если соседний сервис продолжает лежать, зачем каждому новому запросу заново проходить весь путь из таймаутов и повторов?
В какой момент стоит вообще перестать к нему ходить — и как понять, что уже можно вернуться?
Это уже про circuit breaker.
Сервис B не отвечает.
Сервис A делает retry.
Потом ещё один.
И ещё один.
Таких экземпляров A — сто. У каждого свой поток запросов и своё желание довести дело до конца.
B начинает оживать, но вместе с новыми запросами получает волну повторных.
Теперь он снова лежит.
Формально мы добавили отказоустойчивость. Фактически — организовали дополнительную нагрузку на сервис, который не справлялся даже с обычной.
Зачем тогда нужны retry?
Для временных сбоев. Соединение оборвалось, инстанс перезапустился, балансировщик отправил запрос на уже недоступную машину. Следующая попытка действительно может сработать.
Но если запрос не проходит валидацию, повтор не исправит данные. Если нет прав, настойчивость их не добавит. Если сервис перегружен, немедленный повтор может только ухудшить ситуацию.
Retry нужен не для гарантии успеха. Он нужен для обработки временных сбоев.
Поэтому «повторить при любой ошибке» — плохая политика. Нужны конкретные правила.
1. Разделить ошибки
Сетевой сбой, timeout, некоторые 5xx — кандидаты на повтор, а не автоматическое разрешение.
Для 429 нужно учитывать ограничение частоты и
Retry-After, если сервер его передал. Ошибки валидации и доступа бессмысленно повторять без изменения запроса или условий.Смотреть нужно не только на код ответа, но и на контракт операции: безопасно ли вообще выполнить её ещё раз?
2. Увеличивать паузу
Exponential backoff — это растущая задержка между попытками. После каждого сбоя ждём дольше, но не выше заданного предела.
Смысл простой: дать зависимости время восстановиться, а не проверять её здоровье непрерывным потоком запросов.
3. Добавить случайность
Если все экземпляры A получили ошибку одновременно и ждут одинаковое время, повторять они тоже придут одновременно.
Backoff без случайности может просто перенести пик нагрузки.
Jitter добавляет случайный разброс задержек, чтобы клиенты возвращались не строем.
Иначе возникает retry storm: ошибки вызывают повторы, повторы увеличивают нагрузку, нагрузка вызывает новые ошибки.
4. Не забыть про идемпотентность
Здесь возвращаемся к предыдущей теме.
Timeout означает, что клиент не дождался ответа. Он не означает, что сервер ничего не сделал.
Сервис мог списать деньги, сохранить заказ и потерять соединение до отправки ответа. Повторяем запрос — рискуем повторить действие.
Для такой операции нужен механизм идемпотентности. Например, ключ, по которому сервер распознаёт повтор и возвращает результат уже выполненной операции, не выполняя её заново.
Важно: ключ должен оставаться тем же на всех попытках. Новый ключ на каждый retry — это уже новые операции.
5. Ограничить попытки и время
У повторов должен быть конец: лимит попыток и общий дедлайн, включая ожидание между ними.
И стоит проверить, кто именно повторяет запрос. Если это одновременно делают приложение, SDK и прокси, число реальных обращений может оказаться сильно больше, чем ожидает разработчик.
Даже backoff и jitter не делают бесконечные повторы безопасными.
Хорошая retry-политика отвечает на четыре вопроса: что повторяем, безопасно ли это, сколько ждём и когда останавливаемся.
Но есть следующий уровень проблемы.
Если соседний сервис продолжает лежать, зачем каждому новому запросу заново проходить весь путь из таймаутов и повторов?
В какой момент стоит вообще перестать к нему ходить — и как понять, что уже можно вернуться?
Это уже про circuit breaker.
❤1👍1
Circuit Breaker: когда системе пора перестать пытаться
Recommendation Service лежит.
Но каждый пользовательский запрос всё равно пытается получить рекомендации:
— отправляет запрос;
— ждёт timeout;
— делает retry;
— снова ждёт.
Пользователь тем временем смотрит на spinner и размышляет, у какого конкурента каталог работает быстрее.
Retry полезен, когда сбой кратковременный. Но если сервис действительно недоступен, повторные попытки только увеличивают задержку и создают дополнительную нагрузку.
В этот момент нужен Circuit Breaker — автоматический выключатель между системой и проблемной зависимостью.
У него три состояния.
Closed
Всё работает нормально. Запросы отправляются в Recommendation Service, а Circuit Breaker следит за ошибками и задержками.
Единичный timeout ничего не меняет: сбои случаются. Но если ошибок становится слишком много, выключатель переходит в следующее состояние.
Open
Запросы в Recommendation Service временно не отправляются вообще.
Система не ждёт заведомый timeout и не запускает бессмысленные retry. Она сразу возвращает fallback или продолжает работу без рекомендаций.
Это важно не только для пользователя. Упавший сервис получает время на восстановление, а не новую волну запросов от клиентов, которые очень настойчиво проверяют, не стало ли ему лучше.
Half-open
Через некоторое время Circuit Breaker пропускает несколько пробных запросов.
Если они проходят успешно, соединение возвращается в состояние Closed. Если снова падают — выключатель открывается ещё на один период.
Получается простой цикл:
Closed → ошибок стало слишком много → Open → прошло время → Half-open → сервис восстановился или снова Open.
Но самое интересное здесь не сам паттерн.
Допустим, рекомендации недоступны. Действительно ли из-за этого должен перестать работать весь магазин?
Вместо бесконечного spinner можно:
— скрыть блок рекомендаций;
— показать популярные товары;
— использовать последние успешно рассчитанные данные;
— дать пользователю продолжить покупку без персонализации.
Circuit Breaker отвечает на вопрос: когда перестать обращаться к сломанной зависимости.
А архитектура системы должна ответить на следующий: что делать без неё.
Это уже graceful degradation — способность системы терять часть функций, но сохранять основную ценность для пользователя.
Какая необязательная зависимость у вас сегодня способна положить весь пользовательский сценарий?
Recommendation Service лежит.
Но каждый пользовательский запрос всё равно пытается получить рекомендации:
— отправляет запрос;
— ждёт timeout;
— делает retry;
— снова ждёт.
Пользователь тем временем смотрит на spinner и размышляет, у какого конкурента каталог работает быстрее.
Retry полезен, когда сбой кратковременный. Но если сервис действительно недоступен, повторные попытки только увеличивают задержку и создают дополнительную нагрузку.
В этот момент нужен Circuit Breaker — автоматический выключатель между системой и проблемной зависимостью.
У него три состояния.
Closed
Всё работает нормально. Запросы отправляются в Recommendation Service, а Circuit Breaker следит за ошибками и задержками.
Единичный timeout ничего не меняет: сбои случаются. Но если ошибок становится слишком много, выключатель переходит в следующее состояние.
Open
Запросы в Recommendation Service временно не отправляются вообще.
Система не ждёт заведомый timeout и не запускает бессмысленные retry. Она сразу возвращает fallback или продолжает работу без рекомендаций.
Это важно не только для пользователя. Упавший сервис получает время на восстановление, а не новую волну запросов от клиентов, которые очень настойчиво проверяют, не стало ли ему лучше.
Half-open
Через некоторое время Circuit Breaker пропускает несколько пробных запросов.
Если они проходят успешно, соединение возвращается в состояние Closed. Если снова падают — выключатель открывается ещё на один период.
Получается простой цикл:
Closed → ошибок стало слишком много → Open → прошло время → Half-open → сервис восстановился или снова Open.
Но самое интересное здесь не сам паттерн.
Допустим, рекомендации недоступны. Действительно ли из-за этого должен перестать работать весь магазин?
Вместо бесконечного spinner можно:
— скрыть блок рекомендаций;
— показать популярные товары;
— использовать последние успешно рассчитанные данные;
— дать пользователю продолжить покупку без персонализации.
Circuit Breaker отвечает на вопрос: когда перестать обращаться к сломанной зависимости.
А архитектура системы должна ответить на следующий: что делать без неё.
Это уже graceful degradation — способность системы терять часть функций, но сохранять основную ценность для пользователя.
Какая необязательная зависимость у вас сегодня способна положить весь пользовательский сценарий?
👍2
AI ускоряет junior. Но ускоряет ли он его развитие?
Мы уже знаем, что AI способен ускорять выполнение задач. Но скорость delivery и скорость обучения — разные метрики.
В январе Anthropic опубликовал рандомизированное исследование о том, как AI-помощь влияет на формирование навыков программирования.
52 разработчика, преимущественно junior, изучали незнакомую им Python-библиотеку Trio. Одна группа могла обращаться к AI, другая писала код самостоятельно. После задания участники прошли тест на понимание концепций, чтение кода и отладку.
Группа с AI закончила в среднем примерно на две минуты раньше. Разница оказалась статистически незначимой.
Зато результат теста отличался заметно: 50% у группы с AI против 67% у тех, кто писал самостоятельно. Самый большой разрыв был в вопросах на отладку.
Это не означает, что AI вреден для junior.
Интереснее другое: результат зависел от того, как именно разработчик использовал модель.
Хуже справлялись те, кто делегировал AI написание и исправление кода. Лучше — те, кто задавал концептуальные вопросы, просил объяснить решение и продолжал разбираться самостоятельно.
По сути, исследование показывает риск cognitive offloading: задача решена, но часть размышлений, которые должны были сформировать навык, выполнила модель.
Раньше обучение часто выглядело так:
задача → тупик → документация → попытка → ошибка → отладка → понимание.
Теперь легко получить более короткий маршрут:
задача → агент → решение.
Формально результат есть. Фактически из процесса могла исчезнуть половина учебной петли.
И здесь появляется новая ответственность тимлида: различать задачи, которые нужно оптимизировать на скорость, и задачи, в которых разработчику нужно оставить пространство для обучения.
Например:
— знакомую рутину можно смело делегировать агенту;
— при освоении новой технологии AI лучше использовать как наставника, а не генератор ответа;
— после сгенерированного решения полезно просить разработчика объяснить его и найти возможные ошибки;
— часть задач стоит разбирать без автоматической генерации кода.
Возможно, AI-native командам придётся сознательно создавать «неэффективные» учебные ситуации. Не запрещать инструменты, а проектировать способ работы с ними.
Потому что максимальная скорость выполнения задачи и максимальная скорость развития человека — не одно и то же.
А у вас AI для junior сейчас скорее исполнитель или наставник?
Исследование Anthropic о влиянии AI на формирование навыков программирования
Мы уже знаем, что AI способен ускорять выполнение задач. Но скорость delivery и скорость обучения — разные метрики.
В январе Anthropic опубликовал рандомизированное исследование о том, как AI-помощь влияет на формирование навыков программирования.
52 разработчика, преимущественно junior, изучали незнакомую им Python-библиотеку Trio. Одна группа могла обращаться к AI, другая писала код самостоятельно. После задания участники прошли тест на понимание концепций, чтение кода и отладку.
Группа с AI закончила в среднем примерно на две минуты раньше. Разница оказалась статистически незначимой.
Зато результат теста отличался заметно: 50% у группы с AI против 67% у тех, кто писал самостоятельно. Самый большой разрыв был в вопросах на отладку.
Это не означает, что AI вреден для junior.
Интереснее другое: результат зависел от того, как именно разработчик использовал модель.
Хуже справлялись те, кто делегировал AI написание и исправление кода. Лучше — те, кто задавал концептуальные вопросы, просил объяснить решение и продолжал разбираться самостоятельно.
По сути, исследование показывает риск cognitive offloading: задача решена, но часть размышлений, которые должны были сформировать навык, выполнила модель.
Раньше обучение часто выглядело так:
задача → тупик → документация → попытка → ошибка → отладка → понимание.
Теперь легко получить более короткий маршрут:
задача → агент → решение.
Формально результат есть. Фактически из процесса могла исчезнуть половина учебной петли.
И здесь появляется новая ответственность тимлида: различать задачи, которые нужно оптимизировать на скорость, и задачи, в которых разработчику нужно оставить пространство для обучения.
Например:
— знакомую рутину можно смело делегировать агенту;
— при освоении новой технологии AI лучше использовать как наставника, а не генератор ответа;
— после сгенерированного решения полезно просить разработчика объяснить его и найти возможные ошибки;
— часть задач стоит разбирать без автоматической генерации кода.
Возможно, AI-native командам придётся сознательно создавать «неэффективные» учебные ситуации. Не запрещать инструменты, а проектировать способ работы с ними.
Потому что максимальная скорость выполнения задачи и максимальная скорость развития человека — не одно и то же.
А у вас AI для junior сейчас скорее исполнитель или наставник?
Исследование Anthropic о влиянии AI на формирование навыков программирования
❤1👍1
Bus Factor AI-агента: кто понимает процесс, кроме него
Всю неделю мы говорили о долге понимания в коде. Но с AI он появляется не только там.
Представим, команда автоматизировала процесс через агента.
Агент знает нужные промпты, использует MCP, ходит во внутренние системы, выполняет инструкции и закрывает заметную часть работы.
Формально Bus Factor вырос: процесс больше не зависит от одного Васи.
Фактически Вася просто стал цифровым.
Раньше говорили:
«Это знает только Вася».
Теперь:
«Это делает агент. Лучше его не трогать».
Получился тот же knowledge silo, только теперь он спрятан между системным промптом, конфигурацией MCP и особенностями конкретной модели.
Я бы проверял такую автоматизацию несколькими вопросами:
— Где хранятся инструкции агента?
— Версионируются ли промпты и конфигурация?
— Понятно ли, какие инструменты он вызывает и с какими правами?
— Можно ли восстановить логику процесса по шагам?
— Кто владеет агентом и отвечает за изменения?
— Что сломается при смене модели?
— Сможет ли человек проверить результат и разобраться, почему агент принял такое решение?
Необязательно уметь вручную повторить каждое действие агента за то же время. Но команда должна понимать входы, решения, ограничения и точки контроля.
Иначе агент ускоряет процесс, одновременно увеличивая долг понимания. Всё работает быстро, пока не меняется модель, API, сотрудник или исключение в бизнес-правиле. После этого начинается археология, только раскапывать приходится не код, а цепочку промптов и вызовов инструментов.
Автоматизация должна убирать ручную работу, а не превращать знание процесса в чёрный ящик.
Если завтра ваш основной AI-агент перестанет запускаться, команда восстановит процесс по документации или начнёт изучать его поведение по логам?
Всю неделю мы говорили о долге понимания в коде. Но с AI он появляется не только там.
Представим, команда автоматизировала процесс через агента.
Агент знает нужные промпты, использует MCP, ходит во внутренние системы, выполняет инструкции и закрывает заметную часть работы.
Формально Bus Factor вырос: процесс больше не зависит от одного Васи.
Фактически Вася просто стал цифровым.
Раньше говорили:
«Это знает только Вася».
Теперь:
«Это делает агент. Лучше его не трогать».
Получился тот же knowledge silo, только теперь он спрятан между системным промптом, конфигурацией MCP и особенностями конкретной модели.
Я бы проверял такую автоматизацию несколькими вопросами:
— Где хранятся инструкции агента?
— Версионируются ли промпты и конфигурация?
— Понятно ли, какие инструменты он вызывает и с какими правами?
— Можно ли восстановить логику процесса по шагам?
— Кто владеет агентом и отвечает за изменения?
— Что сломается при смене модели?
— Сможет ли человек проверить результат и разобраться, почему агент принял такое решение?
Необязательно уметь вручную повторить каждое действие агента за то же время. Но команда должна понимать входы, решения, ограничения и точки контроля.
Иначе агент ускоряет процесс, одновременно увеличивая долг понимания. Всё работает быстро, пока не меняется модель, API, сотрудник или исключение в бизнес-правиле. После этого начинается археология, только раскапывать приходится не код, а цепочку промптов и вызовов инструментов.
Автоматизация должна убирать ручную работу, а не превращать знание процесса в чёрный ящик.
Если завтра ваш основной AI-агент перестанет запускаться, команда восстановит процесс по документации или начнёт изучать его поведение по логам?
👍1