본문 바로가기

[Disruptor] 5. 그래서 이걸 어디에 써야 하는가?

@이멀젼씨2026. 5. 3. 19:27

지난 네 편에 걸쳐 LMAX의 Disruptor의 특징을 분석해 보았다.

 

그런데 Disruptor에 대해 글을 쓰는 동안 머릿속엔 거래소 외 평범한 백엔드에서도 쓸 일이 있긴 한가 라는 질문이 멤돌았다.

 

거래소 매칭 엔진

 

Disruptor의 본래의 목적은 정해져 있으며 애초에 LMAX가 그걸 만들려고 짠 도구다.

 

다만 거래소가 아닌 일반적인 서비스, 예를 들면 사용자 행동에 따라 포인트를 적립해주거나 마케팅 메시지를 발송하는 시스템에서도 Disruptor가 합리적인 선택이 될 수 있을지가 의문이었다.

 

예를들어 포인트 적립 트래픽이 폭주하거나, CRM 일괄 발송 부하 클때 등 이러한 시나리오 상황에서도 적용할 수 있는 구간이 있지 않을까 하는 생각이 들었다.

 

이번 글에선 거래소 매칭 엔진으로써의 Disruptor와 일반적인 백엔드 어플리케이션에서의 Disruptor 사용을 비교하여 어떤 결론에 도달하였는지를 풀어보겠다.

 

도구를 새로 쥐었을 때 이건 어디에 쓰는 도구인가를 묻는 사고 회로 자체가, 1편부터 4편까지 따라온 진짜 이유라고 생각하기 때문이다.

 

1. Disruptor의 정체부터 다시 짚기

판단 이전에 도구의 정체부터 정확히 짚어야 한다.

 

여기까지 따라왔는데도 막상 누가 Disruptor가 뭐냐 고 한 줄로 답을 요구하면 의외로 헷갈린다.

 

Kafka 같은 메시지 큐인가? 빠른 BlockingQueue인가? Spring @Async 같은 비동기 실행기인가?

 

단도직입적으로, 셋 다 아니다.

 

Disruptor는 단일 JVM 안에서 스레드끼리 메시지를 주고받는 in-process 라이브러리다.

 

같은 프로세스 안에 사는 Producer 스레드와 Consumer 스레드가 RingBuffer라는 공유 메모리 구조 위에서 이벤트를 교환한다.

 

그게 전부다.

 

네트워크도, 디스크도, 다른 프로세스도 끼어들 자리가 없다.

비교해보면 차이가 더 또렷해진다.

구분 Disruptor Kafka ArrayBlockingQueue Spring @Async
범위 단일 JVM 내부 분산 클러스터 단일 JVM 내부 단일 JVM 내부
영속성 없음 (메모리) 있음 (디스크) 없음 없음
평균 latency 수십 나노초 밀리초 수십 마이크로초 수십 밀리초
처리 모델 Lock-free RingBuffer 분산 로그 Lock 기반 큐 스레드풀 위임
운영 난이도 높음 매우 높음 낮음 매우 낮음
학습 곡선 높음 높음 낮음 매우 낮음



(Disruptor 공식 GitHub Wiki 벤치마크 기준 평균 latency는 약 52ns, ArrayBlockingQueue는 약 32μs 수준이다. 한 자릿수가 아니라 세 자릿수 차이다)

이 표가 말하는 결론은 단순하다.

 

Disruptor는 분산 시스템의 도구가 아니다.

 

마이크로서비스끼리 메시지를 던지고 싶다면 Kafka를 써야 하고, 같은 JVM 안에서 무겁지 않은 비동기 작업을 던지고 싶다면 그냥 ExecutorService@Async로 충분하다.

 

Disruptor가 빛나는 영역은 그 둘 사이에 있는 아주 좁은 영역이다.

 

단일 JVM 안에서, 마이크로초도 아닌 나노초 단위 latency가 비즈니스 가치와 직결되는 메시지 파이프라인. 그 외에는 오버 엔지니어링에 가깝다.

 

2. 도입 의사결정 5가지 체크리스트

