Projetando o X.com (Twitter) em Escala: Um Guia Completo de System Design
Resumo: Um guia completo de system design para construir uma plataforma social como o Twitter, lidando com bilhões de tweets, timelines em tempo real e assimetria massiva de leitura/escrita.
Publicado: Fevereiro 2026
Tempo de leitura: 55 minutos
Palavras-chave: #SystemDesign #Twitter #XDotCom #SistemasDistribuidos #Timeline #FanOut #Escalabilidade #ArquiteturaDeSoftware #EntrevistaTecnica
São 3 da manhã de um domingo. Beyoncé acabou de lançar um álbum surpresa e tuitou sobre isso. Nos próximos 60 segundos, 300.000 pessoas retuitam o anúncio. Cada um desses retweets precisa aparecer nas timelines de todos os seguidores -- algumas contas têm 50 milhões de seguidores. Isso são potencialmente 15 trilhões de inserções em timelines disparadas por um único tweet.
Bem-vindo ao desafio mais fascinante de sistemas distribuídos nas redes sociais: projetar o Twitter em escala.
Isso não é mais um overview superficial que te diz "use um load balancer." Isso é uma exploração aprofundada e testada em batalha de cada decisão arquitetural que torna uma plataforma como o X.com possível. Vamos cobrir os trade-offs exatos que o time de engenharia do Twitter enfrentou, as soluções que escolheram e o porquê de cada uma.
Se você está se preparando para uma entrevista de system design em uma grande empresa de tecnologia, arquitetando sua própria plataforma social, ou simplesmente curioso sobre como 500 milhões de tweets por dia chegam a bilhões de timelines em milissegundos -- este guia tem tudo que você precisa.
Vamos construir o Twitter do zero.
Sumário
- Análise de Requisitos
- Cálculos de Envelope
- Arquitetura de Alto Nível
- Design da API
- Modelagem de Dados
- Geração da Timeline: O Problema Central
- Pipeline de Ingestão de Tweets
- Busca e Trending Topics
- Sistema de Notificações
- Mensagens Diretas
- Armazenamento de Mídia e CDN
- Estratégia de Cache
- Arquitetura de Banco de Dados e Sharding
- Confiabilidade e Tolerância a Falhas
- Monitoramento e Observabilidade
- Dicas e Estratégia para Entrevistas
- Anti-Patterns a Evitar
- Conclusão
- Referências
- Referência Rápida
Análise de Requisitos
Antes de escrever uma única linha de código ou desenhar qualquer diagrama de arquitetura, precisamos entender profundamente o que estamos construindo. Em uma entrevista de system design, gastar de 5 a 8 minutos em requisitos não é tempo desperdiçado -- é a base que impede você de construir o sistema errado.
Requisitos Funcionais
Funcionalidades Essenciais (Must Have):
- Postar tweets -- Usuários podem criar posts curtos de texto (até 280 caracteres para usuários gratuitos, 25.000 para premium)
- Timeline inicial -- Usuários veem um feed cronológico ou classificado por algoritmo de tweets das pessoas que seguem
- Seguir/Deixar de seguir -- Usuários podem seguir outros usuários para ver seus tweets
- Retweet e Quote Tweet -- Usuários podem amplificar conteúdo para seus seguidores
- Curtir tweets -- Usuários podem expressar apreciação por conteúdo
- Responder a tweets -- Conversas encadeadas abaixo dos tweets
- Busca -- Busca textual completa em todos os tweets públicos
- Trending topics -- Identificação em tempo real de hashtags e tópicos em alta
- Notificações -- Alertas para menções, curtidas, retweets, seguidores e respostas
- Mensagens Diretas -- Mensagens privadas individuais e em grupo
Funcionalidades Estendidas (Nice to Have):
- Anexos de mídia (imagens, vídeos, GIFs)
- Enquetes
- Spaces (salas de áudio ao vivo)
- Listas e favoritos
- Analíticos para criadores de conteúdo
- Anúncios e conteúdo promovido
- Moderação de conteúdo e denúncia
Requisitos Não-Funcionais
| Requisito | Meta | Justificativa |
|---|---|---|
| Disponibilidade | 99,99% (52 min de downtime/ano) | Plataforma social; usuários esperam disponibilidade constante |
| Latência da Timeline | < 200ms p99 | Usuários rolam rapidamente; deve parecer instantâneo |
| Latência de Postagem | < 500ms p99 | Usuários esperam publicação quase instantânea |
| Modelo de Consistência | Consistência eventual (timeline), Forte (follows, DMs) | Timeline pode ter leve atraso; follows devem ser precisos |
| Durabilidade | Nenhum tweet perdido após confirmação | Cada tweet é um registro permanente |
| Proporção Leitura:Escrita | 1000:1 | Amplificação massiva de leitura devido às timelines |
| Tolerância a Partições | Obrigatória | Sistema global deve tolerar partições de rede |
Estimativas de Escala (Baseado em Dados Reais do Twitter)
- Usuários Ativos Diários (DAU): 250 milhões
- Usuários Ativos Mensais (MAU): 550 milhões
- Tweets por dia: 500 milhões (~6.000 tweets/segundo em média, 12.000/seg no pico)
- Leituras de timeline por dia: 250 bilhões (cada usuário lê a timeline ~1.000 vezes/dia em média)
- Média de seguidores por usuário: 200 (mas a distribuição é extremamente enviesada -- lei de potência)
- Contas de celebridades: ~100.000 contas com mais de 1 milhão de seguidores
- Tamanho médio de um tweet: 300 bytes (texto + metadados)
- Anexos de mídia: 30% dos tweets contêm imagens/vídeo
Cálculos de Envelope
Esses cálculos são críticos em entrevistas. Eles demonstram maturidade de engenharia e ajudam a direcionar decisões arquiteturais.
Armazenamento
Tweets por dia: 500M
Tamanho médio do tweet (texto + metadados): ~300 bytes
Armazenamento diário de tweets: 500M x 300B = 150 GB/dia
Armazenamento anual de tweets: 150 GB x 365 = ~55 TB/ano
Armazenamento em 5 anos: ~275 TB (apenas texto)
Mídia (imagens/vídeo):
30% dos tweets têm mídia
Tamanho médio de mídia: 500 KB (imagens), 5 MB (vídeo)
Assumindo 80% imagens, 20% vídeo:
Mídia diária: 500M x 0.3 x (0.8 x 500KB + 0.2 x 5MB)
= 150M x (400KB + 1MB)
= 150M x 1.4MB = 210 TB/dia
Mídia anual: 210 TB x 365 = ~76 PB/ano
Largura de Banda
Largura de banda de leitura (timeline):
250B leituras de timeline/dia x 10 tweets por página x 300 bytes
= 750 TB/dia = ~8,7 GB/seg
Largura de banda de escrita (novos tweets):
6.000 tweets/seg x 300 bytes = 1,8 MB/seg (trivial)
Largura de banda de mídia é dominante:
Assumindo que 50% das visualizações de timeline incluem carregamento de mídia
= ~4 TB/seg no pico (servido primariamente via CDN)
QPS (Consultas Por Segundo)
Criação de tweets: 6.000/seg (média), 12.000/seg (pico)
Leituras de timeline: 250B/86400 ~ 3M/seg (média), 6M/seg (pico)
Consultas de busca: ~100K/seg
Curtida/Retweet: ~50K/seg
Seguir/Deixar de seguir: ~5K/seg
Insight Principal: A proporção leitura-para-escrita é de aproximadamente 500:1 para operações de timeline. Essa assimetria extrema é o fator mais importante que direciona toda a nossa arquitetura.
Arquitetura de Alto Nível
graph TB
subgraph "Client Layer"
WEB[Web Client<br/>React/Next.js]
IOS[iOS App<br/>Swift]
AND[Android App<br/>Kotlin]
end
subgraph "Edge Layer"
CDN[CDN<br/>CloudFront/Akamai]
LB[Load Balancer<br/>L7 - HAProxy/Nginx]
end
subgraph "API Layer"
GW[API Gateway<br/>Rate Limiting, Auth, Routing]
GQL[GraphQL Federation<br/>Gateway]
end
subgraph "Core Services"
TS[Tweet Service]
TLS[Timeline Service]
US[User Service]
FS[Follow Service/Social Graph]
SS[Search Service]
NS[Notification Service]
DMS[DM Service]
MS[Media Service]
TRS[Trending Service]
end
subgraph "Async Processing"
MQ[Message Queue<br/>Apache Kafka]
FO[Fan-Out Service]
IDX[Indexing Service]
AN[Analytics Pipeline]
end
subgraph "Data Layer"
TC[(Tweet Store<br/>Manhattan/Cassandra)]
UC[(User Store<br/>PostgreSQL)]
GC[(Social Graph<br/>FlockDB/Neo4j)]
SC[(Search Index<br/>Elasticsearch)]
CACHE[(Cache Layer<br/>Redis Cluster)]
TLC[(Timeline Cache<br/>Redis)]
BLOB[(Media Store<br/>S3/HDFS)]
end
WEB & IOS & AND --> CDN
CDN --> LB
LB --> GW
GW --> GQL
GQL --> TS & TLS & US & FS & SS & NS & DMS & MS & TRS
TS --> MQ
MQ --> FO & IDX & AN
FO --> TLC
TS --> TC
US --> UC
FS --> GC
SS --> SC
TLS --> TLC & CACHE
MS --> BLOB
IDX --> SC
style GW fill:#e1f5fe
style FO fill:#fff3e0
style TLC fill:#fce4ec
style MQ fill:#f3e5f5
Decisões Arquiteturais
Por que Microsserviços? O Twitter começou como um monolito em Ruby on Rails. Em 2012, eles migraram para uma arquitetura de microsserviços porque:
- Escalabilidade independente -- Leituras de timeline escalam de forma diferente das escritas de tweets
- Autonomia dos times -- Mais de 100 times de engenharia podem fazer deploy de forma independente
- Diversidade tecnológica -- Busca usa Java, timeline usa Scala, alguns serviços usam Go
- Isolamento de falhas -- Um bug na busca não deveria derrubar a postagem de tweets
Por que GraphQL Federation? Em vez de cada cliente conversar com dezenas de serviços, uma camada de federação GraphQL oferece:
- Endpoint único para clientes
- Agregação de dados entre serviços
- Busca de dados dirigida pelo cliente (mobile recebe menos dados que web)
- Governança de schema entre times
Design da API
Endpoints Core REST/GraphQL
// ===== Tweet Service API =====
interface CreateTweetRequest {
text: string; // max 280 chars (free) ou 25,000 (premium)
media_ids?: string[]; // referências de mídia pré-carregadas
reply_to?: string; // tweet ID se for uma resposta
quote_tweet_id?: string; // tweet ID se for um quote tweet
poll?: PollOptions; // enquete opcional
conversation_settings?: 'everyone' | 'following' | 'mentioned';
}
interface Tweet {
id: string; // Snowflake ID
user_id: string;
text: string;
created_at: string; // ISO 8601
media: MediaAttachment[];
metrics: {
retweet_count: number;
like_count: number;
reply_count: number;
view_count: number;
};
reply_to?: string;
conversation_id: string;
language: string;
source: string; // "Twitter for iPhone", etc.
}
// POST /api/v2/tweets
// GET /api/v2/tweets/:id
// DELETE /api/v2/tweets/:id
// ===== Timeline Service API =====
interface TimelineRequest {
cursor?: string; // cursor de paginação (string opaca)
count?: number; // padrão 20, máximo 200
algorithm?: 'reverse_chronological' | 'ranked';
}
interface TimelineResponse {
tweets: Tweet[];
cursor_top: string; // para tweets mais recentes (pull to refresh)
cursor_bottom: string; // para tweets mais antigos (scroll infinito)
}
// GET /api/v2/timeline/home?cursor=xxx&count=20
// GET /api/v2/timeline/user/:userId?cursor=xxx
// ===== Social Graph API =====
interface FollowRequest {
target_user_id: string;
}
// POST /api/v2/users/:id/follow
// DELETE /api/v2/users/:id/follow
// GET /api/v2/users/:id/followers?cursor=xxx
// GET /api/v2/users/:id/following?cursor=xxx
// ===== Search API =====
interface SearchRequest {
query: string;
type: 'tweets' | 'users' | 'hashtags';
since?: string; // filtro de data
until?: string;
from?: string; // usuário específico
lang?: string;
cursor?: string;
count?: number;
}
// GET /api/v2/search?q=hello&type=tweets&cursor=xxx
// ===== Engagement API =====
// POST /api/v2/tweets/:id/like
// DELETE /api/v2/tweets/:id/like
// POST /api/v2/tweets/:id/retweet
// DELETE /api/v2/tweets/:id/retweet
Estratégia de Rate Limiting
interface RateLimitConfig {
// Limites por usuário (janela deslizante)
tweet_create: { requests: 300, window: '3h' };
timeline_read: { requests: 1500, window: '15m' };
search: { requests: 450, window: '15m' };
follow: { requests: 400, window: '24h' };
like: { requests: 1000, window: '24h' };
dm_send: { requests: 500, window: '24h' };
// Limites por aplicação (para consumidores da API)
app_tweet_read: { requests: 300000, window: '15m' };
app_tweet_create: { requests: 200, window: '15m' };
}
WebSocket para Atualizações em Tempo Real
// Conexão WebSocket para funcionalidades em tempo real
// wss://stream.x.com/v2/stream
interface StreamEvent {
type: 'new_tweet' | 'like' | 'retweet' | 'reply' |
'follow' | 'dm' | 'notification' | 'typing';
data: Record<string, unknown>;
timestamp: string;
}
// O cliente se inscreve em streams específicos:
// - atualizações da timeline do usuário
// - stream de notificações
// - atualizações de conversas de DM
// - indicadores de digitação
Modelagem de Dados
Diagrama Entidade-Relacionamento
erDiagram
USER {
bigint id PK "Snowflake ID"
varchar username "unique, indexed"
varchar display_name
text bio
varchar profile_image_url
varchar banner_image_url
boolean verified
boolean is_premium
timestamp created_at
int followers_count
int following_count
int tweet_count
}
TWEET {
bigint id PK "Snowflake ID"
bigint user_id FK
text content
bigint reply_to_tweet_id FK
bigint conversation_id
bigint quote_tweet_id FK
varchar language
varchar source
jsonb media_keys
timestamp created_at
int retweet_count
int like_count
int reply_count
int view_count
}
FOLLOW {
bigint follower_id FK
bigint following_id FK
timestamp created_at
}
LIKE {
bigint user_id FK
bigint tweet_id FK
timestamp created_at
}
RETWEET {
bigint user_id FK
bigint tweet_id FK
timestamp created_at
}
MEDIA {
bigint id PK
bigint tweet_id FK
varchar type "image|video|gif"
varchar url
varchar thumbnail_url
int width
int height
int duration_ms
varchar alt_text
}
NOTIFICATION {
bigint id PK
bigint user_id FK
varchar type "like|retweet|reply|follow|mention"
bigint actor_id FK
bigint tweet_id FK
boolean read
timestamp created_at
}
USER ||--o{ TWEET : "posts"
USER ||--o{ FOLLOW : "follows"
USER ||--o{ LIKE : "likes"
USER ||--o{ RETWEET : "retweets"
TWEET ||--o{ MEDIA : "contains"
TWEET ||--o{ LIKE : "receives"
TWEET ||--o{ RETWEET : "receives"
USER ||--o{ NOTIFICATION : "receives"
Schema SQL (Tabelas Principais)
-- Tabela de usuários (PostgreSQL, sharded por user_id)
CREATE TABLE users (
id BIGINT PRIMARY KEY, -- Snowflake ID
username VARCHAR(15) UNIQUE NOT NULL,
display_name VARCHAR(50),
bio TEXT,
profile_image VARCHAR(255),
banner_image VARCHAR(255),
verified BOOLEAN DEFAULT FALSE,
is_premium BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
followers_count INT DEFAULT 0,
following_count INT DEFAULT 0,
tweet_count INT DEFAULT 0,
location VARCHAR(100),
website VARCHAR(200)
);
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_created_at ON users(created_at);
-- Tabela de tweets (Cassandra / Manhattan, particionada por user_id)
CREATE TABLE tweets (
id BIGINT PRIMARY KEY, -- Snowflake ID (contém timestamp)
user_id BIGINT NOT NULL,
content TEXT,
reply_to_tweet_id BIGINT,
conversation_id BIGINT,
quote_tweet_id BIGINT,
language VARCHAR(5),
source VARCHAR(50),
media_keys TEXT[], -- array de IDs de mídia
created_at TIMESTAMPTZ NOT NULL,
retweet_count INT DEFAULT 0,
like_count INT DEFAULT 0,
reply_count INT DEFAULT 0,
view_count BIGINT DEFAULT 0,
is_sensitive BOOLEAN DEFAULT FALSE
);
CREATE INDEX idx_tweets_user_id ON tweets(user_id, created_at DESC);
CREATE INDEX idx_tweets_conversation ON tweets(conversation_id, created_at);
-- Relacionamentos de follow (Grafo Social - FlockDB / Cassandra)
CREATE TABLE follows (
follower_id BIGINT NOT NULL,
following_id BIGINT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (follower_id, following_id)
);
-- Índice reverso para "quem me segue"
CREATE INDEX idx_follows_following ON follows(following_id, follower_id);
-- Curtidas (Cassandra, alto throughput de escrita)
CREATE TABLE likes (
user_id BIGINT NOT NULL,
tweet_id BIGINT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (user_id, tweet_id)
);
CREATE INDEX idx_likes_tweet ON likes(tweet_id, created_at DESC);
Geração de Snowflake IDs
O Twitter inventou o sistema de Snowflake IDs, que agora é usado em toda a indústria. Entender isso é essencial.
/**
* Twitter Snowflake ID Generator
*
* Estrutura do ID de 64 bits:
* [1 bit não usado][41 bits timestamp][5 bits datacenter][5 bits worker][12 bits sequência]
*
* - Timestamp: milissegundos desde uma época customizada (Twitter: 4 Nov, 2010)
* - Suporta ~69 anos de timestamps
* - 4096 IDs únicos por milissegundo por worker
* - Ordenável por tempo: IDs mais novos são sempre maiores
*/
class SnowflakeGenerator {
private static EPOCH = 1288834974657n; // Época do Twitter: 4 Nov, 2010
private static DATACENTER_BITS = 5n;
private static WORKER_BITS = 5n;
private static SEQUENCE_BITS = 12n;
private static MAX_SEQUENCE = (1n << this.SEQUENCE_BITS) - 1n; // 4095
private static WORKER_SHIFT = this.SEQUENCE_BITS;
private static DATACENTER_SHIFT = this.SEQUENCE_BITS + this.WORKER_BITS;
private static TIMESTAMP_SHIFT = this.SEQUENCE_BITS + this.WORKER_BITS + this.DATACENTER_BITS;
private sequence = 0n;
private lastTimestamp = -1n;
constructor(
private datacenterId: bigint,
private workerId: bigint
) {}
generate(): bigint {
let timestamp = BigInt(Date.now());
if (timestamp === this.lastTimestamp) {
this.sequence = (this.sequence + 1n) & SnowflakeGenerator.MAX_SEQUENCE;
if (this.sequence === 0n) {
// Espera pelo próximo milissegundo
while (timestamp <= this.lastTimestamp) {
timestamp = BigInt(Date.now());
}
}
} else {
this.sequence = 0n;
}
this.lastTimestamp = timestamp;
return (
((timestamp - SnowflakeGenerator.EPOCH) << SnowflakeGenerator.TIMESTAMP_SHIFT) |
(this.datacenterId << SnowflakeGenerator.DATACENTER_SHIFT) |
(this.workerId << SnowflakeGenerator.WORKER_SHIFT) |
this.sequence
);
}
// Extrai timestamp de um Snowflake ID
static extractTimestamp(id: bigint): Date {
const timestamp = (id >> this.TIMESTAMP_SHIFT) + this.EPOCH;
return new Date(Number(timestamp));
}
}