O feed inicial é a página mais lida de qualquer produto social e uma das mais caras de construir. Cada usuário segue centenas de contas, cada conta publica no próprio ritmo e o feed precisa voltar em menos de algumas centenas de milissegundos, ranqueado, toda vez que alguém abre o app.
A pergunta central de design tem duas respostas clássicas, fan-out na escrita e fan-out na leitura, e todo feed grande acaba usando uma mistura das duas. Este artigo compara as duas com números, mostra o híbrido e termina com um diagrama dos caminhos completos de leitura e escrita.
Requisitos
- O usuário vê as publicações recentes das contas que segue, ranqueadas ou em ordem cronológica.
- Abrir o feed leva bem menos de um segundo no percentil 99.
- Uma publicação nova aparece no feed dos seguidores em segundos.
- Rolagem infinita sem duplicatas nem buracos.
- Continua funcionando quando uma conta tem dezenas de milhões de seguidores.
As leituras dominam. Em 2013, o Twitter atendia cerca de 300.000 leituras de timeline por segundo contra cerca de 6.000 escritas de tweets por segundo, segundo este texto sobre sua arquitetura. Projete primeiro para as leituras.
Contas de guardanapo
| Premissa | Valor |
|---|---|
| Usuários ativos por dia | 10 milhões |
| Aberturas do feed por usuário por dia | 10, ou cerca de 1.160 leituras/s em média |
| Publicações novas por dia | 1 milhão, cerca de 12 publicações/s |
| Média de seguidores por autor | 200, ou cerca de 2.300 escritas de fan-out/s |
| Tamanho do feed em cache | 800 IDs de publicação de 8 bytes, cerca de 64 GB para todos os usuários |
As médias parecem inofensivas. Os pontos fora da curva, não: um autor com 20 milhões de seguidores gera 20 milhões de escritas de fan-out com uma única publicação, um décimo de todo o trabalho diário de fan-out de todos os outros. É esse ponto fora da curva que o design abaixo precisa resolver.
Fan-out na escrita (push)
Quando alguém publica, um job em segundo plano busca os seguidores e insere o ID da publicação no topo do feed em cache de cada um. Ler o feed vira uma única busca: pegue os IDs mais novos da sua lista e busque as publicações.
- Ponto forte: leituras rápidas e previsíveis.
- Custo: uma publicação gera tantas escritas quanto o autor tem seguidores. Uma conta com 30 milhões de seguidores transforma uma publicação em 30 milhões de escritas no cache, e esses seguidores a veem tarde. O mesmo texto descreve publicações das maiores contas levando minutos para chegar a todos os seguidores.
- Desperdício: você escreve no feed de usuários que nem vão abrir o app nesta semana.
Mantenha cada feed em cache curto. Na época, o Twitter limitava as timelines iniciais a 800 entradas no Redis; publicações mais antigas vêm do banco de dados quando alguém rola até lá.
Fan-out na leitura (pull)
Não faça nada quando alguém publica. Quando um usuário abre o feed, busque as publicações recentes de todas as contas que ele segue, mescle e ranqueie.
- Ponto forte: escritas baratas, e contas grandes não custam nada a mais.
- Custo: cada leitura do feed toca centenas de autores. A 300.000 leituras por segundo, são muitas consultas para um trabalho que se repete quase todo.
O híbrido
A maioria dos feeds grandes combina os dois:
- Empurre as publicações de contas comuns para os feeds em cache dos seguidores.
- Pule o fan-out para contas acima de um limite de seguidores e busque as publicações recentes delas na leitura.
- Na leitura, mescle o feed em cache com as publicações buscadas e então ranqueie.
O limite é um botão de ajuste, não uma lei. Coloque-o onde o custo das escritas de fan-out começa a passar o custo das leituras extras, e revise conforme a distribuição de seguidores muda. Usuários que não seguem nenhuma conta grande nunca pagam pelo caminho pull.
Ranking
Um feed cronológico ordena pelo tempo. Um feed ranqueado funciona em três passos: reunir publicações candidatas (a lista mesclada acima, mais recomendações, se você tiver), pontuar cada candidata com sinais como recência, frequência de interação do usuário com o autor e engajamento na publicação, e então filtrar (contas bloqueadas, publicações já vistas, quase duplicatas).
Mantenha o ranking fora do caminho de fan-out. O fan-out guarda candidatas; o ranking acontece na leitura, com os sinais mais novos. Assim, uma mudança no modelo de ranking não exige reprocessar os feeds em cache.
Exclusões, bloqueios e contas privadas
Os feeds guardam IDs, então excluir uma publicação é uma única escrita no armazenamento de publicações; o serviço de feed descarta IDs que não resolvem mais. Bloqueios e contas que viram privadas precisam do mesmo filtro na leitura, porque tirar um ID de milhões de feeds em cache é lento e fácil de errar.
Paginação
Nunca pagine um feed com offsets. Publicações novas chegam no topo enquanto o usuário rola, então a “página 2” se desloca e mostra duplicatas. Devolva um cursor que codifica a posição, como o ID ou a pontuação do último item entregue, e peça “os itens depois deste cursor”. Em feeds ranqueados, guarde a lista ranqueada da sessão por um tempo curto para que as páginas seguintes fiquem consistentes com a primeira.
O diagrama de arquitetura
Conteúdo do diagrama
- User
- Post service
- Posts store
- Post events
- Fan-out workers
- Social graph
- Author over 1M followers?
- Feed cache
- Pulled at read time
- Feed service
- Ranking
- Media CDN
- POST /posts
- post.created
- followers
- no: push post ID
- yes
- GET /feed
- cached timeline
- big accounts
- images and video
Como ler:
- Caminho de escrita (em cima): o serviço de publicações grava a publicação e publica um evento. Os workers de fan-out leem o grafo social e checam o número de seguidores do autor.
- A decisão: para a maioria dos autores, os workers empurram o ID da publicação para o cache de feed de cada seguidor. Para contas acima do limite, nada é empurrado; as publicações delas são buscadas na leitura.
- Caminho de leitura (embaixo): o serviço de feed lê o feed em cache do usuário e as publicações recentes de contas grandes no armazenamento de publicações, mescla tudo e manda para o ranking.
- A mídia passa por um CDN e nunca pelo serviço de feed.
- O bloco Markdown mantém as regras práticas ao lado do design.
O que colocar no diagrama
- Os dois caminhos, bem separados. Escritas e leituras têm problemas de escala diferentes.
- O limite de fan-out. Escreva o número na decisão, mesmo que ele mude depois.
- O que fica onde. Os caches de feed guardam IDs de publicações, não publicações; o conteúdo fica no armazenamento de publicações.
- Onde o ranking acontece. Quem lê deve ver que ele roda na leitura.
O cache no meio deste design merece um diagrama próprio; veja o artigo sobre estratégias de cache. O artigo sobre arquitetura orientada a eventos cobre o lado de eventos e consumidores, e o exemplo de diagrama de system design mostra as mesmas convenções em outro sistema.
Monte no Archboard
- Entre com Google ou GitHub e crie um diagrama.
- Adicione uma Pessoa do pack Modelo C4 e depois nós Contêiner para o serviço de publicações, os workers de fan-out, o serviço de feed e o ranking.
- Adicione nós Banco de dados do pack Modelo C4 para o armazenamento de publicações e o grafo social, Kafka e Redis do pack Dados para os eventos de publicação e os caches de feed, e um CDN do pack Nuvem.
- Adicione uma Decisão e um Início / Fim do pack Fluxograma para o limite de seguidores.
- Desenhe o caminho de escrita em cima e o de leitura embaixo, e adicione um bloco Markdown do pack Básico com as suas regras práticas.
O exemplo usa 13 nós de diagrama e fica dentro do plano Free.
Perguntas frequentes
Fan-out na escrita ou na leitura: qual é melhor? Nenhum dos dois sozinho. Fan-out na escrita dá leituras rápidas, mas explode em contas com seguidores demais; fan-out na leitura é barato na escrita, mas lento na leitura. Feeds grandes usam um híbrido.
O que o cache de feed deve guardar? IDs de publicações, as mais novas primeiro, com limite de algumas centenas por usuário. Busque o conteúdo separadamente, para que edições e exclusões não exijam reescrever todos os feeds.
Como lidar com a publicação de uma celebridade? Pule o fan-out para contas acima de um limite de seguidores e busque as publicações recentes delas quando os seguidores abrem o feed.
Como a paginação do feed deve funcionar? Com um cursor baseado no último item entregue, não com números de página ou offsets, para que publicações novas não causem duplicatas enquanto o usuário rola.
Onde o ranking acontece? Na leitura, sobre a lista mesclada de candidatas. Mantê-lo fora do fan-out permite mudar o ranking sem reconstruir os feeds em cache.