본문 바로가기
카테고리 없음

왜 많은 팀은 결국 Webflux와 같은 "Reactive Programming"를 포기하는가?

by Bill Lab 2025. 12. 22.
728x90

이 글은 1) 리액티브가 왜 실패하는지를 구조적 측면에서 설명하고, 2) 어떤 조건에서는 여전히 강력한지를 정의하며, 3) Kafka / 메시징이 이 문제를 어떻게 분리하는지를 아키텍처 관점에서 정리해본 것이다.

 

리액티브를 충분히 써 본 팀은 비슷한 지점에 도달한다.
도입 초기의 성과를 지나, 운영 복잡성과 성능 병목을 동시에 마주하는 시점이다.

 

 

1. 리액티브의 전제는 명확하다

“느린 I/O를 숨긴다”

즉, 1) 스레드를 오래 점유하지 않는다. 2) 대기 시간 동안 다른 일을 처리한다. 3)높은 동시성을 적은 리소스로 처리한다

 

이 모든 것은 하나의 전제 위에서만 성립한다.

 

I/O가 비동기·논블로킹일 것

 

CPU 작업, 네트워크 I/O, 이벤트 처리 파이프라인, 이 영역에서 리액티브는 압도적으로 효율적이다.

 

문제는 DB다.

 

 

2. DB는 리액티브 모델과 구조적으로 충돌한다

현실의 DB 접근은 다음 특성을 가진다.

    1) JDBC 기반의 blocking I/O

    2) 유한한 커넥션 풀

    3) 트랜잭션이라는 상태 유지

    4) 락, 인덱스, 디스크 I/O

 

즉, DB 앞에서는 결국 직렬화 압력이 발생한다.

리액티브 애플리케이션이 아무리 빠르게 이벤트를 처리해도 DB 앞에서는 다음이 발생한다.

    1) 커넥션 풀 고갈

    2) 요청 대기 큐 증가

    3) 응답 지연 전파

    4) tail latency 폭발

 

이 시점에서 개발조직은 깨닫는다.
“이건 리액티브의 문제가 아니라 DB라는 병목이 모든 걸 무력화한다”

 

 

3. 리액티브가 만들어내는 착시: 도입 초기에는 분명히 효과가 있다.

    1) TPS 증가

    2) 스레드 수 감소

    3) CPU 사용률 개선

 

하지만 트래픽이 증가하거나 DB latency가 흔들리는 순간, 상황은 급변한다.

    1) event-loop 지연

    2) back-pressure 전파 실패

    3) 전체 throughput 감소

 

그리고 그 시점에 남는 것은 개발 및 운영 복잡성이다.

(물론 개발 조직의 성숙도가 높은 곳이면, 고려대상에서 논외로 보아도 될 것이다.)

    1) 디버깅 난이도 상승

    2) 스택 트레이스 분절

    3) 컨텍스트 전파 문제

    4) 모니터링 난이도 증가

 

성능 이득이 사라진 상황에서 이 복잡성은 더 이상 정당화되지 않는다.

그래서 많은 팀이 리액티브를 포기한다.

 

 

4. 중요한 전제: 이건 “리액티브가 틀린 것”이 아니다

핵심은 이것이다. 리액티브는 DB 앞에서 무력해진다
왜냐하면 DB는 리액티브가 아니기 때문이다.

 

 

리액티브는 1) CPU, 2) 스레드, 3)이벤트 처리를 최적화한다.

 

하지만 병목은 항상 DB에 남아 있다.

이 병목의 위치를 바꾸지 않는 한, 리액티브는 구조적으로 한계를 가진다.

 

 

5. Kafka는 이 문제를 “해결”하지 않는다

대신 “분리”한다

여기서 Kafka가 등장한다.

Kafka는 DB를 빠르게 만들지 않는다.
락을 제거하지도 않는다.

Kafka가 하는 일은 단 하나다.

 

