Apache Samza LinkedIn 알림 시스템 최적화 (ATC 사례) - LinkedIn은 왜 이메일 알림에 Apache Samza를 썼을까?
LinkedIn ATC 사례를 직접 분석하며 면접에서 '왜 Hadoop 배치가 아니라 Samza였는가'를 정확히 답하지 못해 겪은 경험을 공유합니다. 로컬 상태 RocksDB 설계와 Kafka Streams·Flink와의 차이를 실패에서 배운 시각으로 정리합니다.
시스템 디자인 면접에서 실시간 알림 파이프라인 설계를 주제로 받았을 때, 저는 "Kafka로 이벤트를 받아서 처리하면 됩니다"라고 답했습니다. 면접관께서 "그러면 사용자 한 명에게 오늘 하루 알림이 50건 쌓였을 때 어떻게 처리하시겠습니까?"라고 물으셨을 때, 저는 답을 내놓지 못했습니다. 상태를 어디에 얼마나 빠르게 읽고 쓸 수 있는가 — 그게 바로 LinkedIn이 Samza를 만든 이유이자, 제가 그날 면접에서 짚지 못한 핵심이었습니다.
Apache Samza는 LinkedIn에서 태어난 분산 스트림 처리 프레임워크입니다. "정작 만든 회사는 이걸 어디에 어떻게 쓰고 있나"를 들여다보면, 스트리밍 시스템 설계에 대한 살아있는 인사이트를 얻을 수 있습니다. 오늘은 그 대표 사례인 ATC(Air Traffic Controller)를 뜯어보겠습니다.

왜 배치가 아니라 스트림 처리였나
LinkedIn도 처음엔 Apache Hadoop으로 데이터를 처리했습니다. 문제는 배치 처리의 지연 시간이었습니다. 하루치 데이터를 모아 한 번에 돌리는 방식으로는, "지금 이 순간 사용자에게 어떤 알림을 보낼지" 같은 결정을 제때 내릴 수 없었습니다.
제가 면접에서 놓쳤던 부분이 바로 이 지점입니다. "Kafka로 받아서 처리한다"는 말은 틀리지 않지만, "수신자별로 오늘 받은 알림 수, 채널별 횟수, 마지막 발송 시각"이라는 상태(state)를 어디에 두는가를 답하지 못하면 절반짜리 설계입니다. LinkedIn은 이 상태를 원격 DB에 두는 대신 각 처리 노드의 로컬 디스크에 두는 발상으로 Samza를 설계했습니다. 참고로 LinkedIn에서 가장 큰 Samza 잡은 피크 시간에 초당 100만 건 이상의 메시지를 처리하고, 수백 대의 머신과 1만 개가 넘는 컨테이너 위에서 수백 개의 프로덕션 애플리케이션이 돌아갑니다.
핵심 사례: ATC(Air Traffic Controller)
사용자가 늘면 알림도 늘어납니다. 그런데 알림이 많아질수록 회원 경험은 오히려 나빠집니다. LinkedIn 각 팀이 독립적으로 알림 정책을 결정하다 보니 팀마다 기술 스택이 달라지고, 결과적으로 과도하고 중복된 알림이 쌓였습니다. 한밤중에 쏟아지는 푸시, 똑같은 내용의 중복 메일 — 아무도 원하지 않는 경험이 구조적으로 만들어진 셈입니다.
LinkedIn은 이 문제를 풀기 위해 "적절한 메시지를, 적절한 회원에게, 적절한 채널로, 적절한 시간에, 적절한 빈도로" — 이른바 5 Rights 원칙을 목표로 ATC를 설계했습니다.
회원에게 알림을 보내려는 모든 요청은 일단 Kafka 토픽에 기록되고, ATC가 이를 읽어서 처리합니다. ATC는 크게 세 부분으로 구성됩니다.
- Partitioners(파티셔너): Kafka에서 들어오는 모든 알림 요청을 읽어, 수신자의 해시를 기준으로 파이프라인 인스턴스 전체에 고르게 분산시킵니다. 이때 형식이 잘못된 메시지를 걸러내는 1차 필터링도 함께 수행합니다.
- Relevance processors(연관 처리기): 개인화된 머신러닝 모델을 읽어 Samza의 상태 저장소에 보관해두고, 들어온 요청에 점수를 매깁니다. 이 점수로 "이 알림을 버릴지, 이메일로 보낼지, 푸시로 보낼지"를 결정합니다.
- ATC pipeline(파이프라인): 앞 두 단계의 결과를 모아 최종 결정을 내립니다. 중복 알림은 병합하고, 알림 수는 임계치로 제한하며, 보낼 시점도 조절합니다. 예를 들어 자정에 밀린 푸시를 그대로 쏟아내는 대신, 스케줄러로 적당한 시간에 보내도록 예약합니다.

