스케일 워즈 #5 — 트위터: Fan-out 패턴과 140자 뒤의 아키텍처

발행: (2026년 5월 27일 AM 06:20 GMT+9)
8 분 소요
원문: Dev.to

Source: Dev.to

Mehmet TURAÇ

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 000Fan‑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분으로 단축한 방법. 🎵

0 조회
Back to Blog

관련 글

더 보기 »