Pular para o conteúdo
fernando.moretes.com
BlogEstudosCursosE-booksOpen SourcePodcasts
Loading…
Fernando Azevedo

Arquiteto de TI Especialista

Arquitetura, AWS, IA em produção, sistemas financeiros e FinOps, escritos a partir do que foi operado, com os números.

  • LinkedIn
  • GitHub
  • E-mail

Conteúdo

  • Blog
  • Estudos de arquitetura
  • Cursos
  • E-books
  • Open Source
  • Podcasts
  • Assuntos
  • Trilhas

Ferramentas

  • Well-Architected self-check
  • Arquitetura deste site
  • Números públicos
  • Histórico de estudo

Sobre

  • Comunidade
  • IA Builder
  • Perfil
  • Currículo (CV)
  • Trabalhe comigo
  • Media kit
  • RSS do blog
  • RSS dos estudos
  • Artigos narrados (podcast)
  • llms.txt
  • Termos, privacidade e uso de IA

(c) 2026 Fernando Francisco Azevedo

Estudos/Teardown
RSSMarkdownLLMs
TeardownDiscordDados

Discord: Como Armazenar Trilhões de Mensagens: Teardown da Migração Cassandra → ScyllaDB

6 de mar. de 2023 7 min Com apoio de IA000 visualizações00 reproduções
Ouvir estudo

cerca de 7 min · áudio gerado ao tocar

Quando você tem trilhões de mensagens e latência de cauda que quebra a experiência do usuário, trocar o banco de dados não é uma decisão de fim de semana. O Discord fez exatamente isso, e a história de como eles saíram do Cassandra para o ScyllaDB, reescreveram os data services em Rust e eliminaram pausas de GC que duravam segundos é um dos casos de engenharia de dados mais instrutivos da última década.

Fatos do Caso

Empresa
Discord Inc.
Domínio
Plataforma de comunicação em tempo real
Escala de dados
Trilhões de mensagens armazenadas; bilhões de inserções por dia (estimativa)
Stack anterior
Apache Cassandra (JVM), Python/Go data services
Stack atual
ScyllaDB (C++), data services em Rust
Publicação do post original
2023 (blog oficial do Discord)
Principal problema resolvido
Latência de cauda (p99/p999) e pausas de GC no Cassandra causando degradação perceptível ao usuário
Resultado reportado
Redução drástica de latência de cauda; clusters menores; operação mais previsível

O Problema: Cassandra Funcionava, Mas Não o Suficiente

O Discord adotou Apache Cassandra cedo, e a escolha fazia sentido: modelo de dados wide-column, escalabilidade horizontal sem ponto único de falha, e um ecossistema maduro. Para uma plataforma de mensagens onde cada servidor do Discord (guild) tem canais, e cada canal tem um histórico de mensagens que precisa ser lido de forma eficiente por timestamp, o modelo de partição do Cassandra é quase perfeito na teoria. Você particiona por (channel_id, bucket) e clusteriza por message_id (Snowflake, monotonicamente crescente), o que dá leituras de intervalo de tempo em uma única partição. Elegante.

O problema não era o modelo de dados. Era a JVM.

O Cassandra roda na JVM e, com heaps grandes, necessários para manter índices e caches em memória para trilhões de mensagens, o garbage collector se torna o inimigo. Pausas de GC de segundos não são hipotéticas nessa escala; são rotineiras. O Discord reportou latências de p99 e p999 completamente imprevisíveis, com picos que chegavam a segundos em operações que deveriam levar milissegundos. Em uma plataforma de comunicação em tempo real, isso é perceptível. Usuários veem o histórico de mensagens demorar para carregar. A experiência quebra.

