콘텐츠로 바로가기

Backpressure Pattern

소비자가 처리 가능한 속도보다 생산자가 빠를 때 소비자가 생산 속도를 제어하는 메커니즘. 버퍼 폭발과 OOM을 방지. 생산자 >> 소비자 속도 차이 → 무한 큐 성장 → 메모리 고갈 또는 지연 폭증. Java의 `Flow` API, Project Reactor, ...

Article
M

Me

hyunyoun's Blog

software-engineering-devopssoftware-engineeringdev-opsarchitecturedesignbackpressure-patternreliabilityobservability10 min read

1. Overview

백프레셔 패턴(Backpressure Pattern)은 초당 1만 개의 트래픽이 쏟아져 들어올 때 서버가 OOM(Out of Memory)으로 비명횡사하는 것을 막고, 스스로 감당할 수 있는 만큼만 데이터를 빨아들이는 시스템의 유체역학적 생존술을 해부합니다.

학습자는 프론트엔드가 백엔드보다 빠를 때 발생하는 '버퍼 오버플로우(Buffer Overflow)'의 파멸적 연쇄 반응을 뜯어봅니다. 나아가 "내가 처리할 수 있는 양은 1초에 10개뿐이니 그만큼만 보내라"고 발신자(Producer)에게 역으로 압력(Pressure)을 가하는 **백프레셔 메커니즘(Pull-based, Drop, Rate Limit)**을 장악합니다. 마지막으로, 리액티브 스트림(Reactive Streams)과 RxJS, Spring WebFlux를 활용하여 데이터의 흐름을 논블로킹(Non-blocking)으로 조절하는 탄력적 데이터 파이프라인(Elastic Data Pipeline) 역량을 확보합니다.

2. Scope & Boundaries

In-Scope

  • Backpressure Concept: 수신자(Consumer)가 발신자(Producer)를 제어하는 피드백 루프.
  • Handling Strategies: 제어(Control), 버퍼(Buffer), 드롭(Drop/Sample), 거절(Fail fast).
  • Reactive Streams: Publisher, Subscriber, Subscription(request(n) 메커니즘).
  • Message Queues (Kafka/RabbitMQ): 시스템 아키텍처 관점에서의 비동기 버퍼링을 통한 백프레셔.

Out-of-Scope

  • Spring WebFlux / RxJS 프레임워크 문법: 특정 라이브러리의 flatMap, concatMap 상세 사용법 \rightarrow 언어/프레임워크 개별 문서로 위임.
  • 분산 시스템의 Circuit Breaker: 하위 시스템 장애 시 요청을 차단하는 패턴 \rightarrow 백프레셔(속도 조절)와는 결이 다르므로 07. System Architecture 영역으로 분리.

Boundaries

  • Backpressure vs Rate Limiting: Rate Limiting(속도 제한)은 보안이나 비즈니스 정책(예: "한 유저는 1초에 5번만 호출 가능")에 의해 고정된 한도 내에서 요청을 강제로 끊어버리는 정적(Static) 차단망입니다. Backpressure는 내 서버의 현재 CPU/Memory 상태에 따라 "지금은 10개 줘", "지금은 바쁘니까 2개만 줘"라고 실시간으로 조절하는 동적(Dynamic) 피드백 루프임을 명확히 긋습니다.

3. Counterexample

  • 밀어내기(Push)의 파국: 유튜버 라이브 방송 중 이벤트가 터졌습니다. 10만 명의 유저가 동시에 채팅을 칩니다. 서버(Producer)는 채팅 내역을 무작정 앱(Consumer)으로 푸시(Push)합니다. 스마트폰은 초당 1천 개의 채팅 렌더링을 감당하지 못하고 메모리가 뻗어 앱이 크래시(Crash)됩니다. 수신자의 소화 능력을 무시하고 폭식시킨 Push 모델의 비극입니다.
  • 버퍼 팽창 (Buffer Bloat): 백엔드 서버가 DB에 데이터를 넣는 속도(10 TPS)보다 API 요청이 들어오는 속도(100 TPS)가 빠릅니다. 백엔드는 이를 '무제한 큐(Unbounded Queue)'에 쌓아둡니다. 메모리 사용량이 치솟다가 1분 뒤 OOM(Out Of Memory)으로 서버가 뻗습니다. 처리하지 못할 데이터를 버리지 않고 껴안고 있다가 폭사하는 구조입니다.

