Skip to content

Reactive Programming ‐ Reactive System & Reactive Programming

woojin.jang edited this page May 20, 2026 · 4 revisions

Reactive Programming - Reactive System & Reactive Programming

MVC 기반 아키텍처 흐름과 리액티브 프로그래밍 탄생 배경

1. Blocking I/O와 스레드 풀 고갈

  • 200개의 스레드가 모두 DB 조회(200ms)나 외부 API 호출(500ms) 결과를 기다리며 대기(Block) 상태에 빠지게 된다.
  • 리소스 낭비 : 이 대기 시간 동안 스레드들은 CPU 연산을 전혀 수행하지 않으면서도, 단지 응답이 오기를 기다린다는 이유만으로 시스템 리소스(스레드)를 점유하고 있다.
  • 스레드 풀 고갈(Thread Pool Exhaustion) : 기본 Tomcat 설정인 200개의 스레드가 모두 이런 I/O 대기 상태에 묶여버리면, 이후에 들어오는 201번째 요청은 처리할 여유 스레드가 없어 큐에서 대기하게 되고, 결국 타임아웃이나 Connection Refused 에러와 함께 요청이 유실된다.

2. 스레드를 늘리는 것의 물리적 한계

  • 쓰레드라는 자원은 공짜가 아니라는 것을 명심해야 한다.
  • 메모리 오버헤드 : 스레드는 생성될 때마다 독립적인 스택 메모리(일반적으로 1MB)를 할당받는다. 스레드가 기하급수적으로 늘어나면 애플리케이션의 메모리 점유율도 감당할 수 없이 커지게 된다.
  • 컨텍스트 스위칭(Context Switching) 비용 : CPU 코어 수는 제한적인데 동작해야 할 스레드만 많아지면, CPU가 여러 스레드를 번갈아 실행하기 위해 현재 상태를 저장하고 다른 상태를 복원하는 작업에 과도한 연산 능력을 낭비하게 되어 오히려 전체 처리량(Throughput)이 저하된다.

3. 패러다임의 전환: 리액티브 프로그래밍과 Non-Blocking I/O

  • Event Loop 기반 비동기 처리 : 요청이 들어오면 대규모 스레드 풀 대신, 소수의 스레드(보통 CPU 코어 수와 동일하거나 2배)로 동작하는 이벤트 루프가 이를 수신한다.
  • 작업 위임과 즉시 반환 : 스레드가 DB나 외부 API에 데이터를 요청한 뒤, 결과를 그 자리에서 기다리지 않고 즉시 스레드 풀로 돌아가 대기 중인 다른 클라이언트의 요청을 처리한다.
  • 이벤트 콜백 : DB나 API로부터 응답 데이터가 준비되어 도착하면 시스템에 이벤트를 발생시키고, 여유가 있는 스레드가 해당 결과값을 받아 나머지 비즈니스 로직을 마저 처리한 뒤 클라이언트에게 응답한다.

4. 진정한 리액티브 아키텍처를 위한 조건

  • 리액티브 스택의 높은 동시성 처리 이점을 극대화하려면 웹 계층만 비동기로 동작해서는 안 되며, 데이터베이스 접근 계층까지 모두 Non-Blocking으로 구성해야한다.
  • 웹 계층을 WebFlux로 구현했더라도 DB 연결에 기존의 동기식 JDBC를 사용한다면 스레드는 결국 DB 응답을 기다리며 블로킹된다.
  • 따라서 다수의 외부 API를 호출하여 시세 데이터를 병합하는 크롤러나, 높은 트래픽의 금융 데이터를 다루는 아키텍처에서는 기존 패러다임을 깨고 R2DBC와 같은 비동기 논블로킹 DB 드라이버를 결합해야만 진정한 리액티브 시스템의 성능을 끌어낼 수 있다.

데이터 스트림 구조에서 OOM 방지를 위한 BackPressure 효과

  • 트래픽이 폭주하더라도 시스템의 어느 한쪽이 OOM 등으로 무너지는 것을 막고 자신의 처리 능력(Capacity) 안에서 가장 안전하고 효율적으로 동작하도록 보장하는 방어 메커니즘이다.
  • Consumer-Driven : 데이터를 만들어내는 쪽(Publisher)이 일방적으로 데이터를 밀어 넣는(Push) 것이 아니라, 데이터를 소비하는 쪽(Subscriber)이 철저하게 트래픽의 주도권을 쥐게 된다.
  • Dynamic Adjustment : 실시간으로 데이터 수신량을 조절한다.
  • Graceful Completion : 모든 데이터를 동적으로 보내고 지체 없이 onComplete()를 호출하여 자원 낭비 없이 안전하게 통신을 종료한다.

