Resposta A: Anthropic Claude Sonnet 5
Design de Sistema de Notificações em Tempo Real para um Aplicativo de Mídia Social
1. Recapitulação dos Requisitos e Estimativa Preliminar
Estimativa de Tráfego:
- 10M DAU × 20 notificações/dia = 200M notificações/dia
- Taxa média = 200M / 86.400s ≈ 2.315 notificações/seg
- Taxa de pico (5x) ≈ 11.500 notificações/seg
- A 100M DAU (futuro): média ~23.000/s, pico ~115.000/s
Estimativa de Armazenamento:
- Cada registro de notificação ~500 bytes (metadados + payload)
- Armazenamento diário: 200M × 500B = 100GB/dia → ~36TB/ano (antes de compressão/arquivamento)
- Requisito de histórico (últimos 100 p...
Mostrar resposta completa ▼
Design de Sistema de Notificações em Tempo Real para um Aplicativo de Mídia Social
1. Recapitulação dos Requisitos e Estimativa Preliminar
Estimativa de Tráfego:
- 10M DAU × 20 notificações/dia = 200M notificações/dia
- Taxa média = 200M / 86.400s ≈ 2.315 notificações/seg
- Taxa de pico (5x) ≈ 11.500 notificações/seg
- A 100M DAU (futuro): média ~23.000/s, pico ~115.000/s
Estimativa de Armazenamento:
- Cada registro de notificação ~500 bytes (metadados + payload)
- Armazenamento diário: 200M × 500B = 100GB/dia → ~36TB/ano (antes de compressão/arquivamento)
- Requisito de histórico (últimos 100 por usuário) é um padrão de acesso "quente" com muitas leituras, favorecendo um design onde notificações recentes são baratas de buscar (por exemplo, lista limitada por usuário), enquanto o histórico completo vai para armazenamento frio.
Isso confirma que estamos lidando com um sistema intensivo em gravação, intensivo em fan-out, sensível à latência que deve desacoplar a ingestão da entrega.
2. Arquitetura de Alto Nível
[Produtores de Eventos] → [Barramento de Eventos / Kafka] → [Serviço de Notificação (Consumidores)]
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
[Serviço de Preferências] [Renderização/Template] [Limitador de Taxa/Deduplicação]
│ │ │
└───────────────┬────────────┴───────────────────────────┘
▼
[Distribuidor / Roteador de Entrega]
┌─────────────┬─────────────┬─────────────┐
▼ ▼ ▼ ▼
[Serviço Push] [Gateway WebSocket/ [Serviço de Email] [Armazenamento In-App
(FCM/APNs)] SSE] (SES/SendGrid) (Escrita - DynamoDB/Cassandra)]
│ │
[Dispositivos Móveis][Clientes Conectados]
Fluxo:
- Um sistema de origem (por exemplo, Serviço de Curtidas, Serviço de Comentários, Serviço de Seguidores, Serviço de Mensagens) emite um evento (por exemplo,
user_liked_post) para um barramento de mensagens durável (Kafka). - O Orquestrador de Notificações consome esses eventos, verifica as preferências do usuário, aplica lógica de deduplicação/limitação de taxa/agrupamento (por exemplo, "John e outras 5 pessoas curtiram sua postagem") e gera um objeto de notificação.
- O orquestrador persiste a notificação (para histórico) e a envia para um Distribuidor de Entrega, que faz o fan-out para os canais apropriados com base na preferência do usuário e no estado do dispositivo (online vs. offline).
- Os trabalhadores de entrega lidam com a transmissão real: provedores de notificação push (FCM para Android, APNs para iOS), WebSocket/SSE para atualizações de badge em tempo real no aplicativo e e-mail via provedor de e-mail transacional.
3. Componentes Chave
3.1 Camada de Ingestão de Eventos — Apache Kafka
- Todos os serviços de origem publicam eventos em tópicos do Kafka (particionados por
user_idpara preservar a ordem por usuário). - O Kafka fornece durabilidade (fator de replicação 3), alta taxa de transferência e buffer natural durante picos de tráfego — crítico, pois o tráfego de pico é 5x a média.
- Tópicos:
notification.likes,notification.comments,notification.followers,notification.messages(ou um único tópico com campo de tipo de evento, dependendo das necessidades de evolução do esquema).
Por que Kafka em vez de SQS/RabbitMQ? O Kafka lida com taxa de transferência muito alta com baixa sobrecarga por mensagem e suporta repetição (útil para reprocessar lotes com falha ou preenchimento posterior). O SQS é mais simples operacionalmente, mas mais difícil de escalar para >100k msg/s de forma econômica e não suporta semânticas de repetição de grupo de consumidores de forma tão limpa.
3.2 Serviço Orquestrador de Notificações
- Grupo de consumidores sem estado lendo do Kafka.
- Responsabilidades:
- Verificação de Preferências: Consultar um armazenamento rápido de chave-valor (Redis ou DynamoDB) para as configurações de notificação do usuário antes de processar mais. Isso evita trabalho desperdiçado gerando notificações que um usuário optou por não receber.
- Deduplicação/Agrupamento: Usar uma janela de agregação de curta duração (por exemplo, conjuntos ordenados do Redis com TTL) para agrupar eventos semelhantes (por exemplo, várias curtidas na mesma postagem em 60 segundos se tornam uma notificação).
- Fan-out para seguidores: Para eventos como "nova postagem de alguém que você segue", isso pode exigir fan-out para milhões de seguidores (problema da celebridade). Use um modelo de fan-out híbrido:
- Fan-out na escrita para usuários regulares (enviar para o feed de notificações de cada seguidor imediatamente).
- Fan-out na leitura para contas de celebridades/alto número de seguidores (calcular no momento da leitura para evitar uma tempestade de escrita).
- Escalável horizontalmente — escalar instâncias de consumidor com base na contagem de partições e no atraso do Kafka.
3.3 Armazenamento de Notificações (Camada de Persistência)
- Armazenamento primário: Um banco de dados NoSQL de colunas largas como Apache Cassandra ou DynamoDB, particionado por
user_id, clusterizado/ordenado portimestamp(decrescente).- Este modelo é ideal porque o padrão de acesso dominante é "obter as últimas 100 notificações para o usuário X", que é uma consulta de intervalo simples em uma partição — sem junções necessárias.
- O Cassandra oferece consistência ajustável e escalabilidade horizontal bem além de 100M de usuários; o DynamoDB oferece uma alternativa totalmente gerenciada com menos sobrecarga operacional (troca: custo mais alto em escala muito grande e risco de partição quente para usuários extremamente ativos, a menos que as chaves de partição sejam salgadas).
- TTL/Arquivamento: Manter apenas notificações recentes (por exemplo, 30-90 dias) no armazenamento quente; dados mais antigos arquivados em armazenamento mais barato (S3 + Glacier) para conformidade/auditoria, com o limite de "últimos 100" aplicado no momento da escrita (uma lista limitada por usuário, ou aparada por compactação periódica).
3.4 Distribuidor de Entrega
- Lê o objeto de notificação finalizado e determina os canais a serem usados com base em:
- Preferências de canal do usuário (push/email/ambos/nenhum)
- Status online do usuário (rastreado por um serviço de presença com suporte do Redis, atualizado por WebSocket/heartbeat)
- Roteia para:
- Serviço de Notificação Push: Integra-se com FCM (Android) e APNs (iOS). Empacotar em uma camada de abstração interna para normalizar retentativas, formatos de payload e gerenciamento de tokens de dispositivo (tokens armazenados em uma tabela
user_devices, atualizados no lançamento do aplicativo). - Entrega em Tempo Real no Aplicativo: Para usuários ativamente conectados via WebSocket/SSE, enviar diretamente para um gateway de conexão (por exemplo, uma frota de servidores WebSocket atrás de um balanceador de carga, usando algo como Socket.IO ou um serviço gerenciado como AWS API Gateway WebSockets). O mapeamento conexão-servidor é rastreado no Redis para que qualquer nó distribuidor possa encontrar qual instância do gateway possui a conexão de um usuário.
- Serviço de Email: Para notificações menos sensíveis ao tempo (por exemplo, resumo semanal) ou como fallback para usuários offline em certos tipos de notificação, integrar com um provedor como Amazon SES ou SendGrid, usando uma fila separada de menor prioridade, pois os SLAs de e-mail são mais flexíveis (segundos a minutos está bom).
- Serviço de Notificação Push: Integra-se com FCM (Android) e APNs (iOS). Empacotar em uma camada de abstração interna para normalizar retentativas, formatos de payload e gerenciamento de tokens de dispositivo (tokens armazenados em uma tabela
3.5 Serviço de Preferências
- Serviço simples com suporte de um banco de dados relacional (Postgres) ou DynamoDB, com cache agressivo no Redis (preferências mudam com pouca frequência, lidas com extrema frequência — candidato perfeito para cache).
- Esquema:
user_id, notification_type, channel, enabled.
4. Modelo de Dados
Tabela de Notificações (Cassandra/DynamoDB)
Chave de Partição: user_id
Chave de Clusterização: notification_id (UUID baseado em tempo, ordenado decrescente)
Atributos:
- type (like, comment, follow, message)
- actor_id (quem o acionou)
- actor_ids (array, para notificações agrupadas)
- target_object_id (post_id, comment_id, etc.)
- message_preview
- created_at
- read_status (booleano)
- delivered_channels (array: push, email, in-app)
Tabela de Preferências do Usuário
Chave de Partição: user_id
Atributos: { likes: {push: true, email: false}, comments: {...}, follows: {...}, messages: {...} }
Tabela de Tokens de Dispositivo
Chave de Partição: user_id
Chave de Clusterização: device_id
Atributos: platform (ios/android), token, last_active
5. Garantindo Confiabilidade ("Nenhuma notificação perdida")
- Mensagens duráveis: Kafka com fator de replicação ≥3 e
acks=allnos produtores garante que os eventos não sejam perdidos antes do processamento. - Processamento de pelo menos uma vez com idempotência: Os consumidores podem reprocessar em caso de falha/reinicialização, portanto, os IDs de notificação são gerados deterministicamente (por exemplo, hash do ID do evento de origem + tipo) para permitir gravações idempotentes — evita notificações duplicadas na retentativa.
- Filas de mensagens mortas (DLQ): Entregas com falha (por exemplo, tempo limite do provedor push) vão para um tópico DLQ com retentativa de backoff exponencial (por exemplo, 3 retentativas com jitter), e depois para revisão manual/alerta se ainda falharem.
- Escrever notificação no armazenamento ANTES de tentar a entrega: Isso desacopla "notificação existe" (durabilidade/histórico) de "notificação entregue" (melhor esforço em tempo real). Mesmo que a entrega push falhe, o usuário a verá na próxima vez que abrir o aplicativo e consultar a API de notificações.
- Padrão de outbox nos serviços de origem: Para evitar problemas de gravação dupla (gravação no DB + publicação de evento), use o padrão de outbox transacional para que, quando uma "curtida" for registrada no DB do serviço de origem, o evento seja garantido para ser publicado no Kafka via uma ferramenta de captura de dados de alteração (CDC) como Debezium.
6. Estratégia de Escalabilidade (10M → 100M DAU)
- Kafka: Aumentar a contagem de partições (particionadas por hash do user_id) — escala linearmente com mais brokers/consumidores.
- Orquestrador e Distribuidor de Notificações: Sem estado, escaláveis horizontalmente atrás de grupos de consumidores; escalar via HPA do Kubernetes com base no atraso do consumidor do Kafka.
- Cassandra: Adicionar nós ao anel; o hashing consistente distribui a carga automaticamente. Observar partições quentes de conteúdo viral/contas de celebridades — mitigar via bucketing (por exemplo, dividir o fan-out de uma celebridade em várias chaves de shard).
- Gateway WebSocket: Escalar horizontalmente; usar sessões fixas via um registro de conexão no Redis para que os distribuidores saibam qual nó do gateway possui a conexão de um usuário, independentemente de quantos nós do gateway existam.
- Provedores Push/Email: Estes são serviços gerenciados de terceiros (FCM, APNs, SES) que escalam independentemente; nossa responsabilidade é agrupar e limitar a taxa para permanecer dentro das cotas do provedor.
- Cache: Cache agressivo no Redis de preferências e dados de presença reduz a carga do DB à medida que o número de usuários cresce 10x.
7. Principais Compromissos
| Decisão | Compromisso |
|---|---|
| Kafka vs. fila mais simples (SQS) | Kafka adiciona complexidade operacional (precisa de expertise de operações dedicada, gerenciamento de ZooKeeper/KRaft), mas fornece taxa de transferência e capacidade de repetição muito superiores necessárias nesta escala. |
| Cassandra vs. DynamoDB | Cassandra oferece mais controle e potencialmente menor custo em escala muito grande, mas requer operações internas; DynamoDB é totalmente gerenciado (mais rápido para construir, menos sobrecarga operacional), mas pode ficar caro em escala extrema e tem restrições mais rígidas de tamanho de item/particionamento de taxa de transferência. |
| Fan-out na escrita vs. fan-out na leitura | Fan-out no momento da escrita oferece menor latência de leitura (ótimo para o SLA de 2 segundos), mas arrisca uma tempestade de escrita "thundering herd" para celebridades; fan-out na leitura evita isso, mas adiciona latência e computação no momento da leitura. A abordagem híbrida equilibra ambos, mas adiciona complexidade de design/código (dois caminhos de código). |
| Armazenar histórico completo vs. 100 notificações recentes quentes + arquivo | Reduz os custos de armazenamento quente e mantém as consultas rápidas, mas requer um caminho de arquivamento/recuperação para conformidade ou recursos de "carregar mais", adicionando complexidade. |
| Entrega de pelo menos uma vez + idempotência vs. exatamente uma vez | Semântica de exatamente uma vez em sistemas distribuídos são caras/complexas (precisa de consumidores transacionais); entrega de pelo menos uma vez + gravações idempotentes alcançam a mesma garantia prática (nenhuma notificação visível duplicada) a um custo operacional muito menor. |
| Push em tempo real para tudo vs. agrupamento/agregação | Agrupar notificações semelhantes (por exemplo, "10 pessoas curtiram sua foto") reduz a fadiga de notificações e o volume de entrega, melhorando a experiência do usuário e o custo, ao custo de uma lógica de orquestração um pouco mais complexa e um pequeno atraso de buffer (ainda bem dentro do SLA de 2 segundos se a janela for curta, por exemplo, máximo de 5-10s antes de descarregar). |
8. Monitoramento e Alertas
- Rastrear atraso do consumidor do Kafka, taxa de sucesso/falha de entrega por canal, latência ponta a ponta P50/P95/P99 (ingestão de eventos → entrega no dispositivo) e profundidade da DLQ.
- Dashboards de SLA alertam o plantão se a latência P95 exceder 2s ou o crescimento da DLQ indicar uma falha de entrega sistêmica (por exemplo, interrupção do provedor FCM), acionando lógica de failover automática (por exemplo, fila de espera + retentativa com backoff em vez de descarte).
Resumo
O design desacopla a geração de notificações (orientado a eventos, baseado em Kafka, idempotente) da entrega (distribuidor multicanal com retentativa/backoff por canal), suportado por um armazenamento de colunas largas otimizado para o padrão de acesso "histórico recente por usuário". Essa separação é o que permite que o sistema atenda ao SLA de latência de 2 segundos, garantindo nenhuma perda de dados, e ele escala horizontalmente em todos os níveis (ingestão, processamento, armazenamento, entrega) para suportar 10x o crescimento futuro com sobrecarga operacional e de custo gerenciável.
Resultado
Votos de vitória
0 / 3
Pontuação média
Pontuação total
Comentário geral
A Resposta A é uma proposta de design de sistema robusta, com estimativas de capacidade precisas, uma arquitetura lógica orientada a eventos, uso apropriado de Kafka, Cassandra/DynamoDB, Redis, WebSockets, APNs/FCM e provedores de e-mail, além de boa cobertura de persistência, preferências, retentativas, DLQs, idempotência, monitoramento e trade-offs. Suas principais fraquezas são que algumas áreas são menos precisas do que poderiam ser, como o limite exato de latência, a estratégia de HA/DR, detalhes do caminho de leitura/API, priorização de sobrecarga e semânticas de entrega nuances em relação a provedores externos.
Ver detalhes da avaliação ▼
Qualidade da arquitetura
Peso 30%A Resposta A apresenta uma arquitetura coerente com produtores de eventos, Kafka, orquestração de notificações, verificações de preferências, armazenamento, despachantes, entrega via WebSocket, provedores de push e workers de e-mail. O fluxo é lógico e completo, embora alguns detalhes do caminho de leitura/API e da separação de comandos de canal sejam menos explícitos.
Completude
Peso 20%A Resposta A cobre os requisitos principais: tipos de notificação, entrega quase em tempo real, canais push/e-mail/in-app, histórico dos últimos 100, preferências, escalabilidade, confiabilidade, custo, monitoramento e modelos de dados. É um pouco mais leve em detalhes de API, segurança/privacidade, recuperação de desastres e comportamento operacional exato durante sobrecarga ou falhas de provedores.
Análise de trade-offs
Peso 20%A Resposta A inclui uma tabela útil de trade-offs cobrindo Kafka vs. SQS, Cassandra vs. DynamoDB, fan-out-on-write vs. fan-out-on-read, armazenamento quente limitado vs. arquivo, at-least-once vs. exactly-once, e entrega em lote vs. em tempo real. O raciocínio é sólido, embora alguns trade-offs sejam resumidos em vez de estarem profundamente ligados às consequências operacionais.
Escalabilidade e confiabilidade
Peso 20%A Resposta A oferece mecanismos robustos de escalabilidade e confiabilidade: replicação e replay do Kafka, escritas idempotentes, DLQs, retentativas, padrão outbox, escalonamento horizontal, particionamento Cassandra/DynamoDB, cache Redis e escalonamento de WebSocket. É menos detalhada em recuperação multirregional, priorização de sobrecarga, disciplina de offset do consumidor, limites de entrega do provedor e semânticas exatas de SLO.
Clareza
Peso 10%A Resposta A é claramente estruturada, fácil de seguir e utiliza diagramas, marcadores, esquemas e uma tabela concisa de trade-offs de forma eficaz. Ela comunica o design de forma eficiente com o mínimo de ambiguidade.
Pontuação total
Comentário geral
A Resposta A é uma proposta de design polida e bem estruturada que seria bem recebida em uma entrevista. Ela acerta na matemática de capacidade, fornece um diagrama de arquitetura legível, modelos de dados concretos, uma tabela de trade-offs clara e aborda o padrão outbox, idempotência, DLQs e escalabilidade horizontal em todos os níveis. Suas fraquezas são a profundidade e a amplitude em algumas áreas: sem design de API/caminho de leitura, sem discussão de segurança ou privacidade, sem topologia HA ou recuperação de desastres, sem isolamento de prioridade entre classes de notificação e aceita acriticamente o SLA de ponta a ponta de 2 segundos sem notar que provedores de terceiros o tornam inexequível. A sugestão de fan-out-on-read também é um ajuste um tanto estranho para uma caixa de entrada de notificações.
Ver detalhes da avaliação ▼
Qualidade da arquitetura
Peso 30%Apresenta uma arquitetura em camadas clara com um diagrama ASCII, barramento de eventos (Kafka), orquestrador, serviço de preferência, despachante, gateway WebSocket, workers de push/email e um armazenamento wide-column. O fluxo é fácil de seguir e os componentes são bem delineados. Pequenas fraquezas: a discussão de fan-out-on-read é um tanto mal aplicada a notificações (é um conceito de feed), e a camada de API/caminho de leitura mais gateway de API são mal abordadas, apesar de terem sido destacadas na política de julgamento.
Completude
Peso 20%Cobre estimativa, todos os quatro tipos de notificação, canais push/email/in-app, histórico via lista limitada mais arquivamento, preferências com esquema, tokens de dispositivo, confiabilidade, escalabilidade e monitoramento. Ausente ou fino: design de API/caminho de leitura, segurança e privacidade, recuperação de desastres e estratégia multirregional, contagens de não lidos e topologia HA explícita (distribuição AZ).
Análise de trade-offs
Peso 20%Uma tabela de trade-offs dedicada cobre Kafka vs SQS, Cassandra vs DynamoDB, fan-out na escrita vs leitura, armazenamento quente vs arquivo, at-least-once vs exactly-once, e batching vs imediatismo. Cada entrada nomeia tanto o benefício quanto o custo, o que é claro e legível. No entanto, os trade-offs são em sua maioria convencionais e declarados brevemente, com menos profundidade em limites de consistência, garantias de ordenação ou nuances de definição de SLO.
Escalabilidade e confiabilidade
Peso 20%Sólido: particionamento e replicação do Kafka, acks=all, at-least-once com IDs idempotentes determinísticos, DLQ com backoff exponencial e jitter, write-before-deliver, outbox transacional com Debezium, HPA em lag do consumidor, expansão do anel do Cassandra, salting de partição quente e registro de conexão Redis. Falta HA explícita multi-AZ/multirregional, procedimentos de DR/failover, backpressure ou isolamento de prioridade entre classes de notificação e semântica de commit de offset.
Clareza
Peso 10%Excelente legibilidade: seções numeradas, um diagrama de arquitetura ASCII, blocos de código para o modelo de dados, uma tabela de trade-offs e um resumo conciso de encerramento. Fácil de percorrer e rápido de entender o design rapidamente.
Pontuação total
Comentário geral
A Resposta A fornece um design de sistema muito forte e bem estruturado. Seus principais pontos fortes são a clareza e a organização, usando um diagrama e uma tabela para tornar conceitos complexos fáceis de entender. Ela cobre todos os requisitos centrais da solicitação, propondo uma arquitetura lógica com escolhas de tecnologia apropriadas e estratégias sólidas para escalabilidade e confiabilidade. No entanto, falta a profundidade e a amplitude da Resposta B, particularmente em áreas como design de API, segurança e planejamento detalhado de recuperação de desastres.
Ver detalhes da avaliação ▼
Qualidade da arquitetura
Peso 30%A arquitetura proposta é lógica, completa e bem adequada para a tarefa. Ela identifica claramente todos os componentes principais e suas interações, e a inclusão de um diagrama auxilia muito na compreensão. O fluxo de produtores de eventos para canais de entrega é bem definido.
Completude
Peso 20%A resposta aborda todos os requisitos funcionais e não funcionais especificados na solicitação. Ela cobre os principais aspectos do design, incluindo estimativas, componentes, modelos de dados e estratégias de escalabilidade e confiabilidade.
Análise de trade-offs
Peso 20%A resposta discute claramente os principais trade-offs em uma tabela dedicada, o que é muito eficaz. Ela fornece raciocínio sólido para escolhas como Kafka em vez de SQS e Cassandra em vez de DynamoDB, demonstrando um bom entendimento dos princípios envolvidos.
Escalabilidade e confiabilidade
Peso 20%O design aborda efetivamente a escalabilidade e a confiabilidade. Ele propõe técnicas padrão e eficazes como escalonamento horizontal para serviços, particionamento em Kafka e Cassandra, e o uso de DLQs e idempotência para confiabilidade. As estratégias são sólidas e bem explicadas.
Clareza
Peso 10%A resposta é excepcionalmente clara e bem organizada. O uso de títulos, um diagrama de fluxo e uma tabela de trade-offs torna o design complexo fácil de seguir e digerir. A escrita é direta e objetiva.