Транспорт бэкенда

Backpressure и Flow Control

Черная пятница. Нагрузка выросла в 10x. Сервис обработки заказов начинает тормозить. Каждый запрос ждёт в unbounded очереди всё дольше - latency растёт с 50ms до 30 секунд. Через 5 минут очередь в памяти занимает 16GB - OOM. Сервис упал. Все заказы потеряны. Backpressure и bounded queues - страховочная сетка против этого.

  • **Netflix** Hystrix и adaptive concurrency limiting отклоняют запросы при перегрузке вместо полного краша. В 2012 этот механизм спас сервис во время регионального AWS сбоя.
  • **Node.js Streams** с автоматическим backpressure через `pipe()` - стандарт для обработки больших файлов без загрузки в память. Без backpressure `fs.readFile()` + `res.write()` убьёт процесс на 10GB файле.
  • **Kafka** Consumer Lag - метрика backpressure на уровне брокера. Если lag растёт - потребители не справляются. Решение: scale out consumers или уменьшить producer rate.

Что такое backpressure

Backpressure - механизм когда потребитель сигнализирует производителю «медленнее». Без backpressure быстрый производитель переполняет буферы медленного потребителя: память исчерпывается, система падает. Backpressure - это контроль потока от получателя к отправителю.

Twitter в 2013 open-source'ил понятие backpressure применительно к Reactive Streams. Проблема встречается везде: Kafka consumer lag, Node.js writable streams, gRPC flow control, TCP receiver window.

Система с unbounded queue начинает потреблять всю доступную память. Это называется:

TCP Flow Control и Window

TCP имеет встроенный backpressure: receive window (rwnd). Получатель сообщает отправителю сколько байт он может принять прямо сейчас. Если буфер заполнен - rwnd=0, отправитель ждёт. Это механизм уровня ядра ОС, прозрачный для приложений.

HTTP/2 добавляет flow control на уровне потоков (streams): каждый stream имеет свой window. Это позволяет контролировать приоритет разных запросов независимо. gRPC использует HTTP/2 flow control для gRPC-stream методов.

TCP receive window (rwnd) = 0. Что должен делать отправитель?

Reactive Streams и RxJS

Reactive Streams (2013) - спецификация для async stream processing с backpressure: Publisher, Subscriber, Subscription, Processor. В Java: Project Reactor (Spring WebFlux), RxJava. В Node.js: RxJS, встроенные Streams. В Go: channels с буфером.

Kotlin Coroutines Flow, Java 9 Flow API, Akka Streams - все реализуют Reactive Streams спецификацию и совместимы между собой. Netflix использует RxJava для orchestration API responses на бэкенде.

Пользователь быстро печатает в поиске. Какой RxJS оператор правильно применить для ограничения API вызовов?

Bounded Queues и стратегии отклонения

Bounded queue - очередь с ограниченным размером. При достижении лимита производитель должен принять решение: заблокироваться, бросить исключение, или применить стратегию overflow. Правильная стратегия зависит от требований к latency и data loss.

Java ThreadPoolExecutor с LinkedBlockingQueue(capacity) - классический пример bounded queue. DiscardOldestPolicy удаляет самое старое задание при переполнении - подходит для real-time данных где свежесть важнее полноты.

Сервис обрабатывает live-данные с биржевого стакана. Очередь заполнена. Какая стратегия overflow правильная?

Load Shedding

Load Shedding - осознанное отбрасывание запросов при перегрузке для защиты системы. Лучше отклонить 10% запросов с 503, чем упасть целиком под нагрузкой. Google SRE рекомендует load shedding как первую линию защиты при перегрузке.

Google CRE (Customer Reliability Engineering) рекомендует: при нагрузке > 70% CPU активировать load shedding. Лучше отклонять запросы детерминированно, чем иметь непредсказуемый рост latency под нагрузкой.

Backpressure - это только про медленных потребителей, нужно просто добавить серверов

Backpressure - фундаментальный принцип: система должна уметь сигнализировать 'медленнее' на всех уровнях. Добавление серверов помогает, но без backpressure новые серверы тоже упадут.

Горизонтальное масштабирование не устраняет backpressure: если downstream БД не масштабируется, добавление app-серверов только увеличит нагрузку на БД. Bounded queues и load shedding нужны независимо от числа серверов.

Почему Load Shedding (отклонение 503) предпочтительнее, чем ставить все запросы в очередь при перегрузке?

Итоги

  • **Backpressure** - сигнал от потребителя производителю: 'медленнее'. Встроен в TCP (rwnd), HTTP/2 (flow control), Reactive Streams, Kafka consumer groups.
  • **Bounded queues** - единственная защита от OOM при перегрузке. Стратегия overflow зависит от домена: block для важных данных, DiscardOldest для real-time.
  • **Load Shedding** - осознанное отклонение с 503 лучше полного краша. Priority-based shedding сначала отклоняет low-priority запросы.

Связанные темы

Backpressure пронизывает все уровни стека от TCP до application layer:

  • TCP и HTTP/2 — TCP receive window - встроенный backpressure. HTTP/2 добавляет stream-level flow control поверх TCP
  • Kafka: producer и consumer — Consumer lag - application-level backpressure индикатор. max.in.flight.requests и linger.ms контролируют producer flow

Вопросы для размышления

  • Как обнаружить, что система испытывает backpressure проблемы, до того как она упадёт?
  • В каких случаях лучше блокировать производителя (blocking), а не дропать данные при перегрузке?
  • Как организовать priority-based load shedding в microservices где нагрузка приходит через единый API Gateway?

Связанные уроки

  • alg-20-greedy
Backpressure и Flow Control

0

1

Войти