Há também o problema de compactação. O Cassandra usa LSM trees (Log-Structured Merge-tree) internamente, e o processo de compactação, necessário para manter a performance de leitura ao longo do tempo, compete com operações de produção por I/O e CPU. Em clusters grandes e sob carga contínua, janelas de compactação se tornam eventos de risco operacional. A equipe do Discord descreveu nós quentes (hot nodes) que causavam pressão de cauda no cluster inteiro, porque o Cassandra não tem isolamento de recursos por operação, um nó lento contamina o percentil de latência do cluster.

A alternativa óbvia seria tunar o GC, usar ZGC ou Shenandoah, reduzir o heap, fragmentar os clusters. A equipe tentou essas abordagens. O problema é que elas compram tempo, não resolvem a causa raiz: você está rodando um banco de dados de missão crítica em cima de um runtime com coleta de lixo não-determinística. Em algum ponto, a engenharia de tuning vira dívida técnica acumulada, e a relação custo-benefício inverte.

Arquitetura Reconstruída: Caminho de Leitura/Escrita de Mensagens

Diagrama reconstruído com base no post oficial do Discord. Mostra o fluxo de dados desde o cliente até o armazenamento, incluindo a camada de data services em Rust e o ScyllaDB como storage primário.

👤 Clients
  • Discord Client · (Web / Mobile / Desktop)
🌐 Edge & Gateway
  • Gateway Service · (WebSocket)
  • API Service · (REST / Internal RPC)
⚙️ Data Services (Rust)
  • Message Data Service · (Rust)
  • Hot Message Cache · (in-process / Redis)
🗄️ Storage Layer
  • ScyllaDB Cluster · (C++, Shard-per-core)
  • Cassandra Cluster · (Legacy / Migration Source)
🔄 Migration Path
  • Data Migrator · (Dual-write / Backfill)

Como o Sistema Funciona: Modelo de Dados, ScyllaDB e a Camada Rust

O modelo de dados central permaneceu essencialmente o mesmo após a migração, e isso é um ponto importante. O Discord não precisou redesenhar o schema para mudar de Cassandra para ScyllaDB, porque o ScyllaDB é compatível com o protocolo CQL (Cassandra Query Language). A tabela de mensagens usa (channel_id, bucket) como chave de partição, onde bucket é derivado do timestamp da mensagem para evitar partições ilimitadas (um canal ativo por anos teria uma partição gigantesca sem bucketing). A chave de clusterização é o message_id, um Snowflake ID que codifica o timestamp, garantindo ordenação cronológica nativa e leituras de intervalo eficientes.

O que mudou foi a engine por baixo. O ScyllaDB é uma reimplementação do Cassandra em C++ com o framework Seastar, que usa um modelo shard-per-core: cada core da CPU tem seu próprio conjunto de dados e sua própria fila de I/O, sem compartilhamento de memória entre cores. Isso elimina a contenção de locks que é endêmica em sistemas multi-threaded com heap compartilhado. Não há GC. O gerenciamento de memória é determinístico. O resultado prático é que as latências de cauda, p99, p999, colapsam para valores muito menores e, mais importante, muito mais previsíveis.

A reescrita dos data services em Rust foi complementar e igualmente deliberada. Os serviços anteriores em Python/Go introduziam sua própria variabilidade de latência: o GC do Go, embora muito melhor que o da JVM, ainda introduz pausas; o Python tem o GIL. Rust oferece performance determinística sem runtime de GC, com segurança de memória em tempo de compilação. Para um serviço que está no caminho crítico de cada leitura e escrita de mensagem, a escolha faz sentido técnico claro. O custo é a curva de aprendizado e a velocidade de desenvolvimento menor, trade-off que o Discord avaliou como aceitável dado o perfil de carga.

A migração em si foi conduzida com dual-write e backfill. Durante a fase de transição, novas mensagens eram escritas tanto no Cassandra quanto no ScyllaDB. Um processo de migração em background lia dados históricos do Cassandra e os escrevia no ScyllaDB. Quando a paridade de dados era confirmada, o tráfego de leitura era gradualmente desviado para o ScyllaDB. Essa abordagem é conservadora e correta para dados de missão crítica, você nunca faz big-bang migration em armazenamento de mensagens de produção. O risco de perda de dados ou inconsistência é alto demais. O custo do dual-write (dobrar a carga de escrita temporariamente) é um preço razoável pela segurança operacional.

