Please enable JavaScript to view the comments powered by Disqus.대규모 시스템의 핵심: 팬아웃(Fanout) Push vs Pull 모델과 Kafka, RabbitMQ 비교
Search
🏷️

대규모 시스템의 핵심: 팬아웃(Fanout) Push vs Pull 모델과 Kafka, RabbitMQ 비교

태그
시스템설계
팬아웃
메시지큐
Kafka
RabbitMQ
공개여부
작성일자
2026/04/04

1. 서론: 당신의 뉴스 피드는 어떻게 1초 만에 로딩될까?

인스타그램, 트위터(X), 페이스북과 같은 소셜 미디어 앱을 열었을 때, 수백 명의 친구들이 올린 최신 게시물이 1초도 안 되는 짧은 시간 안에 내 화면에 렌더링됩니다. 주니어 개발자 시절, 우리는 흔히 이 과정을 단순하게 생각하곤 합니다. "내가 팔로우하는 유저들의 게시물을 데이터베이스에서 SELECT 해오면 되는 것 아닌가?"
하지만 사용자가 수천만 명, 수억 명 단위로 넘어가는 대규모 시스템에서는 단순한 SELECT 쿼리만으로는 엄청난 트래픽을 감당할 수 없습니다. 수백만 명의 사용자가 동시에 앱을 켤 때마다 무거운 조인(Join) 쿼리가 데이터베이스에 쏟아진다면, 시스템은 순식간에 마비되고 말 것입니다.
이러한 문제를 해결하기 위해 대규모 시스템 설계에서 반드시 등장하는 핵심 개념이 바로 팬아웃(Fanout)입니다. 오늘 이 블로그 포스트에서는 팬아웃의 두 가지 핵심 전략인 Push 모델(Fanout-on-write)Pull 모델(Fanout-on-read)의 원리와 트레이드오프(Trade-off)를 깊이 있게 살펴봅니다. 더 나아가, 이러한 대규모 데이터를 비동기적으로 처리하기 위해 필수적으로 사용되는 메시지 브로커인 RabbitMQApache Kafka를 아키텍처 관점에서 비교해 보겠습니다.

2. 팬아웃(Fanout)이란 무엇인가?

'팬아웃(Fanout)'이라는 용어는 본래 전자 공학에서 하나의 논리 게이트 출력이 얼마나 많은 다른 게이트의 입력으로 연결되는지를 의미합니다. 소프트웨어 아키텍처, 특히 뉴스 피드 시스템에서 팬아웃은 어떤 사용자가 게시물을 올렸을 때, 그 게시물을 사용자의 모든 친구(또는 팔로워)에게 전달하는 과정을 뜻합니다.
이 팬아웃을 어느 시점에 수행하느냐에 따라 시스템의 성능과 병목 지점이 완전히 달라집니다.

2.1. Fanout-on-Write (Push 모델)

Push 모델은 사용자가 게시물을 작성하는(Write) 시점에 뉴스 피드를 미리 계산하여 업데이트하는 방식입니다. 새로운 포스트가 작성되면, 시스템은 해당 사용자의 팔로워 목록을 조회한 뒤, 팔로워들의 '뉴스 피드 캐시(Cache)'에 새 게시물의 ID를 직접 밀어 넣습니다(Push).
원리와 장점: 데이터가 쓰이는 시점에 이미 모든 팔로워의 피드가 완성되어 있으므로, 읽기(Read) 작업이 매우 빠릅니다.* 사용자가 앱을 켰을 때 데이터베이스를 무겁게 조회할 필요 없이, 자신의 캐시에 이미 완성된 피드를 O(1)의 시간에 가깝게 가져오기만 하면 됩니다. 실시간성이 보장된다는 큰 장점이 있습니다.
치명적인 단점 (쓰기 증폭, Write Amplification): 팔로워 수가 수천만 명에 달하는 유명인(예: 저스틴 비버)이 게시물을 올린다고 가정해 봅시다. 단 한 번의 게시물 작성이 시스템 내부적으로는 3,000만 번의 캐시 쓰기(Write) 작업으로 증폭됩니다. 이를 핫키(Hotkey) 문제*라고 부릅니다. 트위터의 실제 데이터에 따르면, 초당 평균 4.6천 건의 트윗이 작성될 때, 이를 Push 모델로 처리하면 초당 34만 5천 건의 쓰기 작업이 발생합니다. 또한 거의 접속하지 않는 휴면 사용자의 피드까지 미리 업데이트해야 하므로 컴퓨팅 리소스의 낭비가 심합니다.

2.2. Fanout-on-Read (Pull 모델)

