Design clássico · Sênior
Amigos por perto
Projeto de entrevista clássico. Pontos centrais: WebSocket e presença e indexação geoespacial.
Resposta curta
Projeto de entrevista clássico. Pontos centrais: WebSocket e presença e indexação geoespacial. O exercício pede o backend de um aplicativo em que as pessoas compartilham a própria posição e descobrem quais amigos estão por perto. Diferente do caso de serviço de proximidade, aqui as localizações mudam o tempo todo, enquanto os endereços de estabelecimentos quase não mudam.
01 · CONTEXTO
Posições que não param de mudar
O exercício pede o backend de um aplicativo em que as pessoas compartilham a própria posição e descobrem quais amigos estão por perto. Diferente do caso de serviço de proximidade, aqui as localizações mudam o tempo todo, enquanto os endereços de estabelecimentos quase não mudam.
Em vez de consultar pontos parados, o sistema precisa distribuir uma corrente de atualizações a quem se interessa por elas.
No lado funcional, cada amigo aparece com distância e hora da atualização da posição, e a lista é atualizada a cada poucos segundos. "Perto" é um raio de cinco milhas, configurável, e o histórico de posições deve ser guardado, por exemplo para aprendizado de máquina.
No lado não funcional, a latência baixa importa para receber as atualizações sem muito atraso. Perder ocasionalmente um ponto de dados é aceitável, desde que o sistema esteja em geral disponível, e o armazenamento de posição não precisa de consistência forte.
02 · ESCALA
As contas que mandam no desenho
As premissas da fonte: a posição é atualizada a cada 30 segundos, porque quem anda a pé se move devagar; cem milhões de usuários usam a funcionalidade por dia, com 10% simultâneos, ou seja, dez milhões; e cada usuário tem em média 400 amigos, todos usando a funcionalidade.
Dividindo os dez milhões de usuários simultâneos pelo intervalo de 30 segundos, saem cerca de 334 mil atualizações de posição por segundo. Com 400 amigos e 10% deles online, cada atualização recebida gera cerca de 40 atualizações a encaminhar.
03 · ARQUITETURA
Servidor compartilhado e um canal por usuário
| Abordagem | O que a fonte diz |
|---|---|
| Ponto a ponto | Pouco prática para celular com conexão instável e restrição de energia |
| Servidor compartilhado | Serve como mecanismo de fan-out para os amigos que se quer alcançar |
O servidor compartilhado recebe as posições de todos os usuários ativos, acha para cada atualização os usuários ativos que devem recebê-la e a encaminha, sem repassar quando a distância entre os amigos passa do limite configurado.
Os servidores WebSocket são com estado e encaminham as atualizações aos clientes. O cache de localização guarda a posição mais recente de cada usuário ativo e cada entrada tem prazo de validade: ao expirar, o usuário deixa de ser ativo e seus dados saem do cache. O Pub/sub é um barramento leve com um canal por usuário. Servidores REST cuidam de tarefas auxiliares como amizades e perfis.
O histórico de posições não é necessariamente usado pela funcionalidade em si, e um banco como o Cassandra pode guardá-lo por ser otimizado para carga pesada de escrita.
O celular envia a posição ao balanceador, que a encaminha à conexão persistente do cliente no servidor WebSocket.
O servidor grava a posição no histórico e atualiza o cache de localização.
O servidor publica a posição no canal do usuário, via Pub/sub.
O Pub/sub difunde a atualização a todos os assinantes do canal, isto é, aos servidores responsáveis pelos amigos.
Cada servidor assinante calcula a quem a atualização deve ir e a envia ao celular do amigo.
04 · FAN-OUT
Quem assina o quê
Na inicialização, o servidor busca os amigos do usuário, assina os canais deles no Pub/sub, lê as posições no cache e as repassa ao cliente. Em regime, os servidores WebSocket assinam canais para os usuários conectados a eles, e cada publicação no canal de alguém chega aos servidores responsáveis por seus amigos.
O servidor do remetente publica a posição no canal do usuário.
O canal difunde a mesma atualização a todos os servidores que o assinam.
Cada servidor assinante calcula a quem a atualização deve ir.
Onde o amigo está dentro do limite de distância, a posição é repassada ao celular dele.
Onde a distância passa do limite configurado, o servidor não repassa a posição.
No Pub/sub, o publicador não conhece os assinantes: as mensagens são categorizadas em canais, e os assinantes as recebem na ordem em que foram publicadas. Canal criado e sem uso não consome memória, o que permite pré-alocar canais para todos os usuários da funcionalidade.
05 · GARANTIAS
O que o Pub/sub promete e o que não promete
A documentação do Redis diz que o Pub/sub entrega no máximo uma vez, e que, se o assinante não consegue tratar a mensagem, ela se perde para sempre. Do lado do requisito, perder ocasionalmente um ponto de dados é aceitável.
| Mecanismo | O que a documentação diz |
|---|---|
| Pub/sub do Redis | Entrega no máximo uma vez; a mensagem que o assinante não consegue tratar se perde para sempre |
| Redis Streams | As mensagens são persistidas |
06 · BARRAMENTO
Espalhando os canais por muitos servidores
Na estimativa da fonte, é preciso empurrar cerca de 14 milhões de atualizações por segundo. Supondo 100 mil pushes por segundo por servidor, são necessários pelo menos 140 servidores Redis, e por isso um cluster distribuído por causa da carga de CPU.
Para sustentar o cluster, a fonte usa um componente de descoberta de serviço, como ZooKeeper ou etcd, para saber quais servidores estão vivos. Os servidores WebSocket usam os dados guardados ali para achar onde mora cada canal, e o anel de hash pode ficar em memória em cada um deles.
O servidor WebSocket precisa saber onde mora o canal; ele consulta o anel de hash vindo da descoberta de serviço, que pode ficar em memória nele.
O canal do usuário cai no nó indicado pelo anel.
O servidor publica e assina o canal diretamente nesse nó.
Quando um nó é acrescentado, o hash consistente minimiza os canais que mudam de lugar.
Os servidores WebSocket refazem as assinaturas dos canais movidos, e algumas atualizações podem se perder nesse intervalo.
A documentação do Redis descreve ainda o Pub/sub fragmentado, que ajuda a escalar o Pub/sub em modo cluster e limita o tráfego no barramento do cluster em comparação com o Pub/sub global, em que cada mensagem vai a todos os nós.
A fonte também cita Erlang como alternativa ao Redis, que permitiria criar milhões de pequenos processos que se comunicam entre si, ao custo de ser uma linguagem de nicho em que pode ser difícil contratar pessoas com experiência.
07 · BORDAS
Casos de borda
Para usuários com muitos amigos, a fonte sugere limitar o número de amigos, por exemplo com um teto de cinco mil. O servidor de um usuário assim pode ter mais carga, mas com servidores suficientes fica bem.
Para a variação em que aparecem pessoas aleatórias por perto, define-se um conjunto de canais de Pub/sub por geohash, e é possível assinar vários geohashes para cobrir quem está perto mas em um geohash vizinho.
Ao retirar um servidor, marca-se o servidor como draining no balanceador para parar de mandar conexões a ele.
Perguntas frequentes
O que é Amigos por perto?
Projeto de entrevista clássico. Pontos centrais: WebSocket e presença e indexação geoespacial.
O que o design de Amigos por perto cobre?
Ele está organizado nestas seções: Posições que não param de mudar, As contas que mandam no desenho, Servidor compartilhado e um canal por usuário, Quem assina o quê, O que o Pub/sub promete e o que não promete e Espalhando os canais por muitos servidores.
Quanto tempo leva Amigos por perto e qual é o nível?
Leva cerca de 16 minutos para ler e tem nível Sênior.
Agora projete você mesmo
Rode este caso como uma entrevista na escola de System Design: você toma as decisões, o tutor as questiona e você vê a nota do seu desenho.