LinkedIn 엔지니어링 블로그에서 성과 수치를 확인하고 솔직히 놀랐습니다. 불만 신고가 절반으로, 사이트 전체 참여도는 두 자릿수 % 상승. 회원 간 메시징 P90 지연은 12초에서 1.5초로 뚝 떨어졌고, 지금은 하루 10억 건 이상의 요청을 처리합니다. 알림 경험 하나 바꾸겠다고 만든 시스템이 이 정도 임팩트를 낸 것입니다.
ATC가 활용한 Samza의 세 가지 무기
- 상태 저장 처리(Stateful Processing): 연관 모듈의 머신러닝 모델을 로컬 저장소 RocksDB에 두고, 사용자 피드백에 따라 실시간으로 업데이트합니다.
- 비동기 API와 멀티스레딩: 높은 처리량으로 원격 호출을 수행해, 푸시 알림의 종단 간 지연 시간(p90)을 낮춥니다.
- 호스트 어피니티(Host Affinity): 증분 체크포인트와 호스트 선호도를 결합해, 업그레이드 중에도 다운타임 없이 서비스하고 장애가 나도 즉시 복구합니다.
Samza의 진짜 차별점: 로컬 상태
제가 면접에서 가장 아쉬웠던 부분이 바로 이 설명을 못 했다는 점입니다. 여기서 한 가지는 꼭 짚고 가야 합니다. Samza를 다른 스트림 처리 시스템과 가르는 결정적 특징이 바로 로컬 상태(local state)이기 때문입니다.
보통 스트림 처리 작업은 어딘가에 저장된 상태를 읽고 씁니다. 그런데 이 상태를 원격 데이터베이스에서 읽으면 병목이 생깁니다. 스트림이 메시지를 처리하는 속도가 DB가 요청을 받아주는 속도보다 훨씬 빠르기 때문입니다. 빠른 컨베이어 벨트 끝에 느린 검수원 한 명을 세워둔 꼴입니다.
Samza는 발상을 뒤집었습니다. 각 작업이 쓰는 상태를 같은 머신의 디스크(RocksDB)에 바로 붙여서 둡니다. 원격 호출이 사라지니 읽기와 쓰기 성능이 수십, 수백 배로 빨라집니다. 그러면 머신이 죽으면 상태도 같이 날아가는 것 아니냐고요? 그래서 모든 상태 변경을 changelog 스트림(보통 Kafka)에 기록해둡니다. 장애가 나면 이 로그를 재생해 상태를 그대로 복원합니다. 빠른 로컬 읽기와 안전한 복구를 동시에 잡은 설계입니다.
면접에서 자주 나오는 함정 두 가지
ATC 사례를 면접에서 이야기할 때 두 가지 함정을 특히 조심하시기 바랍니다.
첫 번째는 "RocksDB가 죽으면 상태가 날아간다"는 걱정만 이야기하고 changelog 복구 메커니즘을 빠뜨리는 경우입니다. 면접관께서 "그러면 failover 시 상태 복구는 어떻게 하나요?"라고 바로 이어서 물으실 때 막히게 됩니다. 답은 "Kafka changelog 토픽을 재생(replay)해서 복원한다"입니다.
두 번째는 파티셔닝 전략을 빠뜨리는 경우입니다. 수신자 ID를 키로 파티셔닝하지 않으면 같은 사용자의 알림이 서로 다른 처리 노드로 흩어집니다. 그러면 "오늘 알림 5건 이상 받은 사람은 발송 중지"라는 규칙을 유지할 수 있는 상태가 한 노드에 모이지 않아 일관성이 깨집니다. 키 기반 파티셔닝이 상태 일관성의 전제 조건이라는 점이 핵심 답변입니다.
시스템 디자인 관점: 언제 Samza이고 언제 아닌가
스트림 처리 프레임워크는 Samza 말고도 많습니다. 면접에서 "왜 이걸 골랐나요?"라는 질문에 답하려면 차이를 알아야 합니다.
- Kafka Streams: 클러스터를 따로 띄울 필요 없이 애플리케이션에 끼워 쓰는 임베디드 라이브러리입니다. 마이크로서비스에 가볍게 붙이기 좋습니다.
- Apache Flink: 배치와 스트림을 하나의 모델로 통합하고 생태계가 넓습니다. 복잡한 처리나 배치까지 함께 필요할 때 강합니다.
- Apache Samza: Kafka 친화적이고, 대규모 상태 기반 스트림 처리에 강합니다. 수백 TB 규모의 상태를 로컬에 두고 안정적으로 다뤄야 한다면 좋은 선택지입니다.
정답은 상황에 따라 다릅니다. 이미 파이프라인이 Kafka 중심이고 거대한 상태를 빠르게 다뤄야 한다면 Samza가, 배치까지 한 엔진으로 묶고 싶다면 Flink가 더 어울립니다.
정리
ATC 사례가 알려주는 교훈을 정리하면 이렇습니다.
- 배치로는 못 푸는 실시간 의사결정이 있을 때 스트림 처리가 답이 됩니다.
- 상태를 원격이 아니라 로컬(RocksDB)에 두고 changelog로 복구하는 설계가 Samza의 핵심입니다.
- 키 기반 파티셔닝으로 같은 사용자의 알림 상태를 한 노드에 모으는 것이 5 Rights 원칙의 기술적 전제입니다.
- 중복 병합, 임계치 제한, 발송 시점 예약처럼 회원 경험을 지키는 로직이 결국 불만 절반 감소, 참여도 두 자릿수 % 상승이라는 성과로 이어졌습니다.
시스템 디자인 면접에서 알림 시스템이나 실시간 파이프라인 설계가 나온다면, 단순히 "Kafka로 받아서 처리합니다"에서 멈추지 마십시오. "상태를 어디에 둘 것인가, 원격 DB 병목을 어떻게 피할 것인가, 파티셔닝 키를 어떻게 잡을 것인가"까지 짚으면, 진짜 대규모 시스템을 고민해본 사람으로 보입니다. 더 깊은 내용은 LinkedIn 엔지니어링 블로그에서 확인하실 수 있습니다.