Back Pressure: почему упасть иногда важнее, чем работать
В мире распределенных систем есть одна простая истина: если один сервис может работать быстрее другого, он его рано или поздно убьет. Если вы засыпаете другие сервисы запросами / сообщениями больше, чем они могут проглотить – то вся система рискует выйти из строя.
Чтобы этого не происходило, умные системы используют механизм обратного давления (back pressure). Это не просто ограничение, это встроенный в протокол или архитектуру способ для потребителя сказать производителю: "Эй, я не успеваю, притормози!".
Это фундаментальный принцип построения отказоустойчивых систем. Давайте посмотрим на два примера.
⚡️ Back Pressure в HTTP: паттерн Circuit Breaker
В мире синхронных HTTP-запросов для обеспечения back pressure принято использовать паттерн Circuit Breaker ("Автоматический выключатель").
Представьте, Сервис А вызывает Сервис Б по HTTP. Сервис Б начинает тормозить или отвечать ошибками из-за нагрузки или сбоя в базе данных.
Что сделает наивный Сервис А?
Он будет долбиться в Сервис Б снова и снова, возможно, с ретраями. Если экземпляров Сервиса А много, они создадут "грозовую толпу" (thundering herd), которая окончательно добьет больной Сервис Б, не давая ему ни шанса на восстановление.
Как работает Circuit Breaker?
На самом деле это очень простой переключатель с тремя состояниями:
- Closed (Замкнут): Все хорошо, запросы свободно уходят в Сервис Б.
- Open (Разомкнут): Счетчик ошибок превысил порог (например, 5 ошибок за 10 секунд). "Пробка вылетает". В течение следующих 30 секунд Сервис А даже не пытается отправить запрос в Сервис Б, а сразу возвращает ошибку. Это и есть back pressure! Мы даем Сервису Б время на восстановление, не заваливая его запросами.
- Half-Open (Полуоткрыт): Через 30 секунд выключатель переходит в это состояние и пропускает один-два "пробных" запроса. Если они проходят успешно — цепь замыкается (Closed). Если нет — снова размыкается (Open) еще на 30 секунд.
🌊 Back Pressure в брокерах: NATS JetStream
Представьте, у вас есть сервис, который генерирует события (например, клики пользователей), и второй сервис, который их обрабатывает (считает аналитику, отправляет в БД). Продюсер может генерировать 10 000 сообщений в секунду, а консьюмер — разгребать только 1000.
Что произойдет без back pressure?
Сообщения будут копиться в очереди брокера, потребитель не будет успевать их разгребать. Отставание в обработке будет накапливаться – минуты, час, дни. В итоге вся система будет выполнять уже не актуальную работу и приносить ровно 0 пользы. А потом и память брокера переполнится – и умрет совсем все
Как решает проблему NATS JetStream?
У стримов в NATS есть две важные опции:
max_msgs (`max_bytes`) и DiscardPolicy. Используя эти настройки можно добиться разного поведения, но здесь нас интересует DiscardPolicy.NEW
from faststream.nats import DiscardPolicy, JStream
stream = JStream(
"stream",
discard=DiscardPolicy.NEW,
max_msgs=1000,
# max_bytes=1024 * 1024 * 1024,
)
Когда стрим заполняется до указанного предела, продюсер при попытке сделать
publish() получит ошибку. Это и есть наш back pressure!Продюсер вынужден либо сбросить темп, либо отложить отправку, либо сбросить часть данных. Но главное – он знает, что с другой стороны не успевают. Потребитель защищен на уровне инфраструктуры.
🛡️ Итог
Back pressure — это не какая-то конкретная фича, а философия проектирования. Вместо того чтобы эгоистично пушить данные до тех пор, пока кто-то не умрет, система с обратным давлением умеет адаптироваться к скорости самого медленного звена, что обеспечивает ее выживаемость в критических условиях.
Лучше временно деградировать в производительности или ответить ошибкой, чем вызвать каскадное падение, которое обрушит всё.
#архитектура #программирование