Любой конвейер — это производитель, потребитель и буфер между ними. Пока производитель в среднем не быстрее потребителя, буфер дышит. Но стоит скоростям рассогласоваться — неограниченная очередь начинает не терять данные, а копить задержку: растёт время ответа, растёт память, и система валится по OOM или захлёбывается в bufferbloat.
Backpressure — это сигнал «замедлись», который течёт вверх по конвейеру: от перегруженного потребителя к быстрому производителю. Ограниченная очередь делает перегрузку явной и локальной, а дальше у системы ровно четыре реакции: заблокировать (платим задержкой), сбросить лишнее (платим потерями), деградировать качество или расшириться (платим деньгами).
Эти же кредиты и окна всплывают на всех уровнях — TCP rwnd и slow start, request(n) из Reactive Streams, credit-based flow control во Flink, окна HTTP/2, отставание потребителя в Kafka и адаптивные лимиты конкурентности Netflix. Глава связывает их с соседями про устойчивость и распределённые очереди и держится на наблюдаемости: без метрик очередей и lag перегрузка видна лишь постфактум.
Практическая польза главы
Ограничивайте очереди, а не наращивайте буфер
Большой буфер лишь откладывает OOM и ухудшает хвост задержек. Замените неограниченную очередь на ограниченную: переполнение и есть сигнал backpressure. По закону Литтла (L = λ × W) ограничение длины очереди жёстко ограничивает задержку при фиксированной пропускной способности.
Выберите валюту платежа осознанно
При перегрузке нельзя оптимизировать всё сразу. Блокируйте там, где данные нельзя терять (ETL, журналы), дропайте там, где важна свежесть и жив бюджет задержки (метрики, запросы у тайм-аута). Дропайте рано и по приоритету: head drop протухших полезнее tail drop свежих.
Предпочитайте pull и кредитную схему
Pull-модель (Kafka poll, request(n) из Reactive Streams) даёт backpressure по построению: нет запроса — нет отправки. Кредиты и окна — одна идея на всех уровнях: TCP rwnd, окна HTTP/2, кредиты Flink. Push без отдельного механизма обратного давления молча копит очередь до отказа.
На сервисах — адаптивные лимиты и наблюдаемость
Фиксированный RPS не знает текущей ёмкости; лимит конкурентности ближе к сути насыщения. Netflix concurrency-limits подбирает его по росту задержки (Vegas, Gradient2), как cwnd в TCP. Всё держится на метриках: длины очередей, consumer lag, насыщение пулов, доля сброшенной нагрузки.
Соседняя глава
Паттерны устойчивости
Тайм-ауты, размыкатель цепи и изоляция по отсекам защищают систему, когда перегрузка уже случилась. Обратное давление действует раньше — не даёт быстрому производителю утопить медленного потребителя.
Соседняя глава про собирает защитные паттерны на случай, когда что-то уже пошло не так: обрывает зависшие вызовы, перестаёт долбить упавшую зависимость, а не даёт одному перегруженному пулу утянуть за собой остальные. Это реакция на сбой.
Эта глава про слой ниже и раньше. Backpressure () — это сигнал «замедлись», который течёт вверх по конвейеру: от перегруженного к быстрому . Задача — не дать рассогласованию их скоростей превратиться в бесконечно растущую очередь, рост задержки и в итоге . Перегрузку лучше не допускать, чем потом гасить её размыкателями.
Естественный буфер между производителем и потребителем — это из соседней главы про распределённые очереди. Именно там видно, почему ограниченная очередь и — это не баг, а механизм давления: переполнение очереди и есть сигнал, что пора тормозить вход или сбрасывать лишнее.
и — это про честный ответ на вопрос «что делать, когда данных приходит больше, чем мы успеваем обработать». Вариантов ровно четыре: замедлить источник, сбросить часть нагрузки, деградировать качество или расшириться. Чего делать нельзя — это притворяться, будто буфер бесконечен.
Где живёт буфер
Распределённая очередь сообщений
Очередь — главный буфер конвейера. Глава про очереди показывает, как ограниченная ёмкость и отставание потребителя становятся сигналом давления.
Проблема: рассогласование скоростей и миф о «просто добавить буфер»
Любой конвейер — это производитель, потребитель и буфер между ними. Пока средняя скорость производителя не выше скорости потребителя, буфер дышит: то наполняется, то опустошается. Беда начинается, когда производитель устойчиво быстрее. Тогда буфер растёт монотонно, и вопрос лишь в том, что лопнет первым.
Неограниченная очередь → рост задержки
Если очередь не ограничена, она не теряет данные — она копит время. Каждый новый элемент ждёт всех предыдущих, и задержка от входа до обработки растёт линейно с длиной очереди. Система формально «работает», но отвечает всё медленнее.
Память → нехватка памяти (OOM) и каскад
Растущая очередь — это растущая память. Рано или поздно процесс упирается в лимит и падает по нехватке памяти (OOM), теряя всё содержимое буфера разом. Перезапуск встречает ту же нагрузку и падает снова — перегрузка превращается в краш-цикл.
Bufferbloat → бесполезная свежесть
Большие буферы по всему пути (bufferbloat) держат в полёте устаревшие данные. К моменту обработки запрос часто уже отменён по тайм-ауту на клиенте: мы тратим ресурсы на ответы, которые никто не ждёт.
Почему «просто добавить буфер» не работает: буфер сглаживает всплески, но не лечит устойчивое превышение скорости. Если производитель в среднем быстрее, буфер любого размера лишь откладывает момент отказа и увеличивает задержку к этому моменту. Бесконечный буфер не существует, а большой конечный — это просто отложенная нехватка памяти с худшим хвостом задержек. Лекарство — не размер буфера, а сигнал назад: обратное давление.
Обратное давление (backpressure): ограниченная очередь как механизм давления
— это не отдельный компонент, а свойство протокола между соседними звеньями конвейера. Суть проста: потребитель сообщает производителю, сколько он готов принять, а производитель не отправляет больше. Сигнал «замедлись» распространяется вверх по цепочке — от самого медленного звена к самому быстрому.
Ограниченная очередь — это и есть сигнал
Замените неограниченную очередь на ограниченную (bounded), и обратное давление появится само собой. Когда очередь заполнена, попытка положить ещё один элемент должна заблокироваться, отвергнуться или подождать. Любой из этих исходов — это давление: производитель узнаёт, что потребитель не успевает, ровно в момент переполнения, а не через минуты роста памяти.
Давление течёт против потока данных
Данные текут вниз (от к ), а сигнал ёмкости — вверх (от потребителя к производителю). Если потребитель тормозит, его входная очередь наполняется, его источник тоже перестаёт читать, и так звено за звеном давление доходит до самого первого производителя. В идеале он либо замедляется у источника, либо честно отказывает на входе.
Ключевая мысль: обратное давление делает перегрузку явной и локальной. Вместо «где-то растёт память, а задержка плывёт по всей системе» вы получаете конкретное переполненное звено и конкретный сигнал на входе. Дальше это уже инженерное решение — что делать с отвергнутой нагрузкой, — а не борьба со скрытым деградансом.
Четыре стратегии при перегрузке
Когда очередь заполнена, у системы есть ровно четыре принципиальных реакции. Они не взаимоисключающие — реальные системы комбинируют их по уровням, — но каждая платит своей валютой: задержкой, потерями, качеством или деньгами.
Заполненная очередь: четыре выхода
Быстрый производитель упёрся в ограниченную очередь медленного потребителя. Что делать с лишним — и чем за это платить.
1. Заблокировать (block / throttle)
Производитель ждёт, пока в очереди освободится место. Ничего не теряется, давление честно передаётся вверх. Цена — растущая и риск, что блокировка распространится на тех, кто блокироваться не должен (например, поток, обслуживающий другие запросы). Хорошо внутри процесса и для самописных конвейеров, опасно на границе с внешними клиентами.
2. Сбросить ()
Лишнее отвергается. Варианты различаются тем, что дропать: tail drop (отвергать новые на входе), head drop (выбрасывать самые старые, уже протухшие), по приоритету (сначала жертвовать неважным трафиком). Потеря в обмен на ограниченную задержку и живой сервис. Отвергать дешевле и раньше — лучше, чем после дорогой работы.
3. Сэмплировать / деградировать
Частный случай сброса: обрабатывать не всё, а представительную выборку, или огрублять ответ. Метрики и трейсы сэмплируют, аналитику считают на части потока, поиск отдаёт неполный, но быстрый результат. Точность падает, но сервис остаётся в рамках бюджета задержки. Подходит там, где приблизительный ответ вовремя ценнее точного с опозданием.
4. Расшириться — горизонтальное масштабирование (scale-out)
Добавить потребителей и поднять суммарную . Радикальное лекарство, но не мгновенное: автоскейлинг реагирует за десятки секунд или минуты, а перегрузка наступает за секунды. Поэтому масштабирование — это стратегия про будущее, а блок/дроп — про здесь и сейчас, пока новые мощности поднимаются.
Pull в действии
Распределённая очередь сообщений
Pull-модель потребителя из главы про очереди — это обратное давление по построению: потребитель сам решает, когда и сколько забрать.
Pull против push: кредитная схема и Reactive Streams
Откуда берётся сигнал «замедлись», зависит от того, кто управляет темпом. В модели доставки push производитель шлёт, как только готов, — и обратное давление приходится надстраивать отдельно. В модели выборки pull потребитель сам запрашивает данные, и давление встроено по построению: нет запроса — нет отправки.
Push: быстро, но опасно
Производитель не спрашивает разрешения. Это минимальная задержка и максимальная пропускная способность, пока потребитель успевает. Как только он отстаёт, разница оседает в буфере — и без отдельного механизма обратного давления push молча копит очередь до отказа.
Pull / кредитная схема: давление по построению
Потребитель выдаёт производителю «кредит»: запрос на N элементов. Производитель отправляет не больше N и ждёт нового кредита. Это и есть request(n) — спрос управляет потоком, а очередь никогда не растёт быстрее, чем её разгребают.
Reactive Streams: стандарт асинхронного обратного давления (backpressure)
Спецификация Reactive Streams (версия 1.0, вошла в JDK 9 как java.util.concurrent.Flow) формализует кредитную схему через четыре интерфейса. Publisher производит элементы по спросу подписчика, Subscriber их принимает, Subscription связывает одну пару «издатель — подписчик», а Processor совмещает обе роли в середине конвейера.
Сердце механизма — метод Subscription.request(long n). По спецификации подписчик обязан сигнализировать спрос через request(n), чтобы вообще получать onNext; издатель не имеет права отправить больше, чем запрошено, а спрос накапливается между вызовами. Так бесконечный поток превращается в поток «по требованию» с ограниченными буферами через любую асинхронную границу — это и есть «non-blocking back pressure», заявленное как цель спецификации.
Фундамент сети
Балансировка нагрузки
Протокол TCP уже решает задачу «быстрый отправитель — медленный получатель» окном и кредитами. Те же идеи всплывают на уровне приложений и сервисов.
Фундамент: TCP, кредиты и закон Литтла
Обратное давление не изобрели в реактивных библиотеках — это переоткрытие приёмов, которым десятилетия. Два фундамента стоит держать в голове: то, как поток регулирует протокол TCP, и то, как закон Литтла связывает ёмкость очереди с задержкой.
Управление потоком TCP (flow control):
В получатель в каждом сегменте объявляет rwnd — сколько байт он ещё готов принять. Отправитель не вправе держать «в полёте» больше rwnd неподтверждённых данных. Это оконное управление потоком, родственное кредитной схеме: когда буфер приёмника заполняется, его окно обнуляется и останавливает отправителя — ровно тот сигнал «замедлись», что нужен против медленного получателя.
Протокол TCP:
Управление потоком защищает получателя, а — сеть между ними. По RFC 5681 отправитель держит второе окно, (cwnd), и темп ограничен минимумом из cwnd и rwnd. (slow start) наращивает cwnd, нащупывая ёмкость, а потеря пакета — сигнал перегрузки — режет окно. Это та же логика: пробуй больше, при перегрузке отступай.
Закон Литтла (Little’s Law): L = λ × W, где L — среднее число элементов в системе, λ — частота поступления, W — среднее время пребывания. Из него прямой инженерный вывод: при фиксированной пропускной способности λ ограничение длины очереди L жёстко ограничивает задержку W — и наоборот. Поэтому ограниченная очередь — это не просто защита от нехватки памяти, а способ назначить верхнюю границу задержки: хочешь предсказуемый хвост — ограничь L, а лишнее сбрасывай.
Соседняя глава
Тайм-ауты и изоляция по отсекам (bulkhead)
На уровне сервисов обратное давление смыкается с устойчивостью: лимиты конкурентности, тайм-ауты и изоляция по отсекам — это давление и защита одновременно.
На уровне сервисов: лимиты, тайм-ауты, приоритеты
Между микросервисами нет общего буфера с request(n), но идея та же: ограничить вход так, чтобы медленный сервис не утянул вызывающих. Здесь обратное давление выражается через лимиты и тайм-ауты.
против лимита конкурентности
режет поток по частоте () — token bucket, leaky bucket, скользящее окно. Но фиксированный лимит на запросы в секунду не знает, какая нагрузка системе по силам прямо сейчас. Лимит конкурентности (сколько запросов в обработке одновременно) ближе к сути: по закону Литтла именно число одновременных запросов, а не их частота, определяет насыщение.
Адаптивные лимиты: учиться у протокола TCP
Библиотека Netflix concurrency-limits переносит логику контроля перегрузки на сервисы: лимит конкурентности не задаётся вручную, а подбирается по росту задержки. Алгоритмы Vegas (по задержке) и Gradient2 (по расхождению скользящих средних задержки) ловят момент, когда начинает копиться очередь, и опускают лимит — как cwnd при потере пакета. Это обратное давление, которое само находит ёмкость.
и
Тайм-аут — это сброс самого старого (head drop) во времени: запрос, провисевший дольше бюджета, отбрасывается, освобождая место и не давая буферам раздуться (bufferbloat) на протухших вызовах. Изоляция по отсекам ограничивает каждый пул ресурсов отдельно, так что перегрузка одной зависимости не съедает потоки, нужные другим. Оба приёма родом из соседней главы про устойчивость.
Очереди с приоритетом
Когда сбрасывать всё-таки приходится, важно сбрасывать правильное. Приоритетные очереди и классы трафика позволяют под давлением жертвовать фоновыми и повторяемыми задачами, сохраняя пользовательские и платёжные. Сброс нагрузки без приоритетов одинаково бьёт по важному и неважному; с приоритетами — это управляемая деградация.
Практический порядок защиты на входе сервиса: сначала тайм-аут (не держать безнадёжное), затем лимит конкурентности (не пускать больше, чем по силам), затем приоритетный сброс (если всё равно перебор — жертвовать неважным), и лишь потом масштабирование (на следующую волну нагрузки). Размыкатель цепи из соседней главы стоит ещё дальше — на случай, когда зависимость уже отказала.
Стриминг: Kafka, Flink, gRPC / HTTP/2
В потоковых системах обратное давление — не опция, а несущая конструкция: данные идут непрерывно, и без обратного давления любой медленный оператор взорвал бы конвейер. Три примера показывают разные реализации одной идеи.
| Система | Механизм потока | Сигнал перегрузки |
|---|---|---|
| Apache Kafka | Pull-модель: потребитель сам опрашивает брокер (poll) и фиксирует офсеты — производитель и потребитель развязаны через лог. | Растущее отставание потребителя (consumer lag) — разница между концом лога и закоммиченным офсетом. Это отставание и есть метрика давления. |
| Apache Flink | Кредитное управление потоком (flow control) в сетевом стеке: получатель объявляет отправителю кредиты (1 буфер = 1 кредит), отправитель шлёт не больше доступных кредитов. | Кредиты опускаются к нулю — отправитель останавливается; обратное давление точечно бьёт по перегруженному логическому каналу, не блокируя соседние в общем соединении (протокол TCP). |
| gRPC / HTTP/2 | Оконное управление потоком (flow control) в протоколе HTTP/2: на каждый поток и на соединение есть окно, получатель шлёт WINDOW_UPDATE по мере чтения — те же кредиты, что и в протоколе TCP. | Окно потока обнуляется — отправитель не может слать DATA-кадры, пока получатель не разгребёт и не обновит окно. |
Обратите внимание на общий мотив: кредиты и окна. Кредиты Flink, окна протокола HTTP/2, окно приёма rwnd протокола TCP и request(n) из Reactive Streams — это одна и та же кредитная схема на разных уровнях. Kafka стоит особняком: он не передаёт кредиты, а развязывает стороны логом и делает обратное давление наблюдаемым через — давление не блокирует производителя, но видно по растущему отставанию (lag).
Соседняя глава
Наблюдаемость и мониторинг
Обратное давление живёт на метриках: длины очередей, отставание потребителя, насыщение пулов. Без них перегрузка видна только постфактум.
Компромиссы: задержка против потерь против стоимости
не отменяет перегрузку — оно переводит её из неуправляемой в управляемую. Но за это приходится сознательно выбирать, чем платить. Три валюты конкурируют, и нельзя оптимизировать все три сразу.
Блокировать → платим задержкой
Ничего не теряем, но ждёт. Годится внутри процесса и там, где данные нельзя терять (платежи, журналы). Опасно на границе с пользователем: блокировка превращается в зависший запрос и может распространиться вверх неожиданным образом.
Дропать → платим потерями
Задержка ограничена, сервис жив, но часть данных теряется. Годится для метрик, телеметрии, не-критичных запросов — там, где свежий приблизительный результат лучше точного с опозданием. Дропать надо рано (на входе) и по приоритету.
Расширять → платим деньгами
Ни потерь, ни задержки — но дороже и не мгновенно. Масштабирование закрывает устойчивый рост, а не секундные всплески: пока новые мощности поднимаются, всё равно нужен блок или дроп как буфер во времени.
Где дропать, где блокировать — и как это увидеть
- Блокируй там, где данные нельзя терять и где задержка приемлема: , журналы транзакций, внутренние конвейеры.
- Дропай там, где важна свежесть и жив бюджет задержки: запросы пользователей на грани тайм-аута, метрики, телеметрия — лучше быстрый отказ, чем медленный успех.
- Дропай рано и дёшево: отвергнуть на входе дешевле, чем после дорогой работы; head drop протухших полезнее tail drop свежих.
- обязательна: длины очередей, отставание потребителя, насыщение пулов, доля сброшенной нагрузки. Обратное давление без метрик — это перегрузка, которую видно лишь по росту задержки и нехватке памяти.
Что важно запомнить
- Рассогласование скоростей производителя и потребителя (producer/consumer) неизбежно; неограниченная очередь не теряет данные, а копит задержку — вплоть до нехватки памяти (OOM) и раздувания буферов (bufferbloat).
- «Просто добавить буфер» не лечит устойчивое превышение скорости: большой буфер лишь откладывает отказ и ухудшает хвост задержек.
- Обратное давление (backpressure) — это сигнал «замедлись» вверх по конвейеру; ограниченная очередь делает его явным и локальным.
- При перегрузке есть четыре реакции: заблокировать (платим задержкой), сбросить (платим потерями), деградировать или расшириться (платим деньгами).
- Pull-модель и кредитная схема request(n) дают обратное давление по построению; Reactive Streams формализует это через Publisher/Subscriber/Subscription.
- Кредиты и окна — одна идея на всех уровнях: окно приёма rwnd протокола TCP, окна протокола HTTP/2, кредиты Flink; Kafka делает давление наблюдаемым через отставание потребителя (consumer lag).
- На уровне сервисов обратное давление — это лимиты конкурентности (адаптивные, как у Netflix), тайм-ауты, изоляция по отсекам (bulkhead) и приоритетный сброс; всё держится на наблюдаемости.
Источники и материалы
Карта источников: Reactive Streams держит контракт неблокирующего обратного давления (non-blocking back pressure) и request(n); Flink — кредитное управление потоком (credit-based flow control) в потоковой системе; Netflix concurrency-limits — адаптивные лимиты конкурентности; RFC 5681 — контроль перегрузки протокола TCP (congestion control). Порог очереди, размер окна, политика сброса и деградации (drop/degrade) и лимит конкурентности остаются рабочими параметрами конкретной системы, а не универсальными числами.
Связанные главы
- Паттерны устойчивости - Соседняя глава о том, как система держится при отказах: тайм-ауты, размыкатель цепи, изоляция по отсекам и повторы. Обратное давление (backpressure) — недостающий слой, который не даёт перегрузке возникнуть.
- Распределённая очередь сообщений - Очередь — главный буфер между производителем и потребителем. Здесь видно, почему ограниченная очередь и отставание потребителя превращаются в естественный сигнал «замедлись».
- Балансировка нагрузки - Масштабирование и распределение запросов — одна из четырёх реакций на перегрузку. Балансировщик размазывает поток, но без обратного давления он лишь равномернее топит потребителей.
- Наблюдаемость и мониторинг - Обратное давление живёт на метриках: длины очередей, отставание потребителя, насыщение. Без наблюдаемости перегрузка видна только постфактум — по росту задержки и нехватке памяти (OOM).
