Chapter 11
뉴스 피드 시스템 설계
피드 발행과 생성 두 흐름으로 나뉘는 시스템. 쓰기 시점(푸시)·읽기 시점(풀) 팬아웃과 하이브리드 전략, 5계층 캐시로 빠른 읽기를 만든다.
뉴스 피드는 발행 흐름과 생성 흐름으로 나뉜다
뉴스 피드 시스템은 사용자가 올린 스토리를 친구들에게 전달하는 피드 발행과, 친구들의 스토리를 모아 시간 흐름 역순으로 보여 주는 뉴스 피드 생성의 두 흐름으로 구성된다. 설계의 핵심은 새 포스팅을 친구의 피드로 전달하는 팬아웃 방식이며, 친구가 많은 사용자의 핫키 문제를 풀기 위해 푸시 모델과 풀 모델을 결합한 하이브리드 전략을 사용한다.
11장 스터디 구성
11장은 뉴스 피드 시스템 설계를 시스템 설계 면접의 4단계 접근법에 따라 전개한다. 아래는 본문 학습을 위한 스터디 구성이다.
1단계: 문제 이해 및 설계 범위 확정
- 지원 클라이언트와 정렬 방식
- 친구 수와 트래픽 규모
- 피드에 포함될 콘텐츠 종류
- 개략적 규모 추정
2단계: 개략적 설계안 제시 및 동의 구하기
- 뉴스 피드 API (발행 / 읽기)
- 피드 발행 흐름
- 뉴스 피드 생성 흐름
3단계: 상세 설계
- 피드 발행 흐름 상세 설계
- 쓰기 시점 팬아웃 (푸시 모델)
- 읽기 시점 팬아웃 (풀 모델)
- 하이브리드 모델
- 팬아웃 서비스 동작 순서
- 피드 읽기 흐름 상세 설계
- 캐시 구조
4단계: 마무리
- 데이터베이스 규모 확장
- 무상태 웹 계층
- 메시지 큐와 모니터링
- 가용성과 일관성
1단계: 문제 이해 및 설계 범위 확정
뉴스 피드 시스템을 설계하라는 요구를 받으면, 가장 먼저 면접관이 어떤 기능과 규모를 기대하는지 질문으로 좁혀야 한다. 페이스북, 인스타그램, 트위터의 피드는 겉모습은 비슷하지만 정렬 방식과 트래픽 특성이 다르다.
확인해야 할 질문
- 모바일 앱과 웹 중 어떤 클라이언트를 지원하는가?
- 피드는 어떤 순서로 정렬되는가? 본 설계에서는 시간 흐름 역순(최신 포스팅이 위)으로 가정한다.
- 한 사용자는 최대 몇 명의 친구 또는 팔로어를 가질 수 있는가?
- 트래픽 규모는 어느 정도인가? (일간 활성 사용자 수)
- 피드에는 텍스트만 포함되는가, 이미지와 동영상도 포함되는가?
개략적 규모 추정
대표 전제는 일간 활성 사용자(DAU) 1,000만 명이며, 각 사용자는 하루에 여러 번 피드를 조회하고 여러 친구를 갖는다. 이 규모에서는 피드를 매번 즉석에서 계산하면 읽기 지연이 커지므로, 미리 계산해 캐시에 저장하는 전략이 중요해진다.
| 항목 | 값 | 메모 |
|---|---|---|
| 일간 활성 사용자(DAU) | 1,000만 | 전제 |
| 읽기:쓰기 비율 | 읽기 우세 | 피드 조회가 발행보다 훨씬 잦다 |
| 정렬 방식 | 시간 역순 | 최신 포스팅이 상단 |
| 콘텐츠 종류 | 텍스트·이미지·동영상 | 미디어는 CDN으로 전달 |
2단계: 개략적 설계안 제시 및 동의 구하기
설계안은 피드 발행과 뉴스 피드 생성의 두 부분으로 나뉜다. 피드 발행은 사용자가 스토리를 올리면 데이터를 저장하고 친구들의 피드로 전달하는 흐름이고, 뉴스 피드 생성은 친구들의 포스팅을 시간 역순으로 모아 보여 주는 흐름이다.
뉴스 피드 API
뉴스 피드 API는 클라이언트가 서버와 통신하는 수단이며 HTTP 기반이다. 핵심은 발행 API와 읽기 API 두 가지다.
| API | 엔드포인트 | 설명 |
|---|---|---|
| 피드 발행 API | POST /v1/me/feed | content(포스팅 내용)와 auth_token(인증)을 보내 새 스토리를 게시한다. |
| 피드 읽기 API | GET /v1/me/feed | auth_token으로 사용자를 식별하고 해당 사용자의 뉴스 피드를 가져온다. |
피드 발행 흐름
사용자가 새 포스팅을 올리면 데이터가 저장되고, 친구들의 뉴스 피드로 전달된다. 주요 컴포넌트는 다음과 같다.
모바일 앱이나 브라우저에서 새 포스팅을 올리는 주체다.
트래픽을 여러 웹 서버로 분산한다.
HTTP 요청을 내부 서비스로 중계한다. 인증과 전송률 제한을 담당한다.
새 포스팅을 데이터베이스와 캐시에 저장한다.
새 포스팅을 친구들의 뉴스 피드에 전달(팬아웃)한다.
친구들에게 새 포스팅이 올라왔음을 푸시 알림 등으로 알린다.
뉴스 피드 생성 흐름
사용자가 피드를 요청하면, 시스템은 미리 계산해 둔 피드를 캐시에서 가져와 반환한다. 피드는 친구들의 포스팅을 시간 역순으로 모은 결과다.
뉴스 피드를 읽는 주체다.
트래픽을 여러 웹 서버로 분산한다.
트래픽을 뉴스 피드 서비스로 전달한다.
캐시에서 피드를 구성해 사용자에게 반환한다.
피드를 렌더링할 때 필요한 포스팅 ID 목록을 보관한다.
3단계: 상세 설계
개략적 설계의 두 흐름을 더 깊게 살펴본다. 피드 발행에서는 팬아웃 서비스가, 피드 읽기에서는 캐시 구조가 핵심이다.
피드 발행 흐름 상세 설계 — 팬아웃
팬아웃은 하나의 입력을 여러 출력으로 퍼뜨리는 것을 뜻한다. 부채(fan)를 펼치면 하나의 축에서 여러 갈래로 펴지는 모습에서 온 말이다. 뉴스 피드에서는 한 사용자가 올린 포스팅 하나를 그 사용자의 모든 친구 피드로 퍼뜨리는 동작을 가리킨다.
팬아웃은 새 포스팅을 친구들의 뉴스 피드로 전달하는 과정이다. 언제 전달하느냐에 따라 쓰기 시점 팬아웃(푸시)과 읽기 시점 팬아웃(풀)로 나뉜다.
포스팅이 작성되는 순간 친구들의 피드를 미리 갱신한다.
- 피드가 실시간으로 갱신된다.
- 이미 갱신돼 있으므로 읽기가 빠르다.
- 친구가 많으면 모든 피드를 갱신하는 데 시간이 오래 걸린다(핫키 문제).
- 비활성 사용자의 피드까지 갱신해 자원이 낭비된다.
피드를 요청하는 순간 친구들의 포스팅을 모아 계산한다.
- 비활성 사용자의 피드를 미리 계산하지 않아 자원을 아낀다.
- 친구에게 푸시하지 않으므로 핫키 문제가 없다.
- 읽기 시점에 계산하므로 피드를 읽는 데 시간이 오래 걸린다.
하이브리드 모델
본 설계에서는 두 방법을 결합해 장점만 취한다. 대부분의 사용자에게는 푸시 모델을 사용해 피드를 빠르게 가져오게 하고, 친구나 팔로어가 매우 많은 사용자(셀러브리티)에게는 풀 모델을 사용해 팔로어가 필요할 때 포스팅을 가져가도록 함으로써 시스템 과부하를 막는다. 또한 안정 해시(consistent hashing)로 요청과 데이터를 고르게 분산해 핫키 문제를 완화한다.
안정 해시는 서버를 추가하거나 제거해도 키 대부분의 위치가 바뀌지 않도록 해 주는 분산 방식이다. 단순 해시(키 % 서버 수)는 서버 수가 바뀌면 거의 모든 키가 재배치되지만, 안정 해시는 영향받는 키를 약 1/N로 줄여 특정 서버로 부하가 쏠리는 핫키 문제를 완화한다.
팬아웃 서비스 동작 순서
- 그래프 데이터베이스에서 친구 ID 목록을 가져온다.
- 사용자 정보 캐시에서 친구들의 정보를 가져온다.
- 친구 목록과 새 스토리의 포스팅 ID를 메시지 큐에 넣는다.
- 팬아웃 작업 서버가 메시지 큐에서 데이터를 꺼내, 뉴스 피드 데이터를 뉴스 피드 캐시에 기록한다.
뉴스 피드 캐시는 <포스팅 ID, 사용자 ID> 형태의 매핑으로 저장한다. 포스팅 본문과 사용자 정보 전체를 복제하지 않고 ID만 보관해 메모리를 절약하며, 실제 본문은 읽기 시점에 별도 캐시에서 가져온다.
피드 읽기 흐름 상세 설계
사용자가 피드를 요청하면 다음 순서로 처리된다.
- 사용자가 뉴스 피드를 요청한다.
- 로드밸런서가 요청을 웹 서버로 분산한다.
- 웹 서버가 뉴스 피드 서비스를 호출한다.
- 뉴스 피드 서비스가 캐시에서 포스팅 ID 목록을 가져온다.
- 포스팅 ID로 콘텐츠 캐시와 사용자 캐시에서 본문과 사용자 정보를 채워(hydrate) 완전한 피드를 만든다.
- 완성된 피드를 사용자에게 JSON 형태로 반환한다.
이미지나 동영상 같은 미디어 콘텐츠는 CDN을 통해 전달해 빠르게 로딩되도록 한다.
캐시 구조
뉴스 피드 시스템은 읽기가 매우 잦으므로 캐시가 성능의 핵심이다. 캐시는 역할에 따라 다섯 계층으로 나눌 수 있다.
| 캐시 계층 | 보관 데이터 |
|---|---|
| 뉴스 피드 | 사용자별 뉴스 피드를 구성하는 포스팅 ID 목록을 보관한다. |
| 콘텐츠 | 포스팅 본문 데이터를 보관한다. 인기 콘텐츠는 별도의 핫 캐시에 둔다. |
| 소셜 그래프 | 사용자 간 친구·팔로 관계 데이터를 보관한다. |
| 행동(action) | 좋아요, 답글 등 포스팅에 대한 사용자 행동을 보관한다. |
| 횟수(counters) | 좋아요 수, 답글 수, 팔로어 수, 팔로잉 수 등 카운터를 보관한다. |
4단계: 마무리
기본 설계가 요구사항을 만족하는지 점검하고, 운영 환경에서 추가로 고려할 주제를 정리한다.
데이터베이스 규모 확장
뉴스 피드 시스템도 결국 1장에서 다룬 일반적인 규모 확장 기법을 그대로 적용한다. 데이터베이스 확장은 다음 항목들을 함께 고려한다.
| 고려 항목 | 내용 |
|---|---|
| 수직적 vs 수평적 | 한 서버의 사양을 높이는 수직 확장은 한계와 단일 장애점이 있다. 서버 수를 늘리는 수평 확장이 대규모에 적합하다. |
| SQL vs NoSQL | 복잡한 관계·트랜잭션이 필요하면 SQL, 막대한 쓰기·유연한 스키마가 필요하면 NoSQL을 택한다. 워크로드 특성으로 결정한다. |
| 주-부 다중화 | 쓰기는 주(master), 읽기는 부(replica)로 분산하는 주-부 다중화(master-slave replication)로 읽기 부하를 나눈다. |
| 복제본 읽기 연산 | 읽기 요청을 여러 복제본으로 보내 처리량을 높인다. 단, 복제 지연 때문에 약간 오래된 데이터를 읽을 수 있다. |
| 일관성 모델 | 강한 일관성과 결과적 일관성 중 선택한다. 피드는 보통 결과적 일관성으로 충분하다. |
| 데이터베이스 샤딩 | 데이터를 여러 노드에 나눠 저장한다. 아래에서 더 설명한다. |
샤딩은 하나의 큰 데이터베이스를 여러 조각(shard)으로 쪼개 서로 다른 서버에 나눠 저장하는 것이다. 한 서버가 모든 데이터를 감당하지 못할 때, 사용자 ID나 포스팅 ID를 기준으로 데이터를 분산해 각 서버가 일부만 책임지게 한다. 복제(replication)가 같은 데이터의 사본을 여러 서버에 두는 것이라면, 샤딩은 서로 다른 데이터를 여러 서버에 나누는 것이다. 어떤 샤드로 보낼지 정할 때 5장의 안정 해시를 활용할 수 있다.
그 외 규모 확장 고려사항
- 웹 계층 무상태화 — 웹 서버를 무상태(stateless)로 운영해 로드밸런서 뒤에서 자유롭게 수평 확장한다. 세션 같은 상태 정보는 외부 저장소에 둔다.
- 가능한 한 많은 데이터 캐시 — 읽기가 잦으므로 피드· 콘텐츠·소셜 그래프·카운터 등을 적극적으로 캐시해 데이터베이스 접근을 줄인다.
- 여러 데이터 센터 지원 — 여러 데이터 센터에 배포해 지역 지연을 줄이고 한 센터 장애에 대비한다.
- 메시지 큐로 결합도 낮추기 — 팬아웃 같은 작업을 메시지 큐로 비동기 처리해 컴포넌트 사이의 결합도를 낮추고 트래픽 급증을 흡수한다.
- 핵심 메트릭 모니터링 — 트래픽이 몰리는 시간대의 QPS, 사용자가 뉴스 피드를 새로고침할 때의 지연 시간 같은 지표를 추적해 병목을 조기에 발견한다.
가용성과 일관성
- 캐시와 데이터베이스를 다중화하여 단일 장애점을 제거한다.
- 피드는 결과적 일관성으로도 충분한 경우가 많다. 새 포스팅이 친구의 피드에 수 초 늦게 보여도 사용성에 큰 문제가 없다.
뉴스 피드 설계의 핵심은 읽기를 빠르게 만드는 것이며, 그 답은 대부분의 사용자에게 미리 계산해 두는 푸시 모델과, 친구가 매우 많은 사용자에게 적용하는 풀 모델을 결합한 하이브리드 팬아웃에 있다.