АНАТОМИЯ ФИЧИ: Graceful Degradation на примере Uber. Как система решает, чей запрос выбросить?
Load shedding (сброс нагрузки) выглядит бесплатным: перегрузились – отклоняем лишнее. Но отклонение – тоже работа: принять соединение, распарсить, решить, ответить.
Под трёхкратной перегрузкой наивный шеддинг сжигает CPU на отказы, goodput уходит в ноль, и система стабильно живёт в этом состоянии.
Uber уткнулся в это и построил Cinnamon.
🔬 Поверхностный слой – что наблюдаемо
Сервис под Cinnamon держит 300 % перегрузки при росте p50 на 50 %. Скачок с 3 000 до 6 500 RPS: доля сброса ползёт до ~75 % и выходит на плато за 10 секунд, разброс в стационаре ~10 п.п. и дальше не растёт.
Тот же сервис на CoDel/AIMD осциллирует между "отклоняю всё" и "не отклоняю ничего", разброс раздувается до ~30 п.п. Без шеддинга – death spiral: инстансы валятся по health-check, автоскейл не успевает.
⚙️ Средний слой – что такое Cinnamon
6 tiers × 128 cohorts = 768 приоритетов.
🔹 Tier – бизнес-критичность: t0 – критичная инфраструктура, t1 – онлайн-трафик пользователя (заказать поездку), вниз до t5 (фоновое обновление карты). Приоритет ставится на edge и едет в context по всей цепочке вызовов.
🔹 Cohort – шардинг по пользователю, идея заимствована у WeChat. Надо сбросить 5 % трафика tier 1 – сбрасывается один и тот же набор пользователей на всех сервисах цепочки.
🔹 Rejector – сравнивает приоритет запроса с атомарным порогом. Оверхед ~1 микросекунда.
🔹 PID-контроллер – раз в ~500 мс пересчитывает долю сброса по истории за ~30 секунд.
🧬 Глубокий слой – почему так
🔸 Почему когорты. Если каждый из 5 хопов независимо сбрасывает свои случайные 5 %, пользователь получает 1 − 0,95⁵ ≈ 23 % отказов вместо 5 %: некогерентный шеддинг перемножает вероятности по цепочке. Когорта делает решение детерминированным – отвергнутый на первом хопе отвергается и на пятом, остальные проходят цепочку целиком.
🔸 Почему PID. CoDel смотрит только на текущее состояние очереди и потому качается между крайностями. PID помнит историю и находит стабильную долю сброса – отсюда плато за 10 секунд.
🔸 Почему 1 мкс важнее алгоритма. Шеддинг обязан быть дешевле работы, которую он спасает. Иначе congestive failure: throughput после его включения падает ниже, чем был до.
🤝 Сброс нагрузки в других системах
🔸 Meta Defcon – единица деградации это knob: фича с именем, владельцем и oncall. Уровни L1–L3, цели 5/10/20 % экономии ресурсов. Дёргает человек, реакция – минуты.
🔸 Netflix – 4 корзины по модели Linux tc-prio плюс фолбэки. Инцидент 2024: сброшено больше 50 % запросов, playback держался выше 99,4 %.
🔸 Google – criticality как first-class-поле RPC-стека с автопропагацией и adaptive throttling на стороне клиента: клиент сам перестаёт слать.
🔸 Amazon – отказался от фолбэков: код гниёт годами и в аварию делает хуже. Ставка на constant work и static stability: объём работы всегда одинаков, всплеску неоткуда взяться.
🔸 Envoy – adaptive concurrency: gradient-контроллер подбирает лимит по латентности. Приоритетов нет вовсе – для них нужна очередь, а очередь сама влияет на латентность.
Алгоритм сброса (PID, CoDel, gradient) – верхний и самый сменяемый слой. Решение принимается ниже: что считать единицей деградации.
Uber выбрал пользователя, Meta – фичу, Netflix и Google – запрос, Envoy – ничего, Amazon – постоянство работы вместо деградации.
От этого выбора зависит, кто дёргает рычаг, за секунды или за минуты, и что увидит пользователь на том конце.
📚 Что почитать:
"Cinnamon: Using Century Old Tech to Build a Mean Load Shedder" – Uber Engineering Blog
"Defcon: Preventing Overload with Graceful Feature Degradation" – Meza et al., OSDI'23
"Enhancing Netflix Reliability with Service-Level Prioritized Load Shedding" – Netflix TechBlog, 2024