44강
설계 과제 — 뉴스 피드
글 하나를 쓰면 몇 명에게 전해야 하는가. 이 질문 하나가 설계의 전부다
먼저 보면 좋은 강의: 웹 요청의 해부, 지연 시간과 처리량, 시스템 설계 면접 6단계 방법, 설계 과제 — URL 단축 서비스
단계별로 보기
스페이스바로 재생/정지, 좌우 화살표로 단계 이동, R로 처음으로 돌아갑니다.
대기 중1 / 4 단계 — 쓸 때 뿌릴 것인가, 읽을 때 모을 것인가
길은 둘뿐입니다. 글을 쓸 때 팔로워 200명의 상자에 미리 복사본을 넣어두거나, 아니면 저장만 해두고 누가 피드를 열 때 그때 300명분을 긁어모으거나. 정확히 거울상입니다. 한쪽의 비용을 다른 쪽으로 옮긴 것뿐입니다. 그래서 숫자로 고릅니다. 사람들은 글을 쓰는 것보다 읽는 것을 스무 배 많이 합니다. 비싼 일은 드물게 일어나는 쪽에 둡니다. 그래서 쓸 때 뿌리는 쪽을 고릅니다.
이 강의를 마치면
- '글을 쓸 때 미리 뿌리기'와 '읽을 때 그때 모으기' 두 방식의 비용 구조를 비교한다.
- 팔로워 수가 극단적으로 불균등하기 때문에 하나의 방식으로는 풀 수 없음을 설명한다.
- 유명 사용자의 글쓰기를 동기로 처리하면 왜 시스템이 무너지는지 관찰한다.
- 큐와 워커로 뿌리기를 뒤로 미루고, 일반 사용자와 유명 사용자를 다르게 다루는 혼합 설계를 구성한다.
팬아웃이 전부다
뉴스 피드를 설계하라고 하면 대부분 화면부터 생각합니다. 글 목록, 좋아요, 무한 스크롤. 전부 중요하지만 설계 문제는 거기 있지 않습니다.
진짜 문제는 이 한 문장입니다.
누가 글 하나를 썼다. 이것을 팔로워들의 피드에 어떻게 넣을 것인가?
이것을 팬아웃(fan-out), 우리말로 뿌리기라고 합니다. 그리고 이 강의의 제목이기도 한 명제는 이것입니다.
뿌리기가 전부다. 나머지는 부수적이다.
왜 이게 어려운지 보려면, 먼저 두 가지 방법밖에 없다는 걸 알아야 합니다.
---
두 개의 길
길 A — 쓸 때 미리 뿌린다
글을 쓰는 순간, 팔로워 200명 각자의 피드 상자에 복사본을 하나씩 넣어둡니다.
신문 배달과 같습니다. 새벽에 미리 집집마다 넣어둡니다.
- 읽기: 자기 상자만 열면 끝. 엄청나게 빠릅니다.
- 쓰기: 200번의 넣기 작업. 느립니다.
길 B — 읽을 때 그때 모은다
글을 쓰면 그냥 내 글 목록에 한 번만 저장합니다. 누군가 피드를 열면 그때 그 사람이 팔로우하는 300명의 최근 글을 전부 가져와 시간순으로 섞습니다.
도서관과 같습니다. 미리 배달하지 않고, 필요할 때 가서 찾습니다.
- 쓰기: 한 번의 저장. 엄청나게 빠릅니다.
- 읽기: 300명분을 긁어와 정렬. 느립니다.
어느 쪽인가
정확히 거울상입니다. 한쪽의 비용을 다른 쪽으로 옮긴 것뿐입니다. 그렇다면 무엇으로 고를까요? 숫자로 고릅니다.
뉴스 피드에서 사람들은 글을 쓰는 것보다 읽는 것을 훨씬 많이 합니다. 하루에 글 한 개 쓰고 피드는 스무 번 봅니다. 읽기가 쓰기보다 압도적으로 많습니다.
비싼 일은 드물게 일어나는 쪽에 두는 게 맞습니다. 그러니 답은 길 A입니다. 쓸 때 고생하고 읽을 때 편한 쪽.
대부분의 서비스가 실제로 이렇게 시작합니다. 그리고 한 종류의 사용자를 만나기 전까지는 잘 돌아갑니다.
---
유명 사용자라는 재앙
팔로워가 500만 명인 사람이 글을 씁니다.
길 A대로 하면 500만 개의 복사본을 만들어 500만 개의 상자에 넣어야 합니다.
이게 무슨 뜻인지 감을 잡아 봅시다. 넣기 한 번에 1밀리초가 걸린다고 하면, 500만 번이면 5,000초, 약 83분입니다.
이 작업을 사용자가 "게시" 버튼을 누른 뒤 응답이 올 때까지 기다리게 하면 어떻게 될까요? 브라우저는 30초쯤 뒤에 포기합니다. 유명인은 글을 못 올립니다. 그리고 그동안 그 작업이 데이터베이스를 붙잡고 있어서 다른 모든 사용자의 요청까지 같이 죽습니다.
한 사람의 글 한 줄이 서비스 전체를 무너뜨립니다.
게다가 대부분이 헛수고다
더 억울한 사실이 있습니다. 팔로워 500만 명 중 오늘 앱을 여는 사람은 몇 %일까요? 대부분의 계정은 휴면 상태입니다. 500만 개의 상자 중 대부분은 아무도 열지 않습니다.
한 번도 읽히지 않을 복사본을 만드느라 83분을 쓴 것입니다.
---
해법은 두 가지를 겹친다
첫째 — 뿌리기를 사용자에게서 떼어낸다
게시 버튼을 눌렀을 때 실제로 필요한 일은 이것뿐입니다.
"당신의 글을 확실히 저장했습니다."
뿌리기는 지금 당장 끝나야 할 이유가 하나도 없습니다. 팔로워 화면에 3초 뒤에 떠도 아무도 모릅니다. 그러니 이렇게 나눕니다.
사용자 → 글쓰기 API → "저장했습니다" (여기서 사용자는 끝. 50밀리초)
↓
팬아웃 큐 ← 뿌릴 일을 여기에 적어둔다
↓
워커들 ← 뒤에서 각자 속도대로 뿌린다
↓
데이터베이스사용자는 50밀리초 만에 자유로워지고, 뿌리기는 뒤에서 몇 초에 걸쳐 진행됩니다. 앞서 로깅 강의에서 본 것과 정확히 같은 구조입니다.
대신 잃는 것이 있습니다. 팔로워 화면에 즉시 안 뜹니다. 몇 초 늦습니다. 이것이 우리가 지불하는 대가이고, 뉴스 피드에서는 충분히 받아들일 만한 대가입니다. 은행 잔고가 몇 초 늦게 반영되는 것과는 차원이 다른 문제입니다.
둘째 — 유명인은 아예 안 뿌린다
큐에 넣어도 500만 번은 500만 번입니다. 워커를 아무리 늘려도 낭비는 낭비입니다.
그래서 유명 사용자에게만 길 B를 씁니다.
유명인이 글을 쓰면 아무에게도 안 뿌립니다. 그냥 자기 글 목록에 한 번 저장하고 끝입니다. 대신 누군가 피드를 열 때, 이렇게 합니다.
내 피드 = 내 상자에 미리 배달된 글들 (일반 사용자 → 길 A)
+ 내가 팔로우하는 유명인 몇 명의 최신 글 (유명인 → 길 B)
두 개를 그 자리에서 섞는다이게 왜 되는지가 아름답습니다.
- 일반 사용자는 수가 많지만 팔로워가 적습니다 → 뿌려도 싸다
- 유명인은 팔로워가 많지만 수가 적습니다 → 읽을 때 몇 명분만 더 가져오면 된다
내가 팔로우하는 유명인은 보통 10~20명입니다. 피드를 열 때 20명분의 최근 글을 더 가져오는 것은 전혀 비싸지 않습니다. 게다가 유명인의 글은 모두가 같은 것을 읽으므로 캐시에 완벽하게 들어맞습니다. 500만 명이 같은 글 하나를 읽는다면, 그건 캐시가 가장 잘하는 일입니다.
이 혼합 방식을 하이브리드 팬아웃이라고 합니다.
기준선은 어디에
"몇 명부터 유명인인가?"에 정답은 없습니다. 보통 수만 명 선에서 자릅니다. 중요한 것은 숫자가 아니라 두 종류를 다르게 다뤄야 한다는 사실 자체입니다.
---
이 강의가 진짜 가르치는 것
뉴스 피드 문제의 교훈은 뉴스 피드에만 적용되는 게 아닙니다.
평균으로 설계하면 반드시 죽는다.
"평균 팔로워 수가 300명이니 뿌리기 300번이면 되겠네"라고 계산하면 완벽하게 틀립니다. 평균은 500만 명짜리 계정 하나를 완전히 숨깁니다. 그리고 시스템은 평균에서 죽지 않습니다. 극단에서 죽습니다.
이 패턴은 어디에나 있습니다. 인기 상품 하나에 몰리는 주문, 특정 지역에 몰리는 검색, 한 계정에 몰리는 API 호출. 분포를 안 보고 평균만 본 설계는 전부 같은 방식으로 무너집니다.
직접 해보세요
아래 과제에는 시나리오가 두 개 있습니다. 하나는 일반 사용자, 하나는 유명 사용자입니다. 둘 다 통과해야 합니다.
일반 사용자만 보고 만든 설계는 유명 사용자 시나리오에서 반드시 무너집니다. 그게 이 과제의 요점입니다.
이 강의가 단순화한 것
교육용 모형은 언제나 무언가를 생략합니다. 무엇을 생략했는지 아는 것도 학습의 일부입니다.
전제한 것
- 유명 사용자의 팬아웃 폭발을 표현하기 위해, '글 한 번에 팔로워 수만큼의 쓰기'를 '초당 쓰기 요청이 매우 많은 상태'로 치환해 모델링했다. 실제로는 글 하나가 내부에서 수백만 건으로 증폭되는 것이지 사용자가 초당 수십 번 글을 쓰는 것이 아니다.
- 과제에서 팬아웃 워커가 팔로워 피드 상자에 쓰는 비용은 워커 자신의 작업 시간으로 모델링했다. 그래서 워커에서 데이터베이스로 가는 선이 따로 없다.
- 팔로워 수 200명과 500만 명은 두 극단을 보여주기 위한 값이며, 실제 서비스의 분포는 연속적이다.
- 피드 읽기를 '캐시에서 미리 만들어진 피드를 꺼내는 것' 하나로 단순화했다. 실제로는 순위 매기기와 개인화가 추가로 들어간다.
- 이 모형에서 '피드 상자'는 캐시 부품으로, '뿌리는 사람'은 워커 부품으로 대신 표현했다.
- 유명인 경로를 읽을 때 합치는 비용은 캐시 적중으로 흡수된다고 보고 따로 모델링하지 않았다.
다루지 않은 것
- 피드 순위 알고리즘, 개인화, 광고 삽입
- 언팔로우·차단·삭제 시 이미 뿌려진 복사본을 정리하는 문제
- 피드 상자의 크기 제한과 오래된 항목의 정리
- 일반/유명을 가르는 기준선을 실시간으로 조정하는 문제
- 여러 지역에 걸친 배치와 지역 간 복제
더 읽을거리: Design a News Feed · Google SRE Book — Addressing Cascading Failures
이제 직접 만들어 보세요
읽어서 아는 것과 만들어서 아는 것은 다릅니다. 컴포넌트를 배치하고 트래픽을 흘려서 실제로 동작하는지 확인해 보세요.
이 강의에 물어보기
하루 5번이 강의 본문만 근거로 답합니다. 강의에 없는 내용은 지어내지 않습니다.