그럼 나노초가 진짜로 중요하다 는 걸 어떻게 판단할까. 누군가 Disruptor를 도입하자고 제안을 할때 깐깐하게 따져볼 5가지를 정리해두면 다음과 같다.

 

메시지 흐름이 단일 JVM 안에서 닫히는가

Producer와 Consumer가 같은 프로세스 안에 살아야 한다.

 

조금이라도 네트워크를 타거나 다른 프로세스로 넘어가야 한다면 그 순간 Disruptor의 영역은 끝난다.

 

무조건 통과해야 하는 1차 게이트다.

 

처리량이 "단일 코어를 짜내는" 문제인가

Scale-out으로 풀 수 있는 문제라면 Disruptor가 줄 이점은 거의 없다.

 

Disruptor의 본질적 강점은 단일 노드의 코어 한 대를 극한까지 쥐어짜는 것이다.

 

부하가 올라갔을 때 그냥 인스턴스 한 대 더 띄우면 끝나는 구조라면, RingBuffer라는 복잡한 도구를 도입할 명분 자체가 성립하지 않는다.

 

LMAX가 Disruptor를 만든 출발점이 분산 시스템으로 풀려고 했더니 latency가 오히려 늘어나더라 라는 자각이었다.

 

수평 확장이 자유로운 워크로드는 애초에 그들이 풀려던 문제가 아니다.

 

p99 latency가 마이크로초 단위로 비즈니스 가치를 가지는가

가장 깐깐하게 봐야 하는 항목이다.

 

빠르면 좋겠어요 가 아니라 마이크로초가 늦어지면 매출이 빠진다 가 증명되어야 한다.

 

거래소에서 주문 처리가 1ms 늦으면 차익거래 봇이 거래를 하지 않으며 이는 매출 손실로 이어진다.

 

반면 평범한 쇼핑몰에서 결제가 1ms 늦으면 아무도 모른다.

 

이런 경우 쇼핑몰에 Disruptor를 도입하여 얻은 이점은 아무것도 없다.

 

객체 할당 GC 압력이 실제 병목인가

