Проектирование TwitterX
1. Введение
Twitter (ныне X) — одна из самых технически интересных социальных платформ в мире. Это не просто сервис для коротких сообщений: это система реального времени, которая должна мгновенно доставлять контент сотням миллионов пользователей, обрабатывать вирусные события с непредсказуемыми всплесками нагрузки и поддерживать сложный социальный граф.
Почему Twitter интересен с точки зрения System Design:
- Асимметричный социальный граф: пользователь может иметь 100 миллионов подписчиков (celebrity problem)
- Реальное время: твиты должны появляться в лентах за секунды
- Сложный trade-off между write amplification и read amplification при генерации фида
- Высокая нагрузка на запись во время мировых событий (финалы Чемпионата мира, смерть знаменитостей)
- Trending topics требует стриминговой агрегации в реальном времени
Компании с похожими системами: Weibo (Китай), Mastodon, Bluesky, Threads (Meta).
2. Функциональные требования
Core Features (must-have)
- Публикация твитов (до 280 символов, с медиа)
- Подписка (follow) на других пользователей
- Home Timeline: лента из твитов тех, на кого подписан
- Ретвит и Quote Tweet
- Лайки и replies
- Поиск по хэштегам и ключевым словам
- Уведомления (mentions, likes, retweets, follows)
Extended Features (nice-to-have)
- Trending Topics
- Direct Messages
- Lists (кураторские подборки аккаунтов)
- Twitter Spaces (аудио)
- Bookmarks
- Analytics для авторов
3. Нефункциональные требования
| Параметр | Значение |
|---|---|
| Пользователи | 350M MAU, 150M DAU |
| Нагрузка на запись | ~6,000 tweets/sec, пики до 150,000/sec |
| Нагрузка на чтение | ~300,000 timeline requests/sec |
| Latency Timeline | < 200ms p99 |
| Latency поиска | < 500ms p99 |
| Availability | 99.99% (< 52 минуты простоя в год) |
| Consistency | Eventual consistency для фида приемлема |
| Durability | 99.999% — твиты не должны теряться |
Паттерн нагрузки: read-heavy система. На каждую запись приходится ~50+ чтений.
4. Back-of-the-Envelope Estimation
Пользователи и активность
DAU = 150M
Среднее количество подписок = 200
Среднее количество подписчиков = 200
Celebrity (>1M подписчиков) = ~10,000 аккаунтов
Твитов в день: 150M DAU × 0.1 твита = 15M tweets/day
Tweets/sec (avg): 15M / 86,400 ≈ 175 tweets/sec
Tweets/sec (peak): ×50 = ~8,750 tweets/sec
Хранилище
Текст твита: 280 символов × 2 байта = 560 байт
Метаданные (user_id, timestamp, likes, etc.): ~500 байт
Итого на твит: ~1 KB
15M tweets/day × 1 KB = 15 GB/day текста
За 5 лет: 15 GB × 365 × 5 ≈ 27 TB (только текст)
Медиа (30% твитов с изображениями):
15M × 0.3 × 1 MB (сжатое) = 4.5 TB/day
За 5 лет: ≈ 8 PB (с репликами × 3 = 24 PB)
Фид (Timeline)
Размер фида пользователя: 800 последних записей × user_id = ~6 KB
Активных кэшированных фидов: 150M
Суммарно: 150M × 6 KB ≈ 900 GB RAM
Bandwidth
Timeline reads: 300K req/sec × 50 KB (страница фида) = 15 GB/sec исходящий
Tweet writes: 8,750/sec × 1 KB = 8.75 MB/sec входящий
5. High-Level Design
Основные компоненты
┌──────────────────────────────────────────────────────┐
│ Клиенты │
│ (Web, iOS, Android, API) │
└───────────────────────────┬──────────────────────────┘
│
┌──────▼───────┐
│ API Gateway │
│ (Rate Limit) │
└──────┬───────┘
│
┌───────────────────┼────────────────────┐
│ │ │
┌──────▼──────┐ ┌────────▼───────┐ ┌───────▼──────┐
│ Tweet │ │ Timeline │ │ Search │
│ Service │ │ Service │ │ Service │
└──────┬──────┘ └────────┬───────┘ └───────┬──────┘
│ │ │
┌──────▼──────┐ ┌────────▼───────┐ ┌───────▼──────┐
│ Fan-out │ │ Redis │ │ Elasticsearch│
│ Service │ │ Cluster │ │ Cluster │
└──────┬──────┘ └────────────────┘ └──────────────┘
│
┌──────▼──────┐
│ Message │
│ Queue │
│ (Kafka) │
└──────┬──────┘
│
┌──────▼──────────────────────────────────────────┐
│ Tweet Store │
│ (MySQL/Cassandra — горячие + холодные данные)│
└─────────────────────────────────────────────────┘
Data Flow при публикации твита
- Пользователь отправляет POST /tweet → API Gateway
- Tweet Service валидирует, сохраняет в Tweet Store
- Tweet Service публикует событие в Kafka
- Fan-out Service читает из Kafka, обновляет Redis Timeline Cache для подписчиков
- Search Indexer читает из Kafka, индексирует в Elasticsearch
- Notification Service читает из Kafka, отправляет уведомления
6. API Design
Основные endpoints
Публикация твита
POST /v1/tweets
Authorization: Bearer {token}
Request:
{
"text": "Hello, world! #twitter",
"reply_to_id": null,
"quote_tweet_id": null,
"media_ids": ["media_123"]
}
Response: 201 Created
{
"id": "1234567890",
"text": "Hello, world! #twitter",
"author_id": "user_456",
"created_at": "2024-01-15T10:30:00Z",
"metrics": { "likes": 0, "retweets": 0, "replies": 0 }
}
Получение домашней ленты
GET /v1/timelines/home?count=20&cursor={cursor_token}
Authorization: Bearer {token}
Response: 200 OK
{
"data": [...tweets],
"meta": {
"next_cursor": "abc123",
"result_count": 20
}
}
Поиск
GET /v1/search/tweets?q=%23twitter&sort=recent&start_time=2024-01-15T00:00:00Z
Уведомления
GET /v1/notifications?types=mention,like,retweet&since_id=9876
Аутентификация
- OAuth 2.0 с Bearer tokens
- Rate limiting: 300 req/15min для чтения, 50 tweets/24h для записи
- API keys для сторонних разработчиков с отдельными лимитами
7. Data Model
Основные сущности
Tweet
CREATE TABLE tweets ( id BIGINT PRIMARY KEY, -- Snowflake ID author_id BIGINT NOT NULL, text VARCHAR(280) NOT NULL, reply_to_id BIGINT, -- NULL для оригинальных твитов quote_id BIGINT, -- NULL если не quote tweet media_ids JSONB, -- массив media_id hashtags VARCHAR(50)[], -- для индексирования created_at TIMESTAMP NOT NULL, deleted_at TIMESTAMP, -- soft delete FOREIGN KEY (author_id) REFERENCES users(id) ); -- Партиционирование по времени (monthly partitions) -- Индексы: CREATE INDEX idx_tweets_author ON tweets(author_id, created_at DESC); CREATE INDEX idx_tweets_reply ON tweets(reply_to_id) WHERE reply_to_id IS NOT NULL;
User & Social Graph
CREATE TABLE users ( id BIGINT PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, display_name VARCHAR(100), bio TEXT, follower_count INT DEFAULT 0, following_count INT DEFAULT 0, created_at TIMESTAMP NOT NULL, is_verified BOOLEAN DEFAULT FALSE ); -- Граф подписок — отдельная таблица -- Может быть перенесена в graph DB (Neo4j) при масштабировании CREATE TABLE follows ( follower_id BIGINT NOT NULL, followee_id BIGINT NOT NULL, created_at TIMESTAMP NOT NULL, PRIMARY KEY (follower_id, followee_id) ); CREATE INDEX idx_follows_followee ON follows(followee_id);
Timeline Cache (Redis)
Ключ: timeline:{user_id}
Тип: Sorted Set (ZSET)
Score: Unix timestamp
Member: tweet_id (BIGINT)
Пример:
ZADD timeline:user_456 1705312200 tweet_789
ZREVRANGE timeline:user_456 0 799 -- последние 800 твитов
Media
CREATE TABLE media ( id VARCHAR(32) PRIMARY KEY, -- UUID author_id BIGINT NOT NULL, type ENUM('photo', 'video', 'gif'), url_cdn VARCHAR(512), -- CDN URL width INT, height INT, size_bytes BIGINT, created_at TIMESTAMP NOT NULL );
Выбор баз данных
| Данные | СУБД | Обоснование |
|---|---|---|
| Твиты | MySQL/CockroachDB | Транзакции, strong consistency для записи |
| Старые твиты (>6 месяцев) | Apache Cassandra | Дешёвое холодное хранение, append-only |
| Социальный граф | MySQL + Redis cache | Граф небольшой, частые чтения |
| Timeline cache | Redis Cluster | Sub-millisecond чтение |
| Медиа файлы | S3 + CDN | Object storage для бинарных данных |
| Полнотекстовый поиск | Elasticsearch | Инвертированный индекс, near-realtime |
| Trending | Redis (INCRBY) + Kafka | Счётчики в памяти, стриминг |
8. Deep Dive в ключевые компоненты
8.1 Генерация фида: Push vs Pull vs Hybrid
Это центральная проблема Twitter. Рассмотрим все подходы.
Pull Model (Fan-out on Read)
При запросе фида сервис в реальном времени:
- Берёт список всех подписок пользователя
- Для каждого — читает последние N твитов
- Merges and sorts по времени
def get_timeline(user_id: int, count: int = 20) -> list[Tweet]: # 1. Получить список подписок followees = db.query( "SELECT followee_id FROM follows WHERE follower_id = %s", user_id ) # 2. Для каждого читать последние твиты — N+1 queries! tweets = [] for followee_id in followees: user_tweets = db.query( "SELECT * FROM tweets WHERE author_id = %s " "ORDER BY created_at DESC LIMIT %s", followee_id, count ) tweets.extend(user_tweets) # 3. Merge sort tweets.sort(key=lambda t: t.created_at, reverse=True) return tweets[:count]
Проблемы Pull модели:
- При 1000 подписках — 1000 запросов к БД на каждое открытие ленты
- 300K req/sec × 1000 queries = 300M queries/sec — невозможно
- Высокая read latency
Преимущества: простота, нет write amplification
Push Model (Fan-out on Write)
При каждой публикации твита сервис записывает tweet_id в Redis Timeline Cache всех подписчиков.
def publish_tweet(tweet: Tweet): # 1. Сохранить твит db.insert("tweets", tweet) # 2. Отправить в Kafka kafka.produce("tweets", key=str(tweet.author_id), value=tweet.to_json()) # Fan-out Consumer def fanout_worker(tweet: Tweet): followers = db.query( "SELECT follower_id FROM follows WHERE followee_id = %s", tweet.author_id ) # Batch pipeline для эффективности pipe = redis.pipeline() for follower_id in followers: key = f"timeline:{follower_id}" pipe.zadd(key, {tweet.id: tweet.created_at.timestamp()}) pipe.zremrangebyrank(key, 0, -801) # хранить только 800 последних pipe.execute()
Проблема Celebrity Problem (Fanout Problem):
У Katy Perry 150M подписчиков. Один твит → 150M записей в Redis.
- 150M × 8 байт = 1.2 GB данных за один твит
- Время выполнения: даже при 100K Redis ops/sec = 1500 секунд = 25 минут!
Преимущества Push: чтение фида = O(1), одна команда ZRANGE в Redis
Hybrid Model (используется в реальном Twitter)
Twitter использует гибридный подход:
- Обычные пользователи (< 1M подписчиков): push fanout
- Celebrities (> ~100K подписчиков): твиты не записываются в чужие кэши, хранятся отдельно
- При чтении фида: берём pre-computed cache + "добавляем" твиты знаменитостей в реальном времени через merge
def get_timeline(user_id: int) -> list[Tweet]: # 1. Получить pre-computed фид обычных пользователей regular_tweets = redis.zrevrange( f"timeline:{user_id}", 0, 799, withscores=True ) # 2. Получить celebrity follows данного пользователя celebrity_follows = redis.smembers(f"celebrity_follows:{user_id}") celebrity_tweets = [] for celebrity_id in celebrity_follows: # Читаем напрямую из БД/отдельного кэша tweets = get_recent_tweets(celebrity_id, limit=20) celebrity_tweets.extend(tweets) # 3. Merge all_tweets = merge_sorted([regular_tweets, celebrity_tweets]) return all_tweets[:20] def fanout_tweet(tweet: Tweet): author = get_user(tweet.author_id) if author.follower_count < 100_000: # Push fanout для обычных пользователей push_to_followers_timelines(tweet) else: # Celebrity: только кэшировать сам твит redis.zadd( f"celebrity_tweets:{tweet.author_id}", {tweet.id: tweet.created_at.timestamp()} ) # и уведомить активных подписчиков notify_active_followers(tweet)
Дополнительная оптимизация: Fanout только для активных пользователей (тех, кто заходил последние 3 дня). Для неактивных — on-demand pull при следующем входе.
8.2 Trending Topics
Задача: найти хэштеги/темы, которые сейчас растут по популярности быстрее обычного.
Архитектура
Tweets → Kafka → Trending Processor → Redis ZSET → Trending API
↓
Sliding Window Counters
(5-min, 1-hour, 24-hour)
Алгоритм обнаружения тренда
Trending — это не просто "топ хэштегов по количеству". Это аномальный рост относительно исторической нормы.
class TrendingProcessor: def __init__(self, redis_client, kafka_consumer): self.redis = redis_client self.consumer = kafka_consumer self.WINDOWS = [300, 3600, 86400] # 5min, 1h, 24h def process_tweet(self, tweet: dict): timestamp = tweet['created_at'] hashtags = extract_hashtags(tweet['text']) location = tweet.get('location', 'global') for hashtag in hashtags: for window in self.WINDOWS: # Sliding window с временными бакетами (1 минута каждый) bucket = timestamp // 60 # минутный бакет key = f"trending:{location}:{window}:{hashtag}" self.redis.incr(f"hashtag_count:{bucket}:{hashtag}") self.redis.expire(f"hashtag_count:{bucket}:{hashtag}", window + 120) def calculate_trend_score(self, hashtag: str, location: str = 'global') -> float: # Текущий объём (последние 5 минут) recent_count = self.get_window_count(hashtag, window=300) # Исторический baseline (среднее за предыдущие 24 часа в то же время) historical_avg = self.get_historical_average(hashtag, hour_of_day=current_hour()) if historical_avg < 10: return 0 # Слишком мало данных # Velocity score: насколько текущий объём превышает норму velocity = (recent_count - historical_avg) / historical_avg # Также учитываем абсолютный объём volume_score = math.log10(max(recent_count, 1)) return velocity * 0.7 + volume_score * 0.3 def get_top_trends(self, location: str, limit: int = 30) -> list: # Периодически пересчитываем топ (каждые 30 секунд) candidates = self.redis.zrevrange( f"trending_candidates:{location}", 0, 99 ) scores = { hashtag: self.calculate_trend_score(hashtag, location) for hashtag in candidates } # Обновляем Redis ZSET self.redis.zadd( f"trending:{location}", scores ) return self.redis.zrevrange(f"trending:{location}", 0, limit-1, withscores=True)
Персонализированные тренды
Twitter показывает разные тренды в зависимости от:
- Геолокации пользователя (страна → регион → город)
- Интересов (на основе подписок и активности)
Для локальных трендов используется иерархия: global → country → city
8.3 Search и Индексация
Near-Realtime Indexing
Твиты должны быть доступны для поиска через 5-15 секунд после публикации.
Tweet Service → Kafka topic: "tweets" → Search Indexer Service → Elasticsearch
Elasticsearch индекс для твитов:
{ "mappings": { "properties": { "id": { "type": "long" }, "text": { "type": "text", "analyzer": "twitter_analyzer" }, "author_id": { "type": "long" }, "hashtags": { "type": "keyword" }, "created_at": { "type": "date" }, "lang": { "type": "keyword" }, "location": { "type": "geo_point" }, "metrics": { "properties": { "likes": { "type": "integer" }, "retweets": { "type": "integer" } } } } }, "settings": { "number_of_shards": 20, "number_of_replicas": 2, "analysis": { "analyzer": { "twitter_analyzer": { "tokenizer": "standard", "filter": ["lowercase", "hashtag_filter", "mention_filter"] } } } } }
Поиск с ранжированием
def search_tweets( query: str, sort_by: str = "recent", # "recent" | "top" since: datetime = None, lang: str = None ) -> list[Tweet]: must_clauses = [ {"multi_match": { "query": query, "fields": ["text^2", "hashtags^3"], # хэштеги приоритетнее "operator": "and" }} ] if since: must_clauses.append({"range": {"created_at": {"gte": since.isoformat()}}}) if lang: must_clauses.append({"term": {"lang": lang}}) if sort_by == "recent": sort = [{"created_at": {"order": "desc"}}] score_function = None else: # "top" # Function score: комбинация relevance + engagement score_function = { "functions": [ {"field_value_factor": {"field": "metrics.likes", "factor": 0.01, "modifier": "log1p"}}, {"field_value_factor": {"field": "metrics.retweets", "factor": 0.02, "modifier": "log1p"}}, {"gauss": {"created_at": {"scale": "24h", "decay": 0.5}}} # свежесть ], "score_mode": "sum", "boost_mode": "multiply" } sort = ["_score"] es_query = { "query": {"bool": {"must": must_clauses}}, "sort": sort, "size": 20 } if score_function: es_query["query"] = {"function_score": { "query": es_query["query"], **score_function }} return es.search(index="tweets", body=es_query)
9. Масштабирование
Шардирование
Tweet Store (MySQL → CockroachDB)
- Ключ шардирования: tweet_id (Snowflake ID, содержит timestamp)
- Обеспечивает равномерное распределение
- Хронологические запросы по user_id требуют scatter-gather
- Альтернатива: user_id — все твиты пользователя на одном шарде, эффективен запрос "твиты автора X", но горячие аккаунты создают hot spots
Таблица follows
- Шардирование по
follower_id— эффективен запрос "на кого я подписан" - Для обратного запроса ("кто подписан на меня") — либо денормализация, либо отдельная таблица
Redis Timeline Cache
- Шардирование по
user_idс consistent hashing - 32+ нод Redis, каждая обслуживает ~5M пользователей
- При добавлении ноды — постепенная ребалансировка
Кэширование
┌─────────────────────────────────────────────────────────────┐
│ Caching Strategy │
│ │
│ L1: Application Cache (Guava/Caffeine) │
│ - User profiles: 10 min TTL │
│ - Popular tweets: 5 min TTL │
│ │
│ L2: Redis Cluster │
│ - Timeline cache: ZSET, 7 days TTL │
│ - Celebrity tweets: ZSET, 1 hour TTL │
│ - Social graph hot paths: SET, 1 hour TTL │
│ - Tweet data: HASH, 1 day TTL │
│ - Rate limit counters: STRING с TTL │
│ │
│ L3: CDN (Cloudflare/Fastly) │
│ - Медиа файлы: 1 год (immutable) │
│ - Public профили: 5 min TTL │
└─────────────────────────────────────────────────────────────┘
CDN
- Медиа загружается напрямую в S3 через pre-signed URL (минуя наш backend)
- CDN edge nodes кэшируют популярные изображения
- Множество форматов/размеров генерируются асинхронно после загрузки
Upload: Client → API Gateway → Media Service → S3 → SNS
↓
Image Processor (Lambda)
- Resize: 200px, 600px, 1200px
- Convert to WebP
- Store back to S3
↓
CDN Invalidation
10. Trade-offs и Альтернативы
Push vs Pull для фида
| Push (Fan-out on Write) | Pull (Fan-out on Read) | |
|---|---|---|
| Чтение фида | O(1) — Redis ZRANGE | O(N) — N запросов в БД |
| Запись твита | O(followers) | O(1) |
| Память | Высокое (N × followers) | Низкое |
| Celebrity Problem | Критичная проблема | Нет проблемы |
| Свежесть данных | Мгновенная | Всегда актуальная |
Вывод: Гибрид — единственный масштабируемый вариант для сети с неравномерным распределением подписчиков.
MySQL vs Cassandra для твитов
Twitter исторически использовал MySQL и Gizmoduck (Snowflake). В 2022 при масштабировании перешли к Реляционные данные остались в MySQL, холодные архивные твиты вынесли в Manhattan (собственный distributed KV-store). Cassandra хороша для write-heavy timeline storage.
Elasticsearch vs самописный поиск
Elasticsearch обеспечивает near-realtime поиск из коробки. Twitter в своё время использовал Earlybird — кастомный Lucene-based движок, оптимизированный для real-time твитов (хранение только горячих данных в памяти, сортировка по времени по умолчанию). Выбор зависит от ресурсов команды.
11. Отказоустойчивость
Single Points of Failure
| Компонент | Риск | Митигация |
|---|---|---|
| Kafka | Потеря событий | RF=3, min.insync.replicas=2, multi-AZ |
| Redis | Потеря Timeline Cache | Redis Sentinel + Replica, cold rebuild из БД |
| MySQL Primary | Запись встаёт | Semi-sync replication, автофейловер (Orchestrator) |
| Fan-out Service | Задержка доставки | Горизонтальное масштабирование, dead letter queue |
| API Gateway | Все запросы блокируются | Multi-AZ, health checks, автозамена |
Graceful Degradation
При перегрузке системы Twitter применяет несколько уровней деградации:
- Кэш промахи → чтение из БД (медленнее, но работает)
- Fan-out задержан → твит видно не сразу (eventual consistency)
- Trending недоступен → скрываем блок (не критично)
- Поиск недоступен → показываем ошибку (приемлемо)
- Timeline недоступен → показываем последний кэшированный (стейл данные)
Disaster Recovery
Production (US-East):
- MySQL: Multi-AZ в одном регионе
- Redis: Redis Cluster с репликами
- Kafka: 3 брокера в разных AZ
DR (US-West):
- MySQL: Async replica для read + DR
- S3: Cross-region replication (CRR)
- Kafka MirrorMaker: репликация топиков
RTO (Recovery Time Objective): < 30 минут
RPO (Recovery Point Objective): < 5 минут (лаг реплики)
Data Backup
- MySQL: Daily snapshots в S3 + binary log streaming (point-in-time recovery)
- Redis: RDB snapshots каждые 15 минут + AOF для критичных данных
- Медиа (S3): Versioning включён, Cross-region replication
12. Мониторинг и Alerting
Ключевые метрики (SLI)
По сервисам
Tweet Service: - tweet_publish_success_rate: % успешных публикаций - tweet_publish_latency_p99: < 500ms - tweet_publish_error_rate: < 0.01% Timeline Service: - timeline_fetch_latency_p50: < 50ms - timeline_fetch_latency_p99: < 200ms - timeline_cache_hit_rate: > 95% Fan-out Service: - fanout_lag_seconds: задержка между публикацией и доставкой - fanout_queue_depth: размер очереди Kafka - fanout_error_rate: % ошибок fan-out Search Service: - search_latency_p99: < 500ms - index_freshness_seconds: время между публикацией и индексацией
SLO
| Сервис | SLO |
|---|---|
| Timeline fetch | 99.9% запросов < 200ms |
| Tweet publish | 99.95% успешно за < 1s |
| Search | 99.5% запросов < 500ms |
| Media upload | 99.9% успешно |
| Availability | 99.99% |
Alerting правила (Prometheus/Grafana)
alerts: - name: TimelineLatencyHigh expr: histogram_quantile(0.99, timeline_fetch_duration_seconds) > 0.2 for: 5m severity: warning - name: TweetPublishErrorRateHigh expr: rate(tweet_publish_errors_total[5m]) / rate(tweet_publish_total[5m]) > 0.001 for: 2m severity: critical - name: FanoutLagHigh expr: kafka_consumer_lag{group="fanout-service"} > 100000 for: 10m severity: warning - name: RedisCacheHitRateLow expr: redis_cache_hit_rate < 0.90 for: 5m severity: warning - name: ElasticsearchIndexFreshnessLow expr: search_index_freshness_seconds > 30 for: 3m severity: warning
Дашборды
- Real-time dashboard: RPS, latency, error rates по всем сервисам
- Business metrics: Tweets/sec, new follows/sec, DAU
- Infrastructure: CPU/RAM/Disk по кластерам, Kafka lag, Redis hit rate
- Fan-out dashboard: Распределение по времени доставки, celebrity queue
13. Вопросы для самопроверки
-
Почему Twitter использует гибридную модель fan-out, а не чистый push или pull? Опишите threshold для переключения.
-
Что такое Snowflake ID и почему Twitter разработал его вместо UUID или auto-increment? Какие свойства он обеспечивает?
-
Как работает sliding window counter для подсчёта трендов? Почему нельзя просто посчитать за последний час?
-
Пользователь A подписан на 2000 аккаунтов, 50 из которых — celebrities. Опишите точный процесс получения его Home Timeline.
-
Timeline Cache в Redis хранит только 800 твитов для каждого пользователя. Что происходит, когда пользователь скроллит дальше 800 твитов назад?
-
Как вы обеспечите, что fan-out не дублирует твиты в Timeline Cache при повторной обработке из Kafka?
-
Elasticsearch индексирует ~175 твитов/сек в нормальном режиме, но во время суперкубка пиковая нагрузка в 50 раз выше. Как масштабировать Search Indexer?
-
Как реализовать "soft delete" для твитов (удаление твита должно немедленно исчезнуть из всех фидов)?
-
Twitter показывает количество лайков и ретвитов. При 150M DAU точные счётчики очень дороги. Как бы вы их реализовали?
-
Опишите, что произойдёт с системой, если главный узел Redis Timeline Cache выйдет из строя. Как восстановить данные?
14. Дополнительные ресурсы
Официальные материалы Twitter/X Engineering
- Twitter Engineering Blog — оригинальные статьи команды
- "Scaling Twitter: Making Twitter 10000 percent faster" — классический доклад
- "The Architecture Twitter Uses to Deal with 150M Active Users" — High Scalability
Ключевые Papers и Talks
- Snowflake (Twitter, 2010) — генерация уникальных ID в распределённых системах
- "Earlybird: Real-Time Search at Twitter" (ICDE 2012) — архитектура поиска в реальном времени
- "Storing Data at Twitter" — Manhattan distributed database
- "Flying Faster at Twitter" — оптимизация Timeline
Книги и статьи
- Designing Data-Intensive Applications (Martin Kleppmann) — главы о репликации и партиционировании
- System Design Interview Vol. 2 (Alex Xu) — глава по Twitter
- High Scalability Blog: highscalability.com/twitter — множество статей про Twitter
Смежные системы для изучения
- Apache Kafka — понять consumer groups, партиционирование, компакцию
- Redis Sorted Sets — основа Timeline Cache
- Elasticsearch — inverted index, relevance scoring, horizontal scaling
- Consistent Hashing — для шардирования Redis и Cassandra