Pull 모델은 반대로 사용자가 뉴스 피드를 읽는(Read) 시점에 데이터를 계산하는 방식입니다. 게시물이 작성될 때는 단순히 작성자의 데이터베이스에만 저장해 둡니다. 이후 팔로워가 앱을 켜서 자신의 타임라인을 요청하면, 그때서야 팔로우하는 모든 사람의 최근 게시물을 가져와 병합하고 정렬하여 보여줍니다.
원리와 장점:* 쓰기 시점에 부하가 발생하지 않습니다. 수천만 명의 팔로워를 가진 유명인이 글을 쓰더라도 단 한 번의 DB 쓰기만 발생하므로 핫키 문제가 발생하지 않습니다. 또한, 접속하지 않는 휴면 사용자를 위해 리소스를 낭비할 필요가 없습니다.
치명적인 단점: 읽기(Read) 속도가 매우 느립니다.* 사용자가 피드를 요청할 때마다 무거운 연산을 즉석에서(On-demand) 수행해야 하므로, 시스템의 응답 시간이 길어지고 사용자 경험(UX)이 하락합니다.

2.3. 실무에서의 타협점: 하이브리드(Hybrid) 모델

소프트웨어 공학에 은탄환(Silver bullet)은 없습니다. 트위터나 페이스북 같은 기업들은 두 모델의 장점을 섞은 하이브리드(Hybrid) 모델을 사용합니다.
대부분의 일반 사용자에게는 읽기 속도가 빠른 Push 모델(Fanout-on-write)을 적용하여 피드를 빠르게 로딩합니다. 반면, 팔로워가 수백만 명인 유명인(Celebrity)의 경우, 이들의 게시물은 Push하지 않고 둡니다. 대신 팔로워가 피드를 읽을 때 유명인의 게시물만 따로 가져와서 기존 피드와 병합하는 Pull 모델(Fanout-on-read)을 적용합니다. 이를 통해 쓰기 증폭을 방지하면서도 전반적인 시스템의 읽기 성능을 최적화할 수 있습니다.
---

3. 비동기 팬아웃 처리를 위한 구원투수: 메시지 브로커

하이브리드 모델을 적용하더라도, 수많은 사용자의 캐시에 데이터를 밀어 넣는(Push) 작업은 여전히 매우 무겁습니다. 만약 웹 서버가 사용자의 게시물 작성 요청을 받고, 팔로워들의 캐시를 업데이트하는 모든 작업이 끝날 때까지 기다렸다가 응답을 준다면 사용자는 무한정 대기하게 될 것입니다.
이때 시스템 구성 요소들 간의 결합도를 낮추고(Decoupling), 작업을 비동기(Asynchronous)로 처리하기 위해 메시지 브로커(Message Broker)를 도입합니다. 웹 서버는 그저 "A가 새로운 글을 썼다"라는 이벤트를 메시지 큐에 던지고(Publish) 바로 사용자에게 성공 응답을 반환합니다. 그러면 뒤단에 있는 수많은 팬아웃 워커(Worker) 서버들이 큐에서 메시지를 꺼내어(Consume) 각 팔로워의 캐시를 업데이트하는 무거운 작업을 백그라운드에서 병렬로 처리합니다.
이 역할을 수행하는 대표적인 시스템이 바로 RabbitMQApache Kafka입니다. 주니어 개발자들은 이 두 가지를 혼동하기 쉬운데, 아키텍처 철학이 완전히 다릅니다.

3.1. RabbitMQ: 전통적인 스마트 브로커 (AMQP 스타일)

RabbitMQ는 전통적인 엔터프라이즈 메시징 표준인 AMQP를 구현한 메시지 브로커입니다.
작동 원리: RabbitMQ는 '소비(Consume) = 파괴(Destructive)'의 철학을 가집니다. 브로커는 메시지를 소비자(Consumer)에게 전달하고, 소비자가 성공적으로 처리했다는 확인(Acknowledgment)을 보내면 메시지를 큐에서 즉시 삭제*합니다.
로드 밸런싱 최적화:* 여러 대의 워커(Worker) 서버가 큐에 붙어 있을 때, RabbitMQ는 각 메시지를 워커들에게 라운드 로빈 방식으로 골고루 분배합니다.
언제 선택해야 할까?: 뉴스 피드 팬아웃보다는, 메시지 하나하나의 처리 비용이 매우 높고 순서가 중요하지 않으며 여러 워커가 작업을 나눠서 처리해야 하는 작업 큐(Task Queue) 패턴*에 적합합니다. 예를 들어 사용자가 업로드한 이미지를 리사이징하거나 썸네일을 생성하는 워커 시스템에 훌륭한 선택지입니다.

3.2. Apache Kafka: 대규모 스트림을 위한 로그 기반 브로커