4. Prerequisites

  • 동기(Sync)와 비동기(Async) (Basic): Blocking I/O와 Non-blocking I/O의 차이. (03. Operating Systems)
  • 메시지 큐 아키텍처 (Basic): Kafka, RabbitMQ 등의 비동기 큐잉(Queuing) 시스템. (07. System Architecture)

5. Learning Map

Sequence Core Cluster Objective & Description Evidence (BoK)
1 The Physics of Overflow 생산 속도가 소비 속도를 압도할 때 발생하는 메모리 붕괴(OOM)와 지연(Latency)의 물리적 연쇄 작용을 쥡니다. P1
2 Push to Pull (권력의 역전) "네가 줄 때 받을게(Push)"라는 멍청한 설계를 버리고, "내가 소화할 수 있을 때 10개만 줘(Pull/Request)"로 데이터 흐름의 통제권을 역전시킵니다. P5
3 Backpressure Strategies 쏟아지는 트래픽을 어떻게 처리할 것인가? 버퍼(Buffer), 드롭(Drop/Sample), 실패(Fail-fast)라는 3가지 냉혹한 생존 전략을 뜯어봅니다. Industry
4 System-Level Backpressure 단일 서버를 넘어, Kafka 같은 거대한 메시지 브로커를 활용해 분산 시스템 전체에 댐(Dam)을 건설하는 아키텍처 렌더링을 확보합니다. Industry

6. Learning Topics

Basic

Core Topic 01: 밀어내기의 공포 (The Physics of Overflow)

  • Why to Learn: 수신자의 처리 능력을 고려하지 않고 데이터를 무자비하게 밀어넣어(Push) 결국 전체 시스템이 마비되는 '연쇄 장애(Cascading Failure)'의 물리적 원인을 차단하기 위함입니다.
  • What to Learn:
    • Concepts: Producer (생산자), Consumer (소비자), Push Model, Out of Memory (OOM), Buffer Overflow.
    • Skills: 초당 1,000개의 로그를 생성하는 서버와 초당 100개만 DB에 저장할 수 있는 워커(Worker) 사이에서 1분 뒤에 발생할 메모리 고갈 사태를 수학적으로 계산하기.
  • How to Learn:
    • 1단계: 속도의 불균형: 데이터를 만드는 속도(Producer)와 처리하는 속도(Consumer)는 절대 일치하지 않습니다. 항상 불균형(Imbalance)이 존재합니다.
    • 2단계: Push 기반의 폭주: 전통적인 옵저버 패턴(Observer Pattern)은 이벤트가 발생하면 무조건 리스너에게 통보(Push)합니다. DB가 느려져서 헐떡이고 있는데, 웹서버는 신나게 초당 1천 개의 insert 요청을 던집니다.
    • 3단계: 파멸 (OOM): 처리를 기다리는 데이터는 서버의 메모리(RAM) 어딘가에 쌓입니다. 메모리가 가득 차면 가비지 컬렉터(GC)가 미친 듯이 돌며 CPU를 100% 쳐버리고, 결국 뻗어버립니다. 속도의 불균형이 시스템의 물리적 자원 한계를 뚫어버리는 렌더링을 해부합니다.
  • Implement: Push 모델의 OOM 시뮬레이션 모델링. 생산: 초당 50MB 파일 10개 업로드 (500MB/s). 소비: S3로 전송 속도 (100MB/s). 결과: 1초마다 400MB씩 메모리에 잉여 데이터 누적. 서버 RAM이 4GB라면 정확히 10초 뒤에 서버가 폭발함을 수학적으로 증명하는 시각화.

