오늘은 책 1장에서 확성성을 설명하며 다룬 트위터(x) 사례를 집중 탐구하며 학습하고 실습을 해보겠습니다.

확장성에는 정답이 없고, 데이터의 특성(부하 매개변수)에 따라 설계가 완전히 달라져야 한다는 것을 보여줍니다.

 

1. 문제의 정의: 부하 매개변수(Load Parameter)

트위터의 주요 기능은 크게 두 가지입니다.

  1. 트윗 작성 (Post Tweet): 사용자가 글을 올린다. (평균 4.6k/sec, 피크 12k/sec)
  2. 홈 타임라인 조회 (Home Timeline): 팔로우한 사람들의 글을 모아서 본다. (300k/sec)

핵심 문제: 쓰기(Write)보다 읽기(Read) 요청량이 압도적으로(약 60배 이상) 많습니다. 이 비대칭성 때문에 아키텍처 고민이 시작됩니다.

 

2. 접근 방식 1: 관계형 데이터베이스 모델 (Pull 모델)

초창기 트위터가 사용한 방식이자, 우리가 일반적으로 생각하는 방식입니다.

  • 동작 방식:
    1. 글을 쓰면 Tweets 테이블에 저장합니다. (단순 INSERT)
    2. 홈 타임라인을 볼 때, 내가 팔로우하는 사람 목록을 찾고 그들의 글을 시간순으로 정렬해서 가져옵니다.
  • SQL로 표현하자면
SELECT tweets.*, users.*
FROM tweets
JOIN users   ON tweets.sender_id = users.id
JOIN follows ON follows.followee_id = users.id
WHERE follows.follower_id = current_user
ORDER BY tweets.timestamp DESC;

 

  • 장점: 글 쓰기 부하가 매우 적습니다.
  • 치명적 단점: 읽기(홈 타임라인 조회)가 너무 느립니다. 팔로우하는 사람이 많아질수록 JOIN 비용이 기하급수적으로 늘어납니다. 초당 30만 건의 읽기 요청을 이 복잡한 쿼리로 처리하는 건 DB 입장에서 재앙입니다.

3. 접근 방식 2: 팬아웃 모델 (Push 모델)

트위터는 읽기 속도를 높이기 위해 아키텍처를 뒤집습니다. 읽을 때 고생하지 말고, 쓸 때 고생하자!는 전략입니다.

  • 동작 방식 (쓰기 시점의 작업):
    1. 사용자가 트윗을 씁니다.
    2. 시스템은 그 사용자를 팔로우하는 모든 사람을 찾습니다.
    3. 각 팔로워의 '홈 타임라인 캐시(Redis 같은 In-memory DB)'에 해당 트윗 ID를 찔러 넣어줍니다(Push).
  • 읽기 시점:
    • 사용자는 그냥 자기 캐시(우편함)를 열어보기만 하면 됩니다. 복잡한 DB Join 없이 O(1)의 속도로 타임라인이 완성됩니다.
  • 장점: 읽기 속도가 극단적으로 빠릅니다.
  • 치명적 단점: 쓰기 부하가 폭발합니다(Fan-out).
    • 팔로워가 100명인 사람이 글을 쓰면 100번의 쓰기 작업이 일어납니다.
    • 만약 팔로워가 3,000만 명인 저스틴 비버(Justin Bieber)가 글을 쓰면? 단 한 번의 트윗으로 3,000만 건의 쓰기 요청이 찰나의 순간에 발생합니다. 이를 "핫스팟(Hot Spot)" 문제라고 합니다.

4. 트위터의 최종 해결책: 하이브리드(Hybrid)

"극단적인 두 가지 방식 중 하나만 고집하지 말라."

트위터는 두 방식을 섞었습니다.

  1. 일반 사용자 (팔로워 수가 적음): 접근 방식 2(Push/Fan-out)를 사용합니다. 쓰기 부하가 크지 않고 읽기는 빠르기 때문입니다.
  2. 유명인 (Celebrity, 팔로워가 매우 많음): 접근 방식 1(Pull)을 사용합니다. 저스틴 비버의 트윗은 팔로워들의 타임라인 캐시에 넣지 않습니다. 대신 팔로워가 타임라인을 조회할 때, "일반 사용자들의 캐시 데이터" + "유명인의 별도 트윗 데이터"를 그 시점에 합쳐서(Merge) 보여줍니다.

이것이 바로 시스템의 부하 특성(Load Parameter)에 맞춰 설계를 최적화한 사례입니다.

 

구분 일반 사용자 (그림의 모델) 유명인 (예외 처리)
방식 Push (Fan-out) Pull
저장 위치 팔로워들의 Redis 캐시에 직접 꽂아줌 그냥 DB 테이블에만 저장함
쓰기 부하 높음 (팔로워 수만큼 복사해야 함) 매우 낮음 (내 거 하나만 저장하면 됨)
읽기 부하 매우 낮음 (캐시만 읽으면 됨) 높음 (DB 조회 후 합치는 연산 필요)
비유 우유 배달원이 집 앞 주머니에 우유를 넣어둠 (꺼내 먹기만 하면 됨) 신문 가판대에 신문이 놓임 (내가 직접 가서 가져와야 함)

 

일반 유저는 Push(캐시 미리 생성), 유명인은 Pull(조회 시 DB 조회)로 처리한다는 것을 알았습니다.

다음은 spring 환경에서 redis를 활용한 Push(fan out), Pull 실습 편을 기록하여 가져오겠습니다!

+ Recent posts