Spring MVC와 Spring WebFlux의 차이점과 주의사항 정리

1. 쓰레드 할당 및 필터 단계

  • 클라이언트 요청 → 쓰레드 풀로부터 쓰레드를 할당
  • 사용자의 HTTP 요청이 WAS에 도달하면 WAS가 관리하는 쓰레드 풀에서 유휴 쓰레드 하나를 꺼내 해당 요청에 할당한다.
  • 서블릿 필터 : 할당된 쓰레드는 Spring 컨텍스트에 진입하기 전 톰캣과 같은 서블릿 컨테이너 레벨에서 공통 관심사를 처리하는 필터 영역을 거치게 된다.

2. Spring MVC 핵심 컨트롤러

  • 디스패처 서블릿(Dispatcher Servlet) : 필터를 통과한 요청은 디스패처 서블릿으로 들어온다. 모든 요청을 중앙에서 받아 적절한 곳으로 위임하는 프론트 컨트롤러 패턴이다.
  • HandlerMapping + Controller : 해당 URI 요청을 처리할 수 있는 컨트롤러 메서드가 있는지를 조회 후 매핑된 Controller로 요청을 전달, 컨트롤러는 사용자가 보낸 파라미터를 검증 및 바인딩한 뒤 비즈니스 로직으로 넘긴다.

3. 비즈니스 로직 및 데이터 접근 단계

  • Service 레이어에서 핵심 비즈니스 로직이 실행된다.
  • 데이터를 조회하거나 저장하기 위해 Repository를 호출하여 데이터베이스와 통신한다.
  • 핵심 포인트 : 전통적인 JDBC나 비동기를 지원하지 않는 드라이버를 사용할 경우, DB에 쿼리를 날리고 결과를 받아올 때까지 해당 쓰레드는 블로킹 상태가 된다. 이 구간이 시스템 전체의 처리량(Throughput)을 제약하는 주요 병목 구간이 된다.

4. 응답 및 스레드 반납

  • DB 조회가 완료되면 Dispatcher Servlet으로 결과가 리턴된다.
  • 쓰레드 반납 : 클라이언트에게 최종 HTTP 응답이 전송되면 해당 요청을 처리하기 위해 사용했던 쓰레드는 파괴되지 않고, 다시 WAS의 쓰레드 풀에 반납되어 다음 요청을 대기한다.

1. 이벤트 루프

  • 이벤트 루프는 무한히 회전하며 큐(Queue)에 들어오는 이벤트(클라이언트 요청, DB 응답 등)를 확인하고 처리하는 싱글 쓰레드이다.

2. 요청 1 유입 및 작업 위임

  • 요청 1이 들어오는데 만약 이 요청이 CPU만 쓰는 단순 계산이라면 이벤트 루프가 바로 처리해서 응답한다.
  • 하지만 DB 조회, 외부 API 호출, 파일 읽기 등 오래 걸리는 I/O 작업이라면 이벤트 루프는 자기가 직접 기다리지 않고 운영체제나 백그라운드 쓰레드 풀에 해당 작업을 처리해달라고 요청을 등록한다.

3. 막힘없는 동시 처리

  • 요청 1의 I/O를 던져버린 이벤트 루프는 블로킹되지 않는다.
  • 즉시 다음으로 들어온 요청 2, 요청 3을 받아서 똑같이 처리하거나 위임한다. 하나의 쓰레드로 수천, 수만 개의 동시 커넥션을 맺을 수 있는 이유가 바로 Non-Blocking 기반이기 때문이다.

4. 완료 이벤트

  • 백그라운드에서 요청 1의 I/O 작업이 끝나고 이벤트 루프 측에 작업이 다 되었다는 완료 이벤트를 보낸다.

5. 응답 처리

  • 계속 들고 있던 이벤트 루프가 이 완료 이벤트를 발견하고 비로소 조회된 데이터를 가공해 요청 1을 보냈던 클라이언트에게 최종 응답을 내려준다.

📖 Java🔥

📖 Kotlin⭐

📖 Coroutine📎

📖 Spring🔥

📖 Spring Security⭐

📖 Spring Security OAuth2⭐

📖 Spring Batch📎

📖 Database🔥

📖 MySQL🔥