Matriz de Decisão: Opções de Armazenamento Avaliadas

Continuar com Cassandra (tuning agressivo)

Prós
  • Zero custo de migração
  • Equipe já conhece o sistema
  • Ecossistema maduro, tooling amplo
Contras
  • GC não-determinístico é causa raiz, não sintoma
  • Tuning vira dívida técnica acumulada
  • Hot nodes e compactação continuam sendo riscos operacionais

Rejeitado: trata sintoma, não causa

ScyllaDB (C++, shard-per-core)

Prós
  • Compatível com CQL, sem mudança de schema
  • Sem GC: latência de cauda determinística
  • Shard-per-core elimina contenção entre threads
  • Menor footprint de cluster para mesma carga (estimativa)
Contras
  • Migração de trilhões de registros é operação de alto risco
  • Ecossistema menor que Cassandra
  • Vendor menor, risco de suporte de longo prazo

Aceito: resolve a causa raiz com compatibilidade de protocolo

PostgreSQL / CockroachDB (SQL distribuído)

Prós
  • ACID completo, queries flexíveis
  • Ecossistema e tooling excelentes
Contras
  • Modelo de dados relacional não é natural para wide-column de mensagens
  • Escalabilidade horizontal mais complexa nessa escala
  • Reescrita completa do schema e access patterns

Rejeitado: custo de migração alto sem ganho claro no caso de uso

DynamoDB / Bigtable (cloud-native)

Prós
  • Operação gerenciada, sem overhead de cluster
  • Escalabilidade virtualmente ilimitada
Contras
  • Lock-in de cloud significativo
  • Custo em escala de trilhões de registros pode ser proibitivo
  • Menor controle sobre performance de cauda

Não avaliado publicamente, lock-in e custo são barreiras reais nessa escala

A Decisão pelo Rust: Determinismo Como Requisito de Sistema

A reescrita dos data services em Rust merece análise separada porque ela revela uma filosofia de engenharia importante: quando você tem um requisito de latência de cauda agressivo, cada camada do stack precisa ser avaliada como potencial fonte de jitter. O Discord não trocou apenas o banco de dados, eles auditaram o caminho crítico de ponta a ponta e identificaram que a camada de serviço também era uma fonte de variabilidade.

O argumento técnico para Rust nesse contexto é sólido. O modelo de ownership do Rust garante que não há coleta de lixo em runtime, alocações e desalocações são determinísticas e previsíveis. Para um serviço que processa milhões de operações de leitura e escrita por segundo, a ausência de pausas de GC, mesmo as sub-milissegundo do Go, se traduz em percentis de latência mais baixos e mais estáveis. Além disso, Rust tem performance próxima a C/C++ sem os riscos de segurança de memória que tornariam C++ impraticável para uma equipe de produto.

O custo real do Rust é a velocidade de desenvolvimento e o tamanho do pool de engenheiros. Rust tem uma curva de aprendizado significativa, o borrow checker é um obstáculo real para desenvolvedores vindos de linguagens com GC. Para uma empresa do tamanho do Discord, isso significa que a base de engenheiros que pode contribuir para esses serviços é menor, e o tempo de onboarding de novos engenheiros é maior. Esse é um trade-off que faz sentido para serviços de infraestrutura de baixo nível com requisitos de performance extremos, mas seria questionável para serviços de negócio de alto nível onde a velocidade de iteração é mais importante que a latência de cauda.

O que o Discord fez corretamente foi limitar o uso de Rust ao caminho crítico, os data services que ficam diretamente na frente do banco de dados. Eles não reescreveram tudo em Rust. Essa contenção é madura: você aplica a ferramenta mais cara (em termos de custo de desenvolvimento) onde o retorno é mais alto.

