Design clássico · Pleno
Juiz online (LeetCode)
Projeto de entrevista clássico. Pontos centrais: sandbox, agendamento de jobs e rankings e leaderboards.
Resposta curta
Projeto de entrevista clássico. Pontos centrais: sandbox, agendamento de jobs e rankings e leaderboards. O exercício clássico é projetar uma plataforma como o LeetCode, que ajuda engenheiros a se preparar para entrevistas de programação e promove competições de código periódicas.
01 · CONTEXTO
Um juiz online em escala modesta
O exercício clássico é projetar uma plataforma como o LeetCode, que ajuda engenheiros a se preparar para entrevistas de programação e promove competições de código periódicas. O usuário vê a lista de problemas, abre um problema e escreve a solução em várias linguagens, envia o código e recebe retorno imediato, e acompanha um ranking ao vivo durante as competições.
Os requisitos não funcionais moldam o desenho: priorizar disponibilidade sobre consistência, garantir isolamento e segurança ao rodar código de usuário, devolver o resultado da submissão em até cinco segundos e escalar para competições com cem mil usuários.
Segundo a análise do Hello Interview, a plataforma real tem poucas centenas de milhares de usuários e cerca de quatro mil problemas. Em relação à maioria dos exercícios de design, é um sistema de escala pequena, e isso deve pesar nas escolhas.
02 · MODELO E API
Entidades, endpoints e arquitetura simples
Três entidades bastam. O problema guarda o enunciado, os casos de teste e a saída esperada; a submissão guarda o código enviado e o resultado da execução contra os casos de teste; e o ranking guarda a classificação das competições.
A API segue os requisitos um a um: uma listagem paginada de problemas, a leitura de um problema com o código inicial na linguagem pedida, o envio de uma solução e a leitura paginada do ranking de uma competição.
Para um sistema pequeno, a análise defende uma arquitetura cliente-servidor simples, até monolítica, porque o custo de gerenciar vários serviços não compensa. Para guardar os problemas, escolhe um banco NoSQL como o DynamoDB, já que não há consultas complexas e os casos de teste podem ficar aninhados como subdocumento do problema.
03 · EXECUÇÃO
Onde rodar código desconhecido
Rodar a submissão é o ponto em que o caso fica interessante. A opção ingênua, executar o código dentro do servidor da API, é descrita como um risco enorme de segurança. Além disso, o código roda no mesmo processo do servidor: se ele travar, derruba o servidor junto.
| Opção | O que oferece | Limite principal |
|---|---|---|
| Máquina virtual | Ambiente isolado sobre o servidor, que pode ser reiniciado se algo der errado | Consome muitos recursos e demora a iniciar |
| Contêiner | Compartilha o kernel do host e isola os processos da aplicação; é mais leve e inicia mais rápido | Isola menos que a máquina virtual e exige configuração cuidadosa para evitar fuga para o host |
| Função serverless | Pequena, sem estado e disparada por evento; o provedor escala conforme a demanda | A partida a frio adiciona latência na primeira requisição, e limites de recursos afetam execuções pesadas |
A análise escolhe contêineres, um para cada linguagem suportada e reaproveitado entre várias submissões em vez de criar uma máquina virtual por envio. Prefere-os à função serverless porque não prevê grande variação no volume de submissões e quer evitar a latência de partida a frio.
O cliente envia o código e o identificador do problema ao servidor de API, que os encaminha ao contêiner da linguagem escolhida.
O executor gerencia o contêiner da linguagem, que roda o código em um ambiente isolado.
O executor aciona o contêiner de forma síncrona, espera o término e lê a saída; o contêiner em si não faz chamadas de saída.
O servidor de API grava o resultado da submissão no banco e o devolve ao cliente.
04 · SANDBOX
Endurecendo o contêiner
Contêineres compartilham o kernel do host, então a configuração decide a segurança. A análise lista medidas adicionais, resumidas abaixo.
| Medida | Efeito |
|---|---|
| Sistema de arquivos somente leitura | Código montado como somente leitura; a saída vai para um diretório temporário apagado pouco depois |
| Limites de CPU e memória | Se os limites forem excedidos, o contêiner é encerrado, evitando esgotamento de recursos |
| Tempo limite explícito | Encerra o processo que passar de um limite predefinido, o que também ajuda a cumprir o requisito de resposta em cinco segundos |
| Sem acesso à rede | A rede do contêiner é desativada; na AWS, grupos de segurança e NACLs da VPC restringem o tráfego, exceto conexões essenciais |
| Chamadas de sistema restritas | O seccomp limita as chamadas de sistema que o contêiner pode fazer, para não comprometer o host |
05 · ESCALA
Picos de submissões: fila e contêineres
A conta da análise parte de um pico de dez mil submissões, cada uma rodando cerca de cem casos de teste, com uma premissa conservadora de cem milissegundos por caso. Isso dá um milhão de casos de teste, ou cem mil segundos de execução, e cerca de 1.667 núcleos para terminar em um minuto. A própria fonte nota que, na prática, a maioria dos casos termina em 10 a 50 ms.
Escalar na vertical não resolve: os maiores tipos de instância da AWS chegam a algumas centenas de vCPUs, e uma máquina grande e cara ficaria ociosa na maior parte do tempo.
A saída é escalar na horizontal os contêineres de cada linguagem, com grupos de auto scaling ajustando o número de instâncias à demanda. O único risco considerável é o superprovisionamento, que a fonte trata como administrável, já que a nuvem facilita subir e descer capacidade.
A análise ainda propõe uma fila gerenciada entre o servidor de API e os contêineres: a API enfileira a submissão e os contêineres retiram itens conforme ficam disponíveis. A execução pode levar vários segundos porque o código roda contra centenas de casos de teste, o que combina com o padrão de tarefas longas, em que a API devolve logo um identificador de trabalho e workers em segundo plano fazem o trabalho demorado.
O cliente envia o código ao servidor de API, que coloca a submissão na fila em vez de executá-la na hora.
Os executores retiram submissões da fila conforme ficam disponíveis. A fila ajuda a gerenciar a carga e permite tentar de novo se um contêiner falhar.
Quando o executor obtém o resultado, avisa o servidor de aplicação.
O servidor atualiza ao mesmo tempo o banco e o cache.
Como a fila tornou o sistema assíncrono, o cliente consulta o resultado em intervalos, a cada segundo na proposta da fonte; o endpoint busca a submissão no banco.
Alguns candidatos propõem uma conexão persistente, como WebSockets, para evitar o polling. A fonte diz que funcionaria, mas acrescenta complexidade e não é necessária com consulta a cada segundo.
06 · RANKING
Ranking ao vivo sem derrubar o banco
A fonte define a competição com noventa minutos de duração e até cem mil usuários. A pontuação é o número de problemas resolvidos e, em caso de empate, vale o tempo para concluir todos os dez problemas.
A versão ingênua consulta as submissões da competição, agrupa por usuário e ordena pelo número de problemas distintos resolvidos, e o cliente repete o pedido a cada poucos segundos. A própria análise avisa que isso coloca carga significativa no banco.
| Abordagem | Como funciona | Limite principal |
|---|---|---|
| Polling com consulta ao banco | A cada polling o servidor consulta o banco, ordena os usuários e devolve o topo | Carga alta no banco e latência crescente com o número de usuários |
| Cache com atualização periódica | Um cache como o Redis guarda o ranking e é atualizado periodicamente a partir do banco | A atualização é tão recente quanto o refresh do cache, e o cliente ainda precisa consultar |
| Conjunto ordenado do Redis | Banco e conjunto ordenado são atualizados a cada submissão processada, e o servidor devolve o topo da memória | Atraso por polling de cerca de cinco segundos, aceitável para a maioria dos usuários |
No Redis, o ranking fica em um conjunto ordenado com uma chave por competição: o valor é o identificador do usuário e a pontuação é o total ou o tempo de solução. Ao processar uma submissão, atualiza-se o conjunto com ZADD, e o topo é lido com ZRANGE em ordem reversa, com as pontuações.
O cliente pede o ranking da competição e repete o pedido a cada cinco segundos.
O servidor lê o topo diretamente do conjunto ordenado em memória, sem consultas caras ao banco.
Quando uma submissão é processada, o banco e o conjunto ordenado são atualizados.
O cliente recebe as primeiras páginas do topo e busca as seguintes conforme navega. A análise prefere polling a WebSockets, que seriam exagero para o número modesto de usuários e o atraso aceitável, e sugere até variar o intervalo, consultando com mais frequência no fim da competição.
07 · CASOS DE TESTE
Um conjunto de testes para qualquer linguagem
Escrever casos de teste por problema e por linguagem seria um pesadelo de manutenção. A saída é escrever um único conjunto por problema, executável em qualquer linguagem, com um formato padrão de serialização e um harness de teste por linguagem, que desserializa a entrada, passa ao código do usuário e compara com a saída esperada.
Para uma árvore binária, por exemplo, a análise serializa a entrada em um array por travessia em ordem de nível (BFS), e cada linguagem precisa de um harness capaz de reconstruí-la antes de chamar a solução do usuário.
08 · FECHAMENTO
Simplicidade com caminho para crescer
A fonte descreve como ideal um sistema bem simples, sem excesso de engenharia, mas com um caminho claro para escalar se for preciso. Para nível sênior, espera discutir prós e contras entre contêiner, máquina virtual e função serverless e justificar a escolha, além de sair do modo de desenhar caixas e explicar como rodar de fato os casos de teste.
Perguntas frequentes
O que é Juiz online (LeetCode)?
Projeto de entrevista clássico. Pontos centrais: sandbox, agendamento de jobs e rankings e leaderboards.
O que o design de Juiz online (LeetCode) cobre?
Ele está organizado nestas seções: Um juiz online em escala modesta, Entidades, endpoints e arquitetura simples, Onde rodar código desconhecido, Endurecendo o contêiner, Picos de submissões: fila e contêineres e Ranking ao vivo sem derrubar o banco.
Quanto tempo leva Juiz online (LeetCode) e qual é o nível?
Leva cerca de 14 minutos para ler e tem nível Pleno.
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.