📖 Redis⭐

📖 JPA⭐

📖 QueryDsl📎

📖 MSA⭐

📖 Kafka⭐

📖 Apache Flink📎

  • [Apache Flink - Apache Flink Architecture]
  • [Apache Flink - Stream Processing]
  • [Apache Flink - Data Stream API & Window]
  • [Apache Flink - State Management]

📖 HTTP🔥

📖 AWS⭐

📖 Docker⭐

📖 Kubernetes⭐

📖 Github Actions📎

📖 Jenkins📎

📖 Nginx⭐

📖 Monitoring📎

📖 Test(feat. Load Testing)📎

📖 Test(feat. Java)⭐

📖 Spring AI📎

📖 gRPC📎

  • [gRPC - Writing .proto Files with Protocol Buffers]
  • [gRPC - Various Communication Patterns in gRPC]
  • [gRPC - gRPC Optimization Techniques and Advanced Features]

📖 Spring Cloud Microservice Application📎

📖 TDD(Test-Driven-Development)⭐

📖 PostgreSQL📎

  • [PostgreSQL - Docker만을 사용하는 경량화된 환경 구성 방법]
  • [PostgreSQL - PostgreSQL에서 제공하는 데이터 타입]
  • [PostgreSQL - PostgreSQI의 JSONB, 역인덱싱과 활용 방법]
  • [PostgreSQL - 데이터베이스 성능을 위한 최적화 패턴 및 전략]
  • [PostgreSQL - 트랜잭션과 ACID, Isolation 수준별 차이]
  • [PostgreSQL - Database Lock 교착상태와 읽기/쓰기 성능을 보장하는 MVCC 모델]
  • [PostgreSQL - pgvector와 벡터 저장, 유사도 검색 패턴 개념]
  • [PostgreSQL - 벡터 인덱스 최적화와 벡터 검색과 전문 검색 결합 패턴]
  • [PostgreSQL - PostgreSQL 플러그인]
  • [PostgreSQL - PostGIS - 공간 쿼리와 GIST 인덱스, 지리 타입과 공간 쿼리를 위한 타입과 기본 함수]
  • [PostgreSQL - pg_search - 검색 엔진 없이 텍스트 검색 구현과 주의사항]
  • [PostgreSQL - 단일 인스턴스 한계를 극복하는 분산 패턴과 스케줄링, 분산 환경 구축 방법]
  • [PostgreSQL - Citus - 분산 테이블과 분산 쿼리를 위한 Extension과 데이터 분산 처리]
  • [PostgreSQL - pg_cron - PostgreSQL로 구성하는 CronJob]
  • [PostgreSQL - 스케줄러 + 분산 처리를 동시에 도입하는 주기적 집계 쿼리 패턴]

📖 Workflow-Driven Techniques for Large-Scale Traffic Processing📎

  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Kafka + Debezium을 활용한 CDC 패턴 설계]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Temporal을 활용한 워크플로우 패턴]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Docker와 경량 이미지를 활용한 환경 구축 방법]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Kafka에서의 메시지 Delivery Guarantee]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - 실시간 동기화의 핵심 CDC]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - MySQL Binary Log 기반의 CDC]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Binary Log 기반의 CDC 구현 플랫폼 Debezium이란?]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Debezium Architecture]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Debezium Architecture Best Practice와 주의사항]

📖 Reactive Programming📎

📖 ElasticSearch📎

📖 Design Pattern📎

📖 Clean Spring📎

  • [Real MySQL 8.0 - 인덱스]
  • [Real MySQL 8.0 - 실행 계획]
  • [Real MySQL 8.0 - 아키텍처]
  • [Real MySQL 8.0 - 트랜잭션과 잠금]
  • [도메인 주도 설계의 사실과 오해 - DDD 요약]
  • [도메인 주도 설계의 사실과 오해 - Preface, Entity & VO]
  • [도메인 주도 설계의 사실과 오해 - 연관 관계와 애그리거트]
  • [도메인 주도 설계의 사실과 오해 - 애그리거트 구현]
  • [도메인 주도 설계의 사실과 오해 - 레포지토리와 기타 패턴]
  • [도메인 주도 설계의 사실과 오해 - 통찰력을 향한 리팩터링]
  • [도메인 주도 설계의 사실과 오해 - 유연한 설계를 향한 리팩터링]
  • [도메인 주도 설계의 사실과 오해 - 모델의 경계를 긋고, 핵심에 집중하라]

Clone this wiki locally