스케일 워즈 #5 — 트위터: Fan-out 패턴과 140자 뒤의 아키텍처
Source: Dev.to
The Problem: Lady Gaga and 50 Million Followers
사용자가 트윗을 하면, 그 트윗은 그 사용자의 모든 팔로워 타임라인에 나타나야 합니다.
- 평균 사용자는 약 200명의 팔로워를 가지고 있습니다.
- Lady Gaga는 5천만 명의 팔로워를 가지고 있습니다.
- Lady Gaga가 트윗을 하면, 5천만 개의 타임라인을 업데이트해야 합니다.
Twitter가 각 팔로워마다 별도의 데이터베이스 행을 만든다면:
-- Naive approach: One row per follower
INSERT INTO timeline (user_id, tweet_id, author_id, created_at)
SELECT follower_id, 12345, 'ladygaga', NOW()
FROM followers
WHERE followee_id = 'ladygaga';
-- 50 million INSERTs — disaster!
이 접근 방식은 불가능합니다. 5천만 건의 INSERT는 몇 분이 걸리고 데이터베이스를 잠그게 됩니다.
아키텍처 결정: Fan‑out‑on‑Write vs. Fan‑out‑on‑Read
전략 1: Fan‑out‑on‑Write
사용자가 트윗을 하면, 해당 트윗이 그 순간 모든 팔로워의 타임라인에 기록됩니다.
USER A tweeted
│
▼
┌─────────────────┐
│ Timeline Service│
│ (at write time) │
└────────┬────────┘
│
┌───────┴───────┬───────┬───────┬───────┐
▼ ▼ ▼ ▼ ▼
Follower1 Follower2 Follower3 … FollowerN
timeline timeline timeline timeline
장점
- 읽기(타임라인 보기)가 매우 빠릅니다 — 사용자 타임라인을 가져오기만 하면 됩니다.
- 간단한 아키텍처.
단점
- 팔로워가 많은 사용자에게는 쓰기가 매우 느립니다.
- 저장소 폭증: 각 트윗이 N번 저장됩니다.
전략 2: Fan‑out‑on‑Read
사용자가 타임라인을 열면, 팔로우하는 사람들의 트윗이 그 순간 병합됩니다.
USER opened their timeline
│
▼
┌──────────────────┐
│ Timeline Service│
│ (at read time) │
└────────┬─────────┘
│
┌───────┴───────┬───────┬───────┐
▼ ▼ ▼ ▼
Author1 Author2 Author3 Author4
tweets tweets tweets tweets
│
└──> MERGE ──> Show to user
장점
- 쓰기가 매우 빠릅니다 — 트윗을 저장하기만 하면 됩니다.
- 저장 효율이 높습니다 — 각 트윗이 한 번만 저장됩니다.
단점
- 읽기가 매우 느립니다 — 타임라인 조회당 N개의 쿼리가 필요합니다.
- 병합 작업이 비용이 많이 듭니다.
트위터의 하이브리드 솔루션
Twitter 사용자를 두 가지 카테고리로 나눕니다:
| 카테고리 | 팔로워 수 | 전략 |
|---|---|---|
| 일반 사용자 | 10 000 | Fan‑out‑on‑Read |
# Twitter's hybrid approach (pseudo‑code)
def post_tweet(user, tweet_text):
tweet = create_tweet(user, tweet_text)
if user.follower_count < 10_000:
# Normal user: Fan‑out‑on‑Write
followers = get_followers(user.id)
for follower in followers:
redis.zadd(
f"timeline:{follower.id}",
tweet.timestamp,
tweet.id
)
else:
# Celebrity: Only store their own tweet
# Will be merged when followers open their timeline
redis.zadd(f"user_tweets:{user.id}", tweet.timestamp, tweet.id)
def get_timeline(user_id):
# 1. Get the user's pre‑computed timeline
timeline = redis.zrevrange(f"timeline:{user_id}", 0, 100)
# 2. Add tweets from followed celebrities
for celeb in get_followed_celebrities(user_id):
celeb_tweets = redis.zrevrange(
f"user_tweets:{celeb.id}", 0, 10
)
timeline.extend(celeb_tweets)
# 3. Sort by time and return the latest 100
return sorted(timeline, key=lambda t: t.timestamp, reverse=True)[:100]
Manhattan: Twitter’s Own Database
201 4년, Twitter는 MySQL에서 Manhattan으로 마이그레이션했습니다. Manhattan은 Twitter의 특수한 요구에 맞게 구축된 분산 키‑값 스토어입니다.
- Multi‑datacenter replication: 트윗이 전 세계 여러 데이터 센터에 복제됩니다.
- Low latency: 99번째 백분위수에서 읽기 지연 시간이 < 10 ms입니다.
- High throughput: 초당 수백만 개의 트윗을 처리합니다.
Snowflake: 트위터의 ID 생성 시스템
Tweet ID는 무작위가 아닙니다. 트위터는 Snowflake라는 ID 생성 시스템을 사용합니다:
┌────────────────────────────────────────────────────────────┐
│ 64‑bit Tweet ID (Snowflake) │
├────────────────────────────────────────────────────────────┤
│ Bit 63: Sign bit (always 0) │
│ Bits 22‑62: Timestamp (41 bits — ms since custom epoch)│
│ Bits 17‑21: Datacenter ID (5 bits → 32 datacenters) │
│ Bits 12‑16: Worker ID (5 bits → 32 workers per DC) │
│ Bits 0‑11: Sequence number (12 bits → 4096 per ms/worker)│
└────────────────────────────────────────────────────────────┘
왜 이런 종류의 ID를 사용할까요?
- 분산형: 각 워커가 자체적으로 ID를 생성하므로 별도의 조정이 필요 없습니다.
- 시간 순서 보장: 트윗이 자연스럽게 시간 순으로 정렬됩니다.
- 고유성: 각 워커가 서로 다른 ID 블록을 사용하므로 충돌이 불가능합니다.
- 컴팩트: UUID의 128비트에 비해 64비트만 사용합니다.
트레이드‑오프
✅ 이점
- 낮은 지연시간: 타임라인이 밀리초 안에 로드됩니다.
- 확장성: 수십억 개의 트윗과 사용자를 지원합니다.
- 비용 효율성: 유명인 사용자를 위해 저장소와 쓰기 부하가 최적화되었습니다.
❌ 비용
- 두 개의 팬‑아웃 전략과 하이브리드 로직을 유지함으로써 발생하는 아키텍처 복잡성.
- 사전 계산된 타임라인을 읽기 시 병합과 동기화하는 추가 운영 오버헤드.
- 맞춤형 인프라(Manhattan, Snowflake)와 전문 지식이 필요합니다.
복잡성: 두 가지 다른 전략을 관리하는 것은 어렵습니다
불일치: 유명인 트윗이 팔로워 타임라인에 몇 초 늦게 표시될 수 있습니다
임계값 관리: “10,000‑팔로워” 임계값을 결정하고 동적으로 조정하는 것이 어렵습니다
주요 내용
- 트위터는 “모두에게 동일하게 맞는 하나의 해법”은 없다는 것을 보여주었습니다 — 단일 전략 대신 하이브리드 접근 방식을 사용했습니다.
- 피드, 타임라인, 알림 시스템에서는 읽기‑쓰기 트레이드오프가 항상 발생합니다; 어느 작업이 더 빈번한지 분석하고 그에 맞는 전략을 선택하세요.
- 중앙 집중식 ID 생성(자동 증가)은 분산 시스템에서 병목이 됩니다; Snowflake, ULID, UUID v7 등을 대안으로 검토하세요.
- 타임라인처럼 자주 읽히는 데이터는 Redis와 같은 인‑메모리 캐시가 필수입니다.
다음 — Chapter 6: Spotify의 Squad 모델과 Golden Paths가 서비스 생성 시간을 2주에서 5분으로 단축한 방법. 🎵
