Backpressure на собеседовании системного аналитика
GROUP BY на оконную SUM(amount) OVER (PARTITION BY user_id). Почему результат содержит столько же строк, сколько и исходный набор?Содержание:
Почему backpressure спрашивают
Backpressure — это классический вопрос системного дизайна для системного аналитика. Как только в задаче появляются слова «поток событий», «очередь», «интеграция сервисов» или «нагрузка» — интервьюер проверяет, понимаете ли вы, что происходит, когда одна часть системы производит данные быстрее, чем другая их успевает переваривать.
Интервьюер смотрит не на заученное определение, а на инженерное мышление: осознаёте ли вы, что производитель и потребитель почти никогда не работают с одинаковой скоростью, и что будет с системой, если эту разницу не контролировать. Кандидат, который отвечает «ну поставим очередь побольше» без слова про её ограниченность и переполнение, сразу проваливает вопрос: неограниченная очередь — это отложенный отказ по памяти, а не решение.
Эта статья закрывает базовые вопросы по теме: что такое backpressure, что случается без него, какие есть стратегии и как это описывать в требованиях к системе.
Что такое backpressure
Backpressure (обратное давление) — это механизм, при котором медленный потребитель сигнализирует производителю «притормози, я не успеваю». Производитель получает сигнал и снижает темп до скорости потребителя. По сути это обратная связь в конвейере данных: скорость потока подстраивается под самое узкое место.
Аналогия — водопроводная труба. Если на выходе стоит узкое горлышко, а на входе продолжают качать под полным напором, давление растёт, пока труба не лопнет. Backpressure — это клапан, который передаёт давление обратно к насосу и заставляет его качать медленнее.
В распределённых системах роль «трубы» играет буфер или очередь между сервисами, а роль «насоса» — сервис-производитель (API, брокер сообщений, поток событий). Без обратной связи производитель не знает, что потребитель захлёбывается, и продолжает слать данные в никуда.
Что происходит без backpressure
Если система не умеет тормозить производителя, есть только два плохих сценария.
Первый — неограниченная очередь. Буфер между сервисами растёт бесконечно, пока не съест всю память:
Быстрый производитель → 10 000 сообщений/сек → медленный потребитель (100/сек)
Очередь растёт без предела → OutOfMemory → падение сервисаВторой — потеря данных. Если очереди нет или она переполнилась, лишние сообщения просто отбрасываются:
Нет буфера → входящие сообщения не помещаются → сообщения теряютсяОба сценария всплывают на собесе как ловушка: кандидату описывают систему с быстрым источником и медленным приёмником и спрашивают, что сломается. Правильный ответ — назвать обе развилки (переполнение памяти либо потеря данных) и объяснить, что backpressure нужен именно для того, чтобы выбрать управляемый компромисс, а не получить аварию.
Стратегии обработки backpressure
Универсального решения нет — стратегию выбирают исходя из критичности данных и допустимой задержки. Основные варианты:
- Буферизация (buffer). Ставим ограниченную очередь. Пока в ней есть место, производитель работает свободно; как только буфер заполнился — включается обратное давление. Ключевое слово — «ограниченная»: неограниченный буфер не решает проблему, а лишь оттягивает падение по памяти.
- Отбрасывание (drop). При переполнении часть сообщений выбрасывается — самые старые, самые новые или случайные. Подходит там, где важна свежесть, а не полнота: например, телеметрия или метрики, где потеря одного замера не критична.
- Приостановка производителя (pause). Производитель блокируется и ждёт, пока очередь разгрузится. Гарантирует, что ничего не потеряется, но замедляет весь конвейер и может создать очередь уже на стороне производителя.
- Троттлинг (throttle). Производителю жёстко ограничивают скорость (rate limiting) — например, не больше N запросов в секунду. Темп заранее подстроен под потребителя, всплески сглаживаются.
- Сэмплирование (sample). Обрабатываем только часть потока, остальное игнорируем. Осмысленно для аналитики в реальном времени, где важна тенденция, а не каждое отдельное событие.
- Сброс на диск (spillover). При переполнении памяти данные временно уходят на диск. Память не переполняется, но растёт задержка и нагрузка на диск.
На собесе важно не перечислить ярлыки, а связать выбор с требованиями. Для платежей и заказов терять сообщения нельзя — значит buffer + pause и никакого drop. Для потока кликов или логов допустимо drop или sample, зато нельзя жертвовать задержкой. Именно эту логику «критичность против задержки» интервьюер и хочет услышать.
Reactive Streams
Reactive Streams — стандарт для асинхронного backpressure. Он описывает, как производитель и потребитель договариваются о скорости без блокировок.
Суть спецификации в модели «спрос по запросу» (pull-based): потребитель сам запрашивает у производителя N элементов, производитель присылает не больше N, и цикл повторяется. Производитель физически не может завалить потребителя — тот забирает ровно столько, сколько готов обработать. Это противоположность наивному push-подходу, где источник шлёт всё подряд и надеется, что приёмник справится.
Спецификацию реализуют несколько библиотек:
- Project Reactor — реактивный стек в экосистеме Java/Spring (типы
FluxиMono). - RxJava / RxJS — реактивные расширения для Java и JavaScript.
- Akka Streams — потоковая обработка поверх акторной модели.
- Kotlin Flow — встроенные корутины и холодные потоки в Kotlin.
Пример на Kotlin Flow, где backpressure задаётся оператором на уровне кода:
flow.buffer(100) // ограниченный буфер на 100 элементов
.conflate() // отбрасывать промежуточные, оставлять только последнее
.collect { ... } // потребитель забирает элементы в своём темпеЗдесь buffer(100) даёт ограниченную очередь, а conflate() реализует стратегию drop-старых: если потребитель отстаёт, промежуточные значения схлопываются и остаётся самое свежее.
Backpressure в реальных системах
Системному аналитику полезно знать, что backpressure не только в реактивных библиотеках — он встроен во многие протоколы и брокеры:
- TCP имеет встроенное обратное давление через скользящее окно (flow control): приёмник объявляет размер окна, и отправитель не шлёт больше, чем помещается.
- Kafka реализует backpressure на стороне потребителя: consumer сам вызывает
poll()и забирает столько, сколько успевает обработать, а offset коммитит после обработки. Продюсер при этом не давит напрямую на потребителя — данные копятся в топике до истечения retention. - gRPC-стриминг поддерживает backpressure на уровне HTTP/2-потоков: приёмник управляет окном, и отправитель приостанавливает передачу.
Если в кейсе есть Kafka или очередь, стоит проговорить, что обратное давление там уже частично решено самой моделью «журнала» — производитель и потребитель развязаны по скорости через хранение сообщений, а не через прямую блокировку.
Частые ошибки
- Предлагать неограниченную очередь. «Поставим буфер побольше» без слова про его предел — это отложенный OutOfMemory, а не backpressure. Очередь обязана быть ограниченной.
- Молчать про потерю данных. Стратегия drop допустима, но её нельзя применять к платежам и заказам. Кандидат должен сам оговорить, где терять сообщения нельзя.
- Путать backpressure с ретраями. Ретраи повторяют неудачные запросы, backpressure регулирует скорость успешных. При перегрузке агрессивные ретраи только усугубляют лавину.
- Игнорировать компромисс задержка/полнота. Любая стратегия — это выбор между «ничего не терять» и «не расти по задержке». Ответ без явного компромисса выглядит неполным.
- Считать, что backpressure решается только кодом. Часто он уже встроен в протокол (TCP, HTTP/2, Kafka). Не нужно изобретать то, что даёт транспорт.
Связанные темы
- Capacity planning для SA
- Circuit Breaker для SA
- Bulkhead pattern для SA
- Rate limiting для SA
- Подготовка к собесу системного аналитика
FAQ
Чем backpressure отличается от rate limiting?
Rate limiting — это жёсткий потолок скорости, заданный заранее и не зависящий от состояния потребителя: «не больше 1000 запросов в секунду». Backpressure — динамическая обратная связь: производитель замедляется ровно настолько, насколько отстаёт потребитель прямо сейчас. Троттлинг можно считать одной из стратегий backpressure, но сам по себе rate limiting не знает, справляется приёмник или нет.
Что выбрать: буферизацию или отбрасывание?
Зависит от критичности данных. Если терять сообщения нельзя (платежи, заказы) — ограниченный буфер плюс приостановка производителя. Если важнее свежесть, а не полнота (метрики, телеметрия, клики) — drop или sample, чтобы не копить задержку. Ключевой вопрос интервьюеру: «что дороже — потерянное сообщение или выросшая задержка?».
Backpressure — это то же самое, что очередь?
Нет. Очередь — это буфер, а backpressure — механизм обратной связи, который срабатывает, когда буфер заполнился. Можно иметь очередь без backpressure (тогда она растёт до OutOfMemory) и backpressure без явной очереди (например, pull-модель в Reactive Streams, где потребитель просто запрашивает следующую порцию).
Как объяснить backpressure в требованиях к системе?
Через нефункциональные требования: указать пропускную способность источника и приёмника, максимальный размер буфера, поведение при переполнении (блокировать, отбрасывать, сбрасывать на диск) и допустимую потерю данных. Это переводит абстрактный «backpressure» в конкретные ограничения, по которым команда сможет спроектировать конвейер.
Есть ли backpressure в Kafka?
Да, но реализован он на стороне потребителя. Consumer сам вызывает poll() и забирает столько, сколько успевает обработать; необработанные сообщения остаются в топике до истечения retention. Продюсер не блокируется напрямую медленным консьюмером — они развязаны хранением сообщений, поэтому Kafka часто и выбирают, чтобы сгладить разницу в скоростях.
Это официальная информация?
Нет. Статья основана на спецификации Reactive Streams, документации Project Reactor, RxJava, Kafka и общей практике проектирования систем. Конкретные формулировки вопросов зависят от компании и уровня позиции.
Тренируйте системный анализ — откройте тренажёр с 1500+ вопросами для собесов.