Leitura pelo AWS Well-Architected Framework

Segurança

O post não detalha controles de segurança, mas a arquitetura implica isolamento de rede entre camadas (data services não expostos diretamente), e o modelo shard-per-core do ScyllaDB reduz a superfície de ataque de processos compartilhados. Um ponto cego: não há menção de criptografia em trânsito entre data services e ScyllaDB, nem de controles de acesso granulares ao nível de tabela. Em sistemas financeiros ou regulados, isso seria um gap crítico.

Confiabilidade

Alta. O modelo de replicação do ScyllaDB (herdado do Cassandra) oferece tolerância a falhas de nós sem downtime. A migração via dual-write e backfill gradual é o padrão correto para zero downtime em dados críticos. O risco residual é a janela de dual-write: se houver divergência de dados entre Cassandra e ScyllaDB durante a migração, a reconciliação é complexa. Não há menção de estratégia de rollback explícita.

Eficiência de performance

Este é o pilar central do caso. A combinação ScyllaDB (sem GC, shard-per-core) + Rust (sem GC, zero-cost abstractions) elimina as duas principais fontes de jitter de latência identificadas. O modelo de dados wide-column com bucketing de timestamp é correto para o padrão de acesso (leituras de intervalo por canal). O uso de cache hot para mensagens recentes reduz a pressão sobre o ScyllaDB para o caso de acesso mais comum.

Otimização de custos

O Discord reporta clusters menores para a mesma carga após a migração, o ScyllaDB extrai mais performance por nó que o Cassandra, o que se traduz em menos hardware. O custo de migração (engenharia, dual-write, operação paralela de dois clusters) foi significativo, mas é um custo único. O custo recorrente de operação deve ser menor. A reescrita em Rust tem custo de engenharia alto, mas reduz custo de infraestrutura de forma permanente.

Sustentabilidade

Clusters menores para a mesma carga significa menor consumo de energia. A eficiência do ScyllaDB por nó e a ausência de GC (que desperdiça CPU em coleta) reduzem o footprint computacional. Não é o foco do post, mas é um benefício real.

FA
O Que Eu Faria Diferente
Arquiteto de TI Especialista

A decisão de migrar para ScyllaDB foi correta e bem executada. Mas há três áreas onde eu teria feito escolhas diferentes ou adicionado camadas que o post não menciona. 1. Estratégia de rollback explícita durante a migração. O post descreve dual-write e backfill, mas não menciona um plano de rollback formal. Em sistemas com trilhões de registros, a janela de dual-write pode durar semanas ou meses. Durante esse período, você precisa de um processo de reconciliação automatizado que detecte divergências entre Cassandra e ScyllaDB e uma decisão clara sobre qual fonte é autoritativa em cada fase. Eu implementaria um pipeline de validação contínua que amostrasse partições aleatórias e comparasse checksums, com alertas automáticos para divergências acima de um threshold. Sem isso, você está voando às cegas durante a migração. 2. Observabilidade de cauda como first-class citizen. O problema original era latência de cauda imprevisível. Depois da migração, você precisa de instrumentação que prove que o problema foi resolvido e que detecte regressões antes que afetem usuários. Isso significa histogramas de latência (não médias) expostos via OpenTelemetry, com alertas em p99 e p999 por operação e por shard do ScyllaDB. Os serviços Rust deveriam ter tracing distribuído integrado desde o primeiro deploy em produção, não como adição posterior. 3. Bucketing dinâmico baseado em atividade do canal. O schema atual usa bucketing fixo por timestamp para evitar partições gigantescas. Isso funciona, mas é um compromisso estático.

Veredicto

