В теории игр 🎲 есть знаменитая задача: "дилемма заключённого". Её суть проста: когда каждый участник действует только в своих интересах, не доверяя другим, результат для всех оказывается хуже, чем если бы они договорились.
Изучая вопрос подключения к очередному API, я понял, что при построении распределённых систем эта проблема из теории вполне может перейти в области практики.
Иллюстрацию этой мысли предлагаю рассмотреть на примере стриминговой платформы (вроде Netflix). Итак, у нас есть:
🧠 "Мозг" — единая система рекомендаций с ML-моделью внутри. Она знает, что вам предложить, но может обработать только 1000 запросов в секунду.
📺 "Лента" — главная страница с подборками фильмов.
🔎 "Поиск" — сервис, который находит фильмы по вашему запросу и заодно показывает "похожие".
За каждую из этих частей отвечает своя команда разработчиков.
В обычное время всё идёт по плану: "Лента" и "Поиск" вместе отправляют меньше 1000 запросов, и "Мозг" справляется.
Но вот выходит новый сезон популярного сериала. Все начинают его спешно искать. Нагрузка на "Поиск" резко возрастает. Он начинает отправлять к "Мозгу" всё больше и больше запросов. "Мозг" перегружается и начинает отвечать медленнее.
⚖️ В чём подвох?
Команды "Поиска" и "Ленты" смотрят на свои графики и видят проблему: их сервисы тормозят, потому что ждут ответа от "Мозга". У команд есть два пути:
1⃣ Подождать. Продолжать действовать по правилу: если ответ не пришёл сразу, повторную попытку выполнить с некоторой задержкой. Этот вариант поможет снизить нагрузку, но точно не решит вопрос здесь и сейчас.
2⃣ Поторопиться. Начать повторять запросы быстрее, не откладывая повторы на потом. Это не очень честно по отношению к остальным, но так их собственный сервис будет работать шустрее и потенциально покажет результат пользователю первым.
Здесь и кроется ловушка. Рациональный страх оказаться в отстающих толкает обе команды выбрать второй путь. Они начинают "досить" нагруженный "Мозг" запросами, чтобы получить свой кусок пирога.
🌪️ Шторм запросов
В итоге
🤔 Мысли и выводы
Описанный пример, конечно, искусственный, но он отлично показывает уязвимость. Если система позволяет своим частям действовать абсолютно независимо, это может привести к общему провалу.
Вывод: не стоит полагаться на добрую волю и координацию причастных команд, а необходимо встраивать защиту от таких сценариев в саму архитектуру. В первую очередь речь про Rate Limiter с отдельными лимитами по API-ключу. А далее уже можно смотреть в сторону приоритезации потребителей, обратного давление (Back pressure), предохранителя (Circuit Breaker) и других штуковин.
#архитектура #проектирование #интеграции #сервисы #кейсы