Core Topic 02: 제어권의 역전 (Push to Pull)

  • Why to Learn: 데이터를 주는 놈(Producer)이 속도를 지배하는 낡은 패러다임을 깨고, 데이터를 받는 놈(Consumer)이 자신의 소화 능력에 맞춰 속도를 지배하도록 권력을 역전시키기 위함입니다.
  • What to Learn:
    • Concepts: Pull Model, Reactive Streams Specification, Subscription.request(n), Non-blocking Backpressure.
    • Skills: 생산자에게 "무조건 던지지 말고, 내가 request(10)이라고 외칠 때만 딱 10개 보내라"고 지시하는 리액티브(Reactive) 흐름 통제기 설계하기.
  • How to Learn:
    • 1단계: Pull 모델 (전통적): iterator.next()처럼 소비자가 필요할 때만 하나씩 당겨옵니다. 안전하지만, 데이터가 없을 때는 스레드가 블로킹(Blocking)되어 기다려야 하므로 성능이 떨어집니다.
    • 2단계: Non-blocking Pull (리액티브 스트림): 블로킹 없이 Pull 모델의 장점만 취합니다. 소비자는 구독(Subscription) 통로를 통해 "나 지금 5개 처리할 수 있어(request(5))"라고 신호를 보냅니다. 생산자는 딱 5개만 비동기로 Push 해줍니다.
    • 3단계: 다이나믹 피드백 루프: 소비자가 5개를 다 처리하고 여유가 생기면 다시 request(10)을 보냅니다. 만약 DB가 느려지면 소비자는 아예 request를 보내지 않고 쉽니다(0개). 소비자의 상태가 생산자의 속도를 브레이킹하는 **역방향 압력(Back-pressure)**의 실체를 뜯어봅니다.
  • Implement: Reactive Streams request(n) 인터랙션 모사. Consumer: subscription.request(2); 호출. Producer: 즉시 onNext(data1), onNext(data2) 2개만 발송 후 일시 정지(Suspend). Consumer: DB 저장 완료 후 subscription.request(5); 호출. 자신이 소화 불량에 걸리지 않을 만큼만 스스로 떠먹는 권력 역전 렌더링.

Practical

Core Topic 03: 버릴 것인가 품을 것인가 (Backpressure Strategies)

  • Why to Learn: "잠깐만! 나 지금 꽉 찼어!"라고 백프레셔를 걸었을 때, 생산자 쪽에서 갈 곳을 잃고 대기 중인 잉여 데이터를 어떻게 처리할 것인지 비즈니스 결단을 내리기 위함입니다.
  • What to Learn:
    • Concepts: Buffering (버퍼), Dropping (버리기/샘플링), Throttling/Debouncing, Fail-fast (에러 뱉기).
    • Skills: "실시간 주식 호가 데이터"처럼 1초 전 데이터가 쓸모없는 경우 과감히 버리는(Drop) 전략과, "결제 트랜잭션"처럼 절대 유실되면 안 되는 경우 버퍼(Buffer)에 담거나 즉시 거절(Fail-fast)하는 전략을 분기하기.
  • How to Learn:
    • 1단계: 버퍼링 (Buffer): 가장 일반적인 방법. 잠깐 넘치는 트래픽을 메모리나 디스크(Queue)에 담아둡니다. 하지만 버퍼가 무한정 커지면 OOM이 나므로 반드시 'Bounded Buffer(크기 제한)'를 써야 합니다.
    • 2단계: 드롭 (Drop / Sample): 버퍼가 꽉 차면 최신 데이터(또는 가장 오래된 데이터)를 버립니다. IoT 센서 데이터(1초에 온도 1,000번 측정)나 주식 호가, 실시간 채팅처럼 '가장 마지막 값'만 중요한 도메인에서 쓰이는 가벼운 생존술입니다.
    • 3단계: 빠른 실패 (Fail-Fast): 수강신청 서버입니다. 1만 명이 넘치면 큐에 줄을 세우는 대신 10,001번째 요청부터 즉시 HTTP 503 Service Unavailable 에러를 쳐냅니다. 유저는 "서버가 터졌네" 하고 새로고침을 누르겠지만, 우리 서버의 메모리는 완벽하게 살아남는 방어막을 해부합니다.
  • Implement: 도메인별 백프레셔 핸들링 매트릭스 도출. Case 1: 은행 송금 로그: 절대 유실 불가 \rightarrow 큐(Buffer)에 넣고 큐가 꽉 차면 즉시 사용자에게 '요청 실패(Fail-fast)' 응답. Case 2: 모니터링 마우스 궤적 데이터: 1초에 100번 이동 \rightarrow 1초에 1번만 샘플링(Drop/Throttle)하여 서버 부하 감소. 데이터의 가치에 따라 생존 전략을 다르게 짜는 아키텍처 렌더링.

Advanced