O caso do Discord é um exemplo de engenharia de dados madura: identificar a causa raiz correta (não-determinismo de GC na JVM), escolher um substituto com compatibilidade de protocolo para minimizar o risco de migração (ScyllaDB + CQL), executar a migração de forma conservadora (dual-write + backfill gradual), e estender o princípio de determinismo para a camada de serviço (Rust). Cada decisão tem lógica técnica clara e os trade-offs são explicitamente reconhecidos. O que torna este caso valioso como estudo não é a tecnologia específica: ScyllaDB pode não ser a escolha certa para outros contextos, mas o framework de raciocínio: quando a latência de cauda é um requisito de sistema, você precisa eliminar fontes de não-determinismo em cada camada do stack, e isso frequentemente exige trocar conveniência de desenvolvimento por previsibilidade de runtime. A combinação de ScyllaDB + Rust é uma aposta deliberada nessa direção. O risco residual mais relevante que não foi endereçado publicamente é a dependência de um vendor menor (ScyllaDB) para infraestrutura crítica. O Cassandra tem a Apache Foundation e um ecossistema enorme; o ScyllaDB é uma empresa privada.

Referências

Discord: How Discord Stores Trillions of Messages (Official Blog)
#discord#scylladb#cassandra#rust#data-platform#migration#wide-column#messaging
Fontes do caso
Discord: How Discord stores trillions of messages
Compartilhar:LinkedInWhatsAppXBaixar em Markdown
Escrito com apoio de IA a partir do caso público e da minha leitura como arquiteto.
Próximo passo
Trilha Arquiteto de IA, gratuitaFundamentos, LLMs, RAG, tools, MCP e arquitetura na AWS, em aulas curtas e interativas.Começar a trilhaAgentes de IA em produção na AWSComo eu desenho, oriento e audito agentes que vão ao ar, com os números e as fontes.Ver o IA Builder

Continue lendo

TeardownFigma: Sharding Horizontal de Postgres Sem Parar de CrescerEm 2022, o Figma enfrentou limites físicos do seu banco de dados Postgres monolítico e executou uma migração de sharding horizontal com particionamento por chave, roteamento dinâmico e migração incremental, tudo isso sem downtime e sem travar o crescimento do produto. Este teardown reconstrói a arquitetura, analisa as decisões técnicas e aponta o que eu faria diferente.Ler TeardownKafka em Escala: Tiered Storage, KRaft e Exactly-OnceUma análise aprofundada da arquitetura interna do Apache Kafka, modelo de log/segmentos, eliminação do ZooKeeper via KRaft, offload de dados para S3 com Tiered Storage, semântica exactly-once e o que o Amazon MSK abstrai (e o que ele não abstrai). Um teardown técnico para quem precisa operar Kafka de verdade.Ler TeardownNotion: O Modelo de Blocos e o Sharding do PostgresO Notion cresceu de um banco Postgres monolítico para uma arquitetura shardada horizontalmente, particionando dados por tenant para sustentar crescimento explosivo durante a pandemia. Este teardown reconstrói as decisões de modelagem, o processo de migração e os trade-offs reais de operar Postgres em escala.Ler
AnteriorAtlassian 2022: quando um script de manutenção apaga 400 clientes por duas semanasPróximoDatadog (2023): como um patch de segurança do systemd derrubou 5 regiões simultaneamente
Gostou deste estudo? Receba o próximo.

Post-mortems, ADRs e deep dives de arquitetura no seu email, do jeito que um arquiteto lê.

Sem spam · cancele quando quiser

Pergunte ao Fernando sobre isto

Uma resposta focada sobre este estudo, do meu assistente de IA, baseada no meu trabalho.

Participe da conversa

Entre para comentar

Confirme seu e-mail para participar, você também recebe a newsletter. Sem senha.

Neste estudo
O Problema: Cassandra Funcionava, Mas Não o SuficienteArquiteturaComo o Sistema Funciona: Modelo de Dados, ScyllaDB e a Camada RustMatriz de Decisão: Opções de Armazenamento AvaliadasA Decisão pelo Rust: Determinismo Como Requisito de SistemaWell-ArchitectedVeredictoReferências