DB 병목을 동기 경로에서 제거한다

 

 

 

6. “기다림”이 “지연”으로 바뀐다

이 차이는 결정적이다.

  • 기존 구조
    → DB가 느리면 요청이 멈춘다
  • Kafka 구조
    → DB가 느리면 처리가 늦어질 뿐이다

결과적으로:

  • API는 즉시 응답한다
  • event-loop는 멈추지 않는다
  • 부하는 큐에 쌓인다 (통제 가능)

시스템은 멈추지 않고, 대신 상태가 드러난다.

 

 

7. Back-pressure가 암묵적 → 명시적으로 바뀐다

리액티브의 back-pressure는 파이프라인 내부에서는 유효하다.
하지만 DB는 이를 이해하지 않는다.

Kafka에서는 다르다.

  • consumer lag
  • 처리량
  • 파티션별 부하
  • throttle 지표

즉, “지금 DB가 감당 못한다” 라는 사실이 숫자로 노출된다

 

 

병목이 숨겨지지 않는다. 이것이 운영 안정성의 차이다.

 

 

8. 트랜잭션 경계가 명확히 분리된다

  • API 트랜잭션
    → Kafka publish까지만
  • DB 트랜잭션
    → Consumer 내부에서 처리

이 분리로 인해:

    1) 긴 트랜잭션 제거

    2) 락 유지 시간 감소

    3) 장애 전파 차단

 

리액티브 모델과 트랜잭션의 충돌 지점이 사라진다.

 

 

9. 그렇다면 리액티브는 언제 쓰는가?

조건은 명확하다.
    1) DB 트랜잭션 빈도가 낮고

    2) DB I/O가 20ms 이내로 안정적이며

    3) 구조적으로 blocking 병목이 발생할 가능성이 낮은 도메인

 

예를 들면 기획전 / 전시, 카탈로그, 피드 / 랭킹, 추천 preview 등이 있다.

즉, DB가 사실상 메모리 확장처럼 동작하는 영역이다.

이 조건에서는 리액티브는 여전히 강력하다.

 

 

10. 동기 API + 내부 이벤트 처리 = 현실적 정답

외부 인터페이스는 단순하게 유지한다.

  • 동기 API
  • 명확한 SLA

내부는 다르게 설계한다.

  • 이벤트 기반 처리
  • Redis 캐싱
  • DB는 최종 일관성 담당

이것이 Reactive를 API 스타일이 아니라
시스템 성질로 사용하는 방식
이다.

 

 

11. jOOQ + JPA 조합은 자연스러운 CQRS를 만든다

  • Command
    • JPA
    • 트랜잭션
    • Object Graph
  • Query
    • jOOQ
    • Flat read model
    • 캐시 친화

CQRS를 규율로 강제하지 않아도
도구 선택만으로 자연스럽게 분리된다.

이건 성숙한 조직에서만 가능한 상태다.

 

 

12. WebSocket이 들어오는 순간, 그림은 완성된다

WebSocket은 요청/응답 모델이 아니다.

  • push 기반
  • event-driven
  • 연결 수가 핵심 병목

Kafka + WebSocket 조합에서는:

  • 상태 변경 이벤트 fan-out
  • DB 조회 없는 실시간 업데이트
  • 연결 수 대비 효율 극대화

리액티브의 진짜 가치는
DB가 아니라 연결 수에서 나온다.

 

 

13. 최종 결론

 

많은 팀이 리액티브를 포기하는 이유는 리액티브가 틀려서가 아니다.

 

DB가 리액티브가 아니기 때문이다.

 

 

Kafka는 DB 병목을 제거하지 않고 그 병목이 전체 시스템을 멈추지 않게 만든다.

리액티브는 포기할 기술이 아니라, 아무 데나 쓰면 안 되는 기술이다.

그리고 올바른 조건과 분리 구조 위에서는 여전히 가장 강력한 도구 중 하나다.

728x90