Apache Kafka는 링크드인(LinkedIn)에서 대규모 로그 데이터를 처리하기 위해 개발한 시스템으로, 기존의 메시징 시스템과 달리 데이터베이스의 영속성(Durability) 개념을 메시징에 도입했습니다.
작동 원리: Kafka는 메시지를 삭제하지 않습니다. 들어온 이벤트를 디스크에 추가 전용 로그(Append-only Log) 형태로 영구적으로 기록(보존)합니다. 소비자(Consumer)들은 이 로그 파일에서 데이터를 마치 테이프를 읽듯이 순차적으로 읽어갑니다. 브로커는 메시지를 지우는 대신, 각 소비자가 로그의 어디까지 읽었는지를 나타내는 오프셋(Offset)*만을 기록합니다.
독립적인 멀티 컨슈밍(Multi-Consuming):* 메시지를 읽어도 삭제되지 않기 때문에, 완전히 다른 목적을 가진 여러 서비스가 동일한 메시지 스트림을 독립적으로 소비할 수 있습니다.
언제 선택해야 할까?: 팬아웃(Fanout)처럼 하나의 이벤트(예: 게시물 작성)를 수많은 서비스(캐시 업데이트 서비스, 알림 푸시 서비스, 빅데이터 분석 서비스)가 동시에 받아가야 하는 경우* 압도적인 성능을 자랑합니다. 디스크에 순차적으로 I/O를 수행하므로 초당 수백만 건의 메시지 처리(Throughput)가 가능하며, 시스템 장애 시 오프셋을 되감아(Replay) 과거 메시지부터 다시 처리하는 것도 가능합니다.
---

4. 개발자를 위한 아키텍처 선택 요약 가이드

지금까지 다룬 내용을 한눈에 파악할 수 있도록 핵심 트레이드오프를 표로 정리했습니다.
[팬아웃 모델 설계 트레이드오프]
비교 항목
Faount-on-Write (Push)
Fanout-on-Read (Pull)
작동 시점
글이 작성될 때 미리 피드 계산
피드를 읽어갈 때 실시간 계산
읽기 성능
매우 빠름 (O(1) 캐시 접근)
느림 (여러 사용자의 DB 접근 및 정렬 필요)
쓰기 성능
느림 (수많은 팔로워 캐시 업데이트 필요)
빠름 (작성자의 DB에만 저장)
핫키 문제
발생함 (유명인의 글 작성 시 시스템 마비 위험)
발생하지 않음
권장 사용처
일반적인 사용자의 피드 생성
수백만 팔로워를 가진 유명인(Celebrity)
[비동기 처리를 위한 메시지 브로커 선택 기준]
비교 항목
RabbitMQ (AMQP/JMS 기반)
Apache Kafka (로그 기반 브로커)
메시지 보존
처리 성공(Ack) 후 즉시 삭제
디스크에 설정된 기간 동안 영구 보존
소비자 패턴
메시지를 워커들에게 경쟁적으로 분배 (Load Balancing)
각 그룹이 오프셋을 이용해 독립적으로 소비 (Fan-out)
성능 (Throughput)
초당 수만 건 처리 적합 (메시지 삭제 및 라우팅 오버헤드)
초당 수백만 건 처리 적합 (순차적 디스크 I/O)
과거 데이터 재처리
불가 (삭제되었기 때문)
가능 (오프셋을 이전으로 돌려서 Replay 가능)
권장 사용처
무거운 단일 작업의 로드 밸런싱 (예: 이미지 처리, 이메일 전송)
대규모 이벤트 스트림 처리, 데이터 파이프라인 통합, 복수 서비스의 팬아웃

5. 결론: 완벽한 아키텍처는 없다, 트레이드오프만 있을 뿐

우리가 흔히 사용하는 인스타그램이나 트위터 같은 앱의 매끄러운 동작 뒤에는, 트래픽을 견디기 위한 엔지니어들의 치열한 고민이 숨어 있습니다.
단순히 "읽기를 빠르게 하기 위해 무조건 Push 모델을 쓴다"라거나 "Kafka가 요즘 대세니까 무조건 Kafka를 쓴다"는 식의 단순 결론("이렇게 해라")은 시스템 설계에서 가장 경계해야 할 태도입니다.
유명인의 트래픽을 방어하기 위해서는 Pull 모델을 섞어 써야 하며, 내가 설계하려는 시스템이 데이터의 영속성과 대규모 팬아웃이 필요한지(Kafka), 아니면 멱등성(Idempotence)이 보장된 개별 태스크 분배가 필요한지(RabbitMQ)에 따라 알맞은 기술을 선택해야 합니다. 이 글이 주니어 개발자 여러분이 각 기술이 탄생하게 된 원리와 근거를 깊이 이해하고, 상황에 맞는 올바른 트레이드오프를 내리는 데 도움이 되기를 바랍니다.