Core Topic 04: 분산 시스템의 댐 (System-Level Backpressure with Kafka)

  • Why to Learn: 코드 레벨(RxJS, WebFlux)의 작은 백프레셔를 넘어, 서버 A(생산)와 서버 B(소비)라는 완전히 분리된 거대한 아키텍처 환경에서 물밀듯 쏟아지는 트래픽을 통제하는 '댐(Dam)'을 건설하기 위함입니다.
  • What to Learn:
    • Concepts: Message Broker (Kafka, RabbitMQ), Event-Driven Architecture, Decoupling, Consumer Lag.
    • Skills: 블랙프라이데이 때 주문 API 서버(100대)가 초당 10만 건을 던져도, 결제 서버(10대)가 죽지 않도록 중간에 Kafka를 끼워 넣어 거대한 댐(버퍼)을 구축하기.
  • How to Learn:
    • 1단계: 시스템의 강결합: API 서버(Producer)가 결제 서버(Consumer)를 HTTP(REST)로 직접 호출합니다. 결제 서버가 감당을 못하고 응답이 10초로 느려집니다. API 서버도 HTTP 커넥션이 물려(Blocking) 같이 뻗어버립니다.
    • 2단계: 댐의 건설 (Kafka): API 서버는 결제 서버를 부르지 않습니다. 그저 "주문 들어옴" 메시지를 카프카(Kafka)라는 거대한 댐에 던지고(Produce) 바로 응답을 줍니다(비동기).
    • 3단계: 소비자의 페이스 (Pull): 결제 서버는 카프카에 쌓인 메시지를 자기 능력껏(초당 100개씩) 당겨옵니다(Pull). 트래픽이 10만 건 쏟아지면 카프카에 메시지가 쌓일(Lag) 뿐, 결제 서버는 절대 뻗지 않습니다. 밤을 새워서라도 천천히 다 처리해 내는 분산 아키텍처의 위대한 백프레셔 렌더링을 뜯어봅니다.
  • Implement: MSA 환경의 댐(Dam) 기반 트래픽 제어 시각화. Traffic: 분당 100만 건 유입. Producer (API): 유입 즉시 Kafka Topic orders에 append 후 200 OK 응답 (초당 10,000건 처리, OOM 없음). Kafka (Dam): 100만 건 안전하게 디스크에 버퍼링. Consumer (Payment): 자기 처리 속도(초당 500건)에 맞춰 Pull. 서버 부하는 평온(CPU 40%)하게 유지되며 밤새 2,000초 동안 100만 건 완벽 소화.

7. Terminology

Term (EN / ko, abbr) 1문장 정의 단계(기본/권장/실무/심화) 역할/맥락 관련 개념 유사/대비/함께 사용 오해 포인트 Evidence(Primary/Secondary/Industry) Flags(core)
Backpressure (백프레셔 / 역압) 데이터를 받는 쪽(Consumer)이 처리 한계에 도달했을 때, 데이터를 보내는 쪽(Producer)에게 "나 꽉 찼으니 그만 보내"라고 역으로 압력(피드백)을 가해 시스템 붕괴를 막는 제어 매커니즘입니다. 기본 시스템의 메모리 초과 방지 및 안정적인 흐름 제어 Reactive Streams / Flow Control Rate Limiting (속도 제한) 속도 제한(Rate Limit)은 외부 트래픽을 정책적으로 쳐내는 보안/비즈니스 문지기이고, 백프레셔는 내부 서버 간의 소화 능력에 따른 실시간 눈치게임(흐름 제어)임 P1:CS2023 core
Reactive Streams (리액티브 스트림) JVM 진영에서 백프레셔를 표준화하기 위해 만든 스펙으로, 소비자가 request(n)을 통해 비동기적으로(Non-blocking) 데이터 요청량을 조절하는 권력 역전의 핵심 기술입니다. 권장 코드 레벨의 Non-blocking 흐름 제어 표준 Publisher / Subscriber / Spring WebFlux Observer Pattern 단순 옵저버 패턴은 데이터가 생기면 무조건 푸시(Push)해서 컨슈머를 죽일 수 있지만, 리액티브 스트림은 컨슈머가 허락한 개수만큼만 보내는 보호망이 있음 P5:SFIA core
Consumer Lag (컨슈머 랙) 생산자가 카프카(Kafka) 같은 큐에 데이터를 밀어 넣는 속도가 소비자가 빼가는 속도보다 빨라서 큐에 데이터가 끝없이 쌓이고 있는 지연(Lag) 상태를 말합니다. 실무 댐(Dam)의 수위 측정 및 스케일 아웃 판단 지표 Kafka / Message Queue / Buffer OOM (Out of Memory) 큐(버퍼)가 무한정 커지면 좋겠지만 디스크 용량 한계가 있으므로, Lag가 지속해서 치솟으면 결국 컨슈머 서버를 늘려(Scale-out) 빼가는 속도를 높여야 함 Industry core
Fail-Fast (빠른 실패) 밀려오는 데이터를 버퍼에 쌓아두고 희망 고문을 하다가 다 같이 죽는 대신, 처리 한계를 넘은 즉시 503 에러를 뱉어 시스템의 코어를 지키는 냉혹한 방어술입니다. 심화 잉여 데이터 처리 전략 중 즉각적 포기(Drop) 전략 Circuit Breaker / Load Shedding Bounded Buffer (크기 제한 버퍼) 무제한 버퍼는 100% 서버를 죽입니다. 버퍼 사이즈를 딱 1,000으로 고정(Bounded)하고, 그걸 넘치면 즉시 Fail-fast로 쳐내는 것이 진정한 시스템 하드닝임 Industry core