Disruptor가 사전 할당으로 GC를 우회하는 메커니즘은 4편(https://emgc.tistory.com/165)에서 살펴봤다.

 

그런데 GC가 진짜 병목인지는 수치로 증명되어야 한다.

 

JVM 옵션 몇 개로 풀리는 문제일 수도 있고, 애초에 객체를 그렇게 많이 만들 일이 없는 워크로드일 수도 있다.

 

JFR이나 GC 로그로 Stop-the-World가 p99 latency를 실제로 잠식하고 있다 는 증거가 보여야 다음 단계로 넘어갈 수 있다.

 

운영팀이 lock-free 디버깅을 감당할 수 있는가

가장 자주 무시되는 항목이지만 결정적이다.

 

Disruptor는 빠르지만, 한번 문제가 나면 디버깅이 지옥이다.

 

CAS 실패, memory barrier 누락, false sharing 잔존, Wait Strategy 오선택.

 

어플리케이션에 알람이 왔을 때 이걸 추적할 수 있는 사람이 팀에 몇 명이나 있는가.

 

답이 한두 명 이라면, 그 사람이 퇴사하는 순간 시스템은 블랙박스가 된다.

 

종합 판정 기준

  • 5개 모두 YES → 망설일 이유 없음, 도입
  • 4개 YES → 적극 검토
  • 3개 YES → 대안과 비교 후 결정
  • 2개 이하 YES → 오버킬, 다른 도구를 찾자

 

3. Disruptor가 빛나는 곳, 안 빛나는 곳

 

위 체크리스트를 통과한 실제 사례부터 짚어보자.

 

유용한 곳

LMAX Exchange 매칭 엔진

정석이다.

 

단일 스레드로 동작하는 비즈니스 로직이 RingBuffer로 이벤트를 받아 처리한다.

 

Martin Fowler의 LMAX Architecture 문서에 따르면 단일 스레드에서 초당 6백만 주문(6M ops/s) 을 처리한다.

 

5가지 체크리스트 모두 YES다.

 

Log4j2의 AsyncLogger

우리가 자주 사용하고 있는 로거이다.

 

Log4j2의 비동기 로깅은 내부적으로 AsyncLoggerDisruptor를 사용한다.

 

공식 문서는 synchronous 대비 단일 스레드 환경에서 약 6배, 64스레드 경합 환경에서는 최대 68배까지 차이가 벌어진다고 나와있다.

 

생각해보면 로깅은 거의 완벽한 케이스다.

  • 단일 JVM 안에서 닫힘 (✓)
  • 코어 한 대에 묶임 (✓)
  • 비즈니스 스레드를 절대 막으면 안 됨 → latency 중요 (✓)
  • 초당 수만~수십만 로그 객체 → GC 압력 (✓)
  • 디버깅 부담? 라이브러리가 흡수해주니 사용자는 신경 쓸 필요 없음 (✓)

모든 조건을 다 만족했다.

 

그래서 Log4j2가 Disruptor를 채택한 거다.

 

유용하지 않은 곳

 

유용한 곳이 있다면 반대로 유용하지 않는 곳이 있다.

 

HTTP 요청-응답 처리

Tomcat NIO 스레드풀이나 Netty가 이미 충분히 잘 하고 있다.

 

여기에 Disruptor를 끼워 넣어봐야 latency가 줄어드는 게 아니라 운영 복잡도만 올라간다.

 

DB I/O가 병목인 시스템

RingBuffer를 아무리 빠르게 돌려도 Consumer가 DB write를 기다리며 1ms 블로킹되면 그 모든 노력은 무의미해진다.

 

병목은 항상 가장 느린 구간에서 결정된다.

 

Disruptor가 52ns로 빨라져도 DB가 1ms를 잡아먹으면 시스템 전체 latency는 1ms다.

 

마이크로서비스 간 통신

Disruptor는 in-process 라이브러리다.

 

프로세스 경계를 넘을 수 없다.

 

서비스 간 메시지가 필요하면 Kafka나 RabbitMQ가 정답이다.

 

4. 거래소 매칭 엔진의 본질

매칭 엔진이 하는 일 자체는 의외로 단순하다.

 

오더북 이라는 메모리 자료구조 두 개, 매수 호가, 매도 호가를 가격순으로 정렬해두고, 새 주문이 들어올 때마다 반대편 호가와 매칭한다.

 

DB도 없고, 네트워크 호출도 없고, 그냥 메모리 위 자료구조 조작이다.

문제는 이 단순한 작업을 초당 수백만 건 처리하면서 latency를 마이크로초로 유지하는 것이다.
 

여기서부터 어려워진다.

LMAX는 이에 대한 해답으로 단일 스레드 BLP라는걸 만들어서 처리하였다.

 

BLP는 Business Logig Processor의 약자이며 매칭 로직 그 자체를 자바 객체로 만들어 단일 스레드에서 동작시켰다.

 

  • 메모리 내 주문장만 봄
  • DB 호출 없음
  • lock 없음 (단일 스레드니까)
  • GC 거의 없음 (객체 사전 할당)


이 BLP가 단일 스레드로 초당 6백만 주문을 처리한다.

 

이게 LMAX 아키텍처의 핵심이다.

그러면 Disruptor의 역할은?

BLP가 단일 스레드인데도 6M ops/sec가 나오려면, BLP가 주문 매칭 외의 일을 절대 안 해야 한다.
  

역직렬화도 안 하고, 디스크 기록도 안 하고, 네트워크 송신도 안 한다. 그건 다 다른 스레드들이 한다.

 

남은 문제는 "그러면 BLP에게 어떻게 주문을 안전하게, 빠르게, lock 없이 전달할 것인가" 다.

 

그 자리에 Disruptor가 들어간다.


              [Network 주문 수신]
                      ↓
                 [Receiver]
                      ↓
  ┌─────── Input Disruptor (RingBuffer) ────────┐
  │                                             │
  │  ┌─→ Journaler    (디스크에 기록)              │
  │  ├─→ Replicator   (복제 노드로 송신)            │
  │  └─→ Unmarshaller (역직렬화)                  │
  │                                             │
  │  ※ 셋 다 끝나야 BLP가 그 슬롯을 본다               │
  │    (SequenceBarrier로 의존성 표현)              │
  └──────────────────┬──────────────────────────┘
                     ↓
          [BLP — 단일 스레드, 매칭 로직]
                     ↓
  ┌────── Output Disruptor (RingBuffer) ────────┐
  │  ┌─→ Marshaller  (직렬화)                     │
  │  └─→ Publisher   (네트워크 송신)                │
  └─────────────────────────────────────────────┘

 


핵심 포인트는 두 가지다.

1. Input 단계의 fan-out + dependency graph. Journaler / Replicator / Unmarshaller가 같은
  RingBuffer를 동시에 본다. 

각자 자기 Sequence를 들고 자기 속도로 진행한다. 

 

BLP는 이 셋의 SequenceBarrier가 모두 통과한 슬롯만 처리한다.

 

즉, BLP가 어떤 주문을 보는 순간 그 주문은 이미 디스크에 적혀 있고, 이미 복제도 끝나 있고, 이미 역직렬화도 되어 있다.

 

2. BLP 구간은 단일 Producer → 단일 Consumer. Disruptor의 가장 빠른 모드다. CAS조차 거의 쓰지 않는다.

영속성과 복구는? DB가 없는데 어떻게 영속성을 보장하나?

 

이벤트 소싱이다.

 

Journaler가 모든 입력 이벤트를 순서대로 디스크에 적어둔다.

 

장애가 나면 Journal을 처음부터 replay해서 BLP의 메모리 상태(오더북 전체)를 복구한다.

이게 가능한 이유는 BLP가 결정론적이기 때문이다.

 

같은 입력 시퀀스를 같은 순서로 주면 항상 같은 결과가 나온다.

 

단일 스레드라서 가능한 보장이다.

 

멀티스레드면 스케줄링에 따라 결과가 달라져서 replay로 복구가 안 된다.

 

왜 일반 큐가 아니라 Disruptor였나

 

 

LMAX가 처음에는 ArrayBlockingQueue로 시도했는데 안 됐다. 이유는 4편까지 다룬 그대로다.

 

  • Lock contention
    • BlockingQueue는 producer와 consumer가 같은 락을 두고 싸운다.
    • 스레드가 많을수록 느려진다.
  • GC 압력
    • 일반 큐는 메시지 객체를 매번 new 한다 → Stop-The-World→ p99 latency spike.
  • False sharing
    • 인접 변수가 같은 cache line에 들어가서 코어 간 cache 무효화가 발생.
  • Fan-out 처리의 어색함
    • 같은 데이터를 3개 consumer가 봐야 하는데, 일반 큐로는 복사하거나 큐 3개를 깔아야 한다.

Disruptor는 이 네 가지를 한 번에 해결한다.

 

그래서 DB도 안 쓰고, 락도 안 쓰고, GC도 거의 없는, 단일 스레드인데 초당 6백만 처리하는 비현실적인 구조가 성립한다.

5. 일반 비즈니스로 알아보는 케이스

 

케이스1.  포인트 적립 시스템과 Disruptor

이제 본격적으로, 포인트 적립 트래픽이 폭주하는데 RingBuffer로 풀면 어떨까 라는 케이스를 생각해보았다.

 

적립 시스템은 한 덩어리가 아니다.

 

그 안에 들어가 있는 세부 시나리오를 분리해서 봐야 정확한 판정이 나온다.

 

시나리오 1. 메인 적립 트랜잭션 처리

 

가장 자연스럽게 떠오르는 후보다.

 

POS나 앱에서 적립 요청이 쏟아진다 → RingBuffer가 받아서 빠르게 흘려보낸다.

항목 판정 근거
① 단일 JVM 적립 서버 안에서는 가능
② 코어 한 대 한정 적립 서버 인스턴스 늘리면 풀림
③ 마이크로초 latency 100ms도 사용자는 모름
④ GC 압력 트랜잭션 객체가 무겁지 않음
⑤ 디버깅 가능 운영팀 학습 부담 큼

 

→ 도입 불가

 

이유는 단순하다.

 

DB write가 무조건 들어간다.

 

적립은 영속성이 생명이다.

 

메모리에서만 빠르게 처리하고 끝낼 수 있는 작업이 아니다.

 

PostgreSQL이든 MySQL이든 INSERT 한 번이 들어가는 순간 모든 latency 게임은 끝난다.

 

RingBuffer가 52ns로 빨라진들, DB write가 15ms를 잡아먹으면 시스템 latency는 그대로 15ms로 결정된다.

 

게다가 적립 부하가 늘면 그냥 적립 서버를 한 대 더 띄우면 된다.

 

수평 확장이 멀쩡히 작동하는 워크로드에 RingBuffer를 끌어들일 명분 자체가 없다.

가장 핵심처럼 보이는 영역이 가장 부적합한 영역이라는 게 아이러니하다.

 

시나리오 2 적립 inline 부정행위 탐지

 

적립 요청이 들어오는 그 찰나에 in-memory rule engine으로 부정 적립 패턴인지 를 판정하고, 의심 거래는 적립/결제 흐름 중간에서 inline으로 차단한다.

항목 판정 근거
① 단일 JVM fraud-detection 서비스 단독
② 코어 한 대 한정 룰 평가가 메모리 기반
③ 마이크로초 latency inline 차단까지 100ms 안
④ GC 압력 이벤트 객체가 폭발적으로 생성
⑤ 디버깅 가능 팀이 lock-free 디버깅 가능해야 함

 

4/5 도입 검토 가능

 

이쪽은 latency가 비즈니스 가치(부정 손실 차단)와 직결된다.

 

늦게 차단하면 그만큼 손해가 발생하기 때문이다.

 

이벤트 스트림이 단일 JVM 안에서 닫히고, 룰 엔진이 메모리 위에서 돌고, 객체 할당이 폭발적이라는 점에서 Disruptor를 사용할 수 있는 케이스에 정확히 들어간다.

 

다만 디버깅 비용이 결정타다.

 

lock-free로 짜인 inline fraud 모듈이 잘못 동작하기 시작하면, 그 영향은 결제·적립 흐름 전체로 번진다.

 

적립이 1초 늦게 되는 장애 와 결제가 막히는 장애" 는 비즈니스 임팩트가 비교가 안 된다.

 

도입 결정을 내리기 전에 반드시 확인해야 하는건 이 시스템을 고칠 수 있는 사람이 팀에 최소 두 명 이상 있는지가 필수이다.

케이스 2. CRM 발송 시스템과 Disruptor

CRM 캠페인 한 번에 수백만 건 발송하는데, RingBuffer로 분배하면 빨라지지 않을까?

 

이쪽도 한 덩어리가 아니다.

 

발송 자체와 그 앞단의 집계는 완전히 다른 워크로드다.

 

시나리오 3. 발송 워커 큐

 

마케팅 캠페인이 시작되면 수십만~수백만 건의 메시지가 한꺼번에 큐에 쌓인다 → RingBuffer가 워커들에게 빠르게 분배한다.

 

항목 판정 근거
① 단일 JVM 발송 서비스 단독
② 코어 한 대 한정 워커 늘리면 됨
③ 마이크로초 latency 외부 API가 수백 ms
④ GC 압력 메시지 객체가 가벼움
⑤ 디버깅 가능 보통

 

 외부 API가 병목

 

패턴 자체는 Disruptor와 잘 맞는 fan-out이 맞다.

 

그런데 발송이라는 작업은 결국 통신사 SMS API, 카카오 비즈메시지 API, 이메일 SMTP 같은 외부 서비스 호출에 묶인다.

 

빠르면 100ms, 느리면 수 초가 걸린다.

 

여기서 RingBuffer가 52ns 빠른들 무슨 의미가 있는가.

 

0.0001%도 안 되는 개선이다.

 

이런 워크로드는 그냥 일반 메시지 큐(Kafka, RabbitMQ, SQS)에 적재하고 워커 풀이 비동기로 끌어가서 발송하는 구조가 정답이다.


운영 도구 풍부하고, 재시도 정책 명확하고, 워커 수평 확장도 자유롭다.

 

발송이 느린 이유는 큐가 느려서가 아니라, 발송이라는 행위 자체가 외부에 묶여 있기 때문이다.

 

Disruptor를 들이민다고 그 본질이 바뀌지 않는다.

 

시나리오 4. 실시간 세그먼테이션 / 이벤트 집계

 

회원 수천만 명을 대상으로 최근 7일 동안 매일 방문한 사용자 를 실시간 집계해서 캠페인 타겟을 만든다고 가정해보자.

 

이벤트가 초당 수만~수십만 건 들어온다.

 

항목 판정 근거
① 단일 JVM 집계 서버에 한정
② 코어 한 대 한정 단일 노드 집계 시
③ 마이크로초 latency 분 단위 갱신으로도 충분
④ GC 압력 대량 이벤트 처리 시 발생
⑤ 디버깅 가능 어렵지만 가능

 

 가능하지만 Kafka Streams / Flink가 더 적합

 

데이터 양만 보면 Disruptor 영역에 가까워 보인다.

 

그런데 핵심은 ③이다.

 

세그먼테이션 결과가 1초 늦게 갱신되면 캠페인 효과가 떨어지나? 1분 늦게 갱신되면 떨어지나?

 

보통 분 단위, 시간 단위로 충분하다.

 

이 정도 시간 해상도에서는 분산 스트림 처리 프레임워크(Kafka Streams, Flink, Spark Streaming)가 압도적으로 유리하다.

 

수평 확장 자유롭고, 운영 도구 풍부하고, 장애 복구도 표준화되어 있다.

 

Disruptor의 강점인 나노초 latency가 이 영역에서는 오버엔지니어링일 뿐이다.

 

도구 선택의 기준이 단순히 가장 빠른가가 아니라 필요한 만큼 빠른가, 운영이 가능한가 라는 걸 보여주는 전형적인 케이스다.

 

6. 한 가지 더 — 스택의 벽

 

지금까지는 기술 적합성만 따졌다.

 

하지만 도입 결정에는 한 가지 더 깐깐하게 봐야 할 변수가 있다.

 

Disruptor는 JVM 라이브러리다.

 

Kotlin, Scala도 OK지만 어쨌든 JVM 위에서 돌아야 한다.

 

만약 메인 백엔드 스택이 Python, Node.js, Go, Ruby 같은 JVM 환경이 아니라면, Disruptor를 도입하기 위해 별도의 JVM 마이크로서비스를 신규로 띄워야 한다.

 

운영팀은 새 스택을 배워야 하고, 모니터링·배포 파이프라인도 새로 깔아야 한다.

 

체크리스트 5개를 통과해도, 이 조직적 비용이 도입 명분을 깎아먹는다.

 

사실상 JVM이 아닌 환경에서는 위 시나리오처럼 latency에 치명적이고 다른 대안이 없는 경우가 아니라면 도입 자체가 비합리적이다.

Python에 비슷한 컨셉의 PoC급 라이브러리(py-disruptor 등)가 있긴 하다. 그런데 GIL 때문에 Java판처럼 hardware sympathy를 누리지 못한다. CPU 캐시라인을 의도적으로 활용하는 Disruptor의 본질이 GIL 환경에서는 사실상 무력화된다. 빠른 RingBuffer 흉내만 낼 뿐이다.

 

7. 종합 판정

 

지금까지의 시뮬레이션을 한 표로 정리하면 이렇다.

 

시나리오 판정 핵심 이유
1. 메인 적립 트랜잭션 DB write가 병목
2. 적립 inline 부정행위 탐지 latency-critical, in-memory
3. CRM 발송 워커 큐 외부 API가 병목
4. CRM 실시간 세그먼테이션 Kafka Streams가 더 적합

 

결론은 명확하다.

 

일반적인 포인트 적립 시스템과 CRM 발송 시스템에서 Disruptor는 거의 모든 곳에서 오버엔지니어링이다.

 

4개 중 1개만이 검토 가능 범주에 들어가고, 그마저도 유지보수할 인력이 있는가메인 스택이 JVM인가 라는 두 가지 전제가 충족되어야 한다.

 

망치를 들었다고 모든 게 못으로 보이기 시작하는 순간이 가장 위험하다.

8. 그럼 1~4편은 다 헛수고였나

이쯤 결론이 나오면 자연스레 따라오는 의문이 있다.

 

그럼 4편까지 그렇게 깊게 판 건 다 시간 낭비 아니야?

 

아이러니하게도, 그건 또 아니다.

 

Disruptor라는 도구는 안 써도, 1~4편에서 벗긴 개념들은 우리가 매일 만지는 거의 모든 코드 안에

 

False sharing (2편)

운영 중인 Spring Boot 서버에서 LongAdder가 왜 AtomicLong보다 빠른지 안다. 내부적으로 cell padding을 한다는 걸 알기 때문이다.

 

Lock-free와 CAS (3편)

ConcurrentHashMap이 왜 Hashtable보다 빠른지, synchronized 없이 어떻게 thread-safe할 수 있는지를 안다.

 

JVM 동시성 라이브러리의 내부를 들여다볼 때 무엇을 의심해야 하는지를 안다.

 

Memory barrier (3편)

volatile 키워드가 단순히 메인 메모리에서 읽어라 가 아니라 여기 위아래 명령어를 함부로 섞지 마라 라는 의미라는 걸 안다.

 

double-checked locking에서 왜 volatile이 필수인지, 왜 그게 빠진 코드가 미묘하게 깨지는지를 안다.

 

Wait Strategy의 트레이드오프 (4편)

스레드풀 사이즈를 정할 때 BusySpin 대신 Yielding을 선택한다는 게 무슨 의미인지를 안다.

 

CPU 사용률이 100% 찍히는 게 항상 나쁜 게 아니라는 것도 안다.

 

이게 다 1~4편에서 가져가는 자산이다.

9. 마무리

Disruptor를 공부하는 과정은 거꾸로 보면, 우리가 평소에 의식하지 않고 쓰던 추상화의 바닥을 들여다본 시간이었다.

 

Spring이 가려주는 동시성, JVM이 가려주는 메모리 모델, OS가 가려주는 컨텍스트 스위칭.

 

평소에는 몰라도 코드는 잘 돌아간다.

 

다만 한번 깊은 곳을 봐 두면, 다음에 디버깅이 막혔을 때 의심해볼 수 있는 자리가 늘어난다.

 

누군가가 이거 우리 서비스에 도입하면 안 될까요 라고 들고 왔을 때, 개발자로서 가장 먼저 던져야 하는 질문은 결국 이거 진짜 필요해? 이다.

 

5가지 체크리스트로 깐깐하게 따져보고, 대안과 비교하고, 운영 비용까지 계산해본다.

 

대부분의 경우 답은 아니, 그냥 Kafka 쓰자 또는 그냥 ExecutorService로 하자 이다.

 

적립도 CRM 발송도 그런 케이스다.

 

그리고 그게 부끄러운 결론은 아니다.

 

도구는 문제에 맞게 골라 쓰는 것이고, 망치를 손에 들었다고 모든 게 못으로 보이기 시작하면 그 순간이 가장 위험하다.

 

대신 망치 한 번을 끝까지 분해해본 사람은, 다음에 다른 도구를 쥐었을 때도 이건 어디에 쓰는 도구인가 를 정확하게 묻는다.

 

그게 1~4편을 거쳐 5편까지 온 진짜 이유이다.

 

결국 LMAX 엔지니어들이 하드웨어의 한계까지 파고들면서 공개한 것은 자료구조 자체가 아니라, 필요한가를 가장 먼저 묻는 그 태도일 것이다.

 

도구는 안 써도, 사고방식은 평생 남는다.

이멀젼씨
@이멀젼씨 :: 이멀젼씨

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차