Историю про аварию все рассказывают одинаково: добили её повторы. Сервис замедлился, каждый клиент попробовал ещё раз, лишняя нагрузка легла на машину, которая и так не успевает. Дальше оно не поднимается, пока не выключишь клиентов. Я верил в это достаточно, чтобы повторить на архитектурном ревью. И ни разу не видел тех же чисел с выключенными повторами.
Поэтому собрал самый маленький стенд, на котором эффект вообще виден. Сервис держит 20 запросов одновременно по 20 миллисекунд каждый, то есть потолок 1000 в секунду. Перед ним очередь на 100. Всё, что приходит на полную очередь, сразу получает 503. Клиент выпускает запросы по расписанию, а не ждёт, пока освободится предыдущий, с таймаутом 100 миллисекунд, потому что именно так выглядит толпа пользователей.
Дальше я загнал нагрузку за потолок с выключенными повторами. Это и есть та половина опыта, которую я раньше не удосужился прогнать.
При 800 в секунду проходит всё. При 1000 в секунду тоже проходит всё, с медианой 58 миллисекунд. При 1200 в секунду, то есть при нагрузке на 20 процентов больше, полезный выход падает с 909 в секунду до 65. Это в 14 раз хуже от роста в 1.2 раза. Никто ничего не повторяет.
Сервис при этом не сломан. За 6.6 секунды прогона он доводит до конца 6067 запросов, это около 920 в секунду, почти тот же потолок, что был всегда. Просто 5627 из этих 6067 завершений, то есть 93 процента, пишутся клиентам, которые уже сдались и ушли. Очередь держит 100 запросов, сервис разгребает 1000 в секунду, значит запрос, попавший в хвост очереди, ждёт около 100 миллисекунд. Ровно столько, сколько клиент выставил таймаутом. Всё, что дальше этой точки, это работа ни для кого.
Теперь включаем повторы
Тот же стенд, те же нагрузки, каждый отказ повторяется до 2 раз.
Трафик идёт с 7200 попыток до 20701, это в 2.88 раза. При 1500 поданных выходит 2.95 раза. Отказы 503 растут с 1133 до 14521. А полезный выход идёт с 65 в секунду до 76.
Вверх. Не вниз. Повторы в этом стенде купили 11 лишних успехов в секунду, утроив трафик, потому что очередь и так была полной. Лишние попытки отбивались на входе за микросекунду каждая, не трогая 20 рабочих мест. В моей истории причина и попутчик стояли наоборот: обвал устроила встреча таймаута с очередью. Шторм повторов ехал сверху и то число, которое важно, не ухудшил.
Хочу быть аккуратным насчёт того, как далеко это несёт. Отказ здесь дешёвый, один системный вызов и 503, а за ним ограниченная очередь. В сервисе, где путь переполнения стоит настоящей работы, соединения с базой или потока, тот же тройной трафик идёт прямиком в то место, которое и так узкое. Там я жду, что знак перевернётся. Эту версию опыта я не прогнал.
Число, которое я чуть не пропустил
Сервер всё это время выглядел здоровым. Запросы завершались, темп завершений держался у потолка, задержка внутри сервиса не менялась вообще, потому что единица работы по-прежнему занимала свои 20 миллисекунд. Проблему я увидел только после того, как завёл счётчик ответов, записанных клиенту, который уже отвалился. Пока этого счётчика не было, график со стороны сервиса показывал 920 в секунду, а график со стороны клиента 65. Оба были верны.
Чего я не проверял
Разнос повторов по времени, а это очевидное следующее плечо: здесь повторы бьют мгновенно, а разносить их и есть тот совет, который даёт фольклор. Бюджет повторов, который ограничивает попытки долей от трафика, а не числом на запрос. Закрытую нагрузку, где пользователи ждут, а не подсыпают: одно это убирает почти всё усиление. И настоящую сеть между двумя сторонами, со своей очередью, которую я не моделировал никак.
Узкое утверждение про то, за какую ручку хвататься. Если очередь перед сервисом вмещает больше работы, чем клиент согласен ждать, сервис проведёт аварию, доделывая запросы для ушедших. Делать это он будет независимо от того, повторяет кто-нибудь или нет.