8. References

Primary

  • [P1] CS2023 - Software Engineering (SE) - Concurrent and Distributed Systems (Reactive programming)
  • [P5] SFIA - System Design (SYMD) - Scalability and flow control

Secondary

  • [Reactive Manifesto] - Reactive Streams Specification (Message-driven, Back-pressure)
  • [Designing Data-Intensive Applications] Martin Kleppmann - Message Brokers and Flow Control

Industry

  • [Spring WebFlux Documentation] - Reactive Core and Backpressure
  • [Confluent Blog] - Backpressure and Flow Control in Apache Kafka

9. Final Checklist

Primary

  • 데이터베이스 쓰기 속도가 초당 100 TPS인 상황에서 웹 서버가 무작정 초당 10,000 TPS로 데이터를 푸시(Push)할 경우, 큐에 쌓인 데이터가 가비지 컬렉터(GC) 폭주와 OOM(Out of Memory)을 유발하는 물리적 인과관계를 설명할 수 있는가?
  • 전통적인 옵저버 패턴(Observer Pattern)의 일방적 푸시(Push) 방식이 가진 한계를 지적하고, 리액티브 스트림(Reactive Streams)의 request(n) 메커니즘을 통해 수신자가 송신자의 속도를 제어(Pull)하는 권력 역전을 논증할 수 있는가?

Secondary

  • 백프레셔로 인해 처리하지 못하고 대기 중인 잉여 트래픽을 핸들링할 때, 유실되면 안 되는 결제 로그(Buffer/Fail-fast)와 최신 값만 중요한 마우스 좌표 로그(Drop/Sample)의 비즈니스적 가치에 따라 생존 전략을 다르게 분기할 수 있는가?
  • 단일 프로세스 내의 WebFlux/RxJS 레벨의 백프레셔를 넘어, 마이크로서비스 간의 통신에서 트래픽이 폭주할 때 Kafka와 같은 메시지 큐를 '거대한 댐(Dam)'으로 활용하여 비동기 분산 시스템 수준의 백프레셔를 설계할 수 있는가?

Industry

  • API Gateway에서 초당 1만 건의 트래픽을 막는 '속도 제한(Rate Limiting)' 정책과, 백엔드 서버가 DB 부하 상태에 따라 유동적으로 데이터 수신량을 조절하는 '백프레셔(Backpressure)'의 목적과 동적(Dynamic) 특성 차이를 정확히 렌더링할 수 있는가?
  • 댐 역할을 하는 버퍼 큐(예: Kafka)의 수위(Consumer Lag)가 지속해서 치솟는 것을 모니터링했을 때, 무작정 버퍼 크기를 늘리는 대신 컨슈머를 스케일 아웃(Scale-out)하거나 시스템 진입점 앞단에서 로드 셰딩(Load Shedding, 과부하 차단)을 수행하는 거버넌스를 구축할 수 있는가?

Reliability, Observability & Technical Debt

7 / 8