1

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

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):

  1. Postar tweets -- Usuários podem criar posts curtos de texto (até 280 caracteres para usuários gratuitos, 25.000 para premium)
  2. Timeline inicial -- Usuários veem um feed cronológico ou classificado por algoritmo de tweets das pessoas que seguem
  3. Seguir/Deixar de seguir -- Usuários podem seguir outros usuários para ver seus tweets
  4. Retweet e Quote Tweet -- Usuários podem amplificar conteúdo para seus seguidores
  5. Curtir tweets -- Usuários podem expressar apreciação por conteúdo
  6. Responder a tweets -- Conversas encadeadas abaixo dos tweets
  7. Busca -- Busca textual completa em todos os tweets públicos
  8. Trending topics -- Identificação em tempo real de hashtags e tópicos em alta
  9. Notificações -- Alertas para menções, curtidas, retweets, seguidores e respostas
  10. 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

RequisitoMetaJustificativa
Disponibilidade99,99% (52 min de downtime/ano)Plataforma social; usuários esperam disponibilidade constante
Latência da Timeline< 200ms p99Usuários rolam rapidamente; deve parecer instantâneo
Latência de Postagem< 500ms p99Usuários esperam publicação quase instantânea
Modelo de ConsistênciaConsistência eventual (timeline), Forte (follows, DMs)Timeline pode ter leve atraso; follows devem ser precisos
DurabilidadeNenhum tweet perdido após confirmaçãoCada tweet é um registro permanente
Proporção Leitura:Escrita1000:1Amplificação massiva de leitura devido às timelines
Tolerância a PartiçõesObrigatóriaSistema 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:

  1. Escalabilidade independente -- Leituras de timeline escalam de forma diferente das escritas de tweets
  2. Autonomia dos times -- Mais de 100 times de engenharia podem fazer deploy de forma independente
  3. Diversidade tecnológica -- Busca usa Java, timeline usa Scala, alguns serviços usam Go
  4. 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));
  }
}
Carregando publicação patrocinada...