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