Arquitetura real · Sênior
Dropbox Nucleus: motor de sync reescrito em Rust
Arquitetura real em produção. Pontos centrais: testes determinísticos.
Resposta curta
Arquitetura real em produção. Pontos centrais: testes determinísticos. Um sync engine roda no computador do usuário e coordena o upload e o download dos arquivos para um sistema de arquivos remoto. Nucleus é o codinome do novo motor, reconstruído do zero para o cliente desktop ao longo de quatro anos.
01 · CONTEXTO
O que um sync engine faz e por que é difícil
Um sync engine roda no computador do usuário e coordena o upload e o download dos arquivos para um sistema de arquivos remoto. Nucleus é o codinome do novo motor, reconstruído do zero para o cliente desktop ao longo de quatro anos.
A escala é grande: o Dropbox tem centenas de bilhões de arquivos, trilhões de revisões e exabytes de dados de clientes. Além disso, clientes podem ficar offline por longos períodos e reconciliar as mudanças na volta, o que torna partições de rede operação normal em vez de exceção.
Um exemplo mostra como a durabilidade vai além de não corromper o servidor. O motor legado representava um move como um par de delete no local antigo e add no novo. Se o delete passasse e o add não, o usuário veria o arquivo sumido no servidor e nos outros dispositivos, embora só o tivesse movido localmente.
02 · POR QUE REESCREVER
Da dívida de modelo de dados ao Nucleus
Reescrever do zero tem má fama: Joel Spolsky chamou isso de maior erro estratégico que uma empresa de software pode cometer. O próprio Dropbox diz que a reescrita foi muito difícil e que só foi uma ótima ideia pelo cuidado com que o processo foi conduzido.
A causa mais importante dos problemas do motor legado era o modelo de dados, que nasceu para um mundo sem compartilhamento, com arquivos sem identificador estável entre moves. Refatorar sozinho não muda o modelo de dados fundamental, e as melhorias de desempenho no uso de memória, como aumentar o número de arquivos gerenciados, continuaram elusivas.
| Aspecto | Motor legado (Classic) | Nucleus |
|---|---|---|
| Identidade dos nós | Indexados pelo caminho completo | Move é uma atualização nos atributos do nó |
| O que se persiste | O trabalho pendente de cada arquivo ou pasta | Observações do estado |
| Concorrência | Locks globais para coordenar componentes | Quase todo o código em uma única thread |
| Estados do protocolo | Metadados de um arquivo podiam chegar antes do diretório pai | Mesma visão do remoto antes de considerar uma mutação |
03 · MODELO DE DADOS
Três árvores em vez de trabalho pendente
O motor legado persistia o trabalho pendente de cada arquivo ou pasta. O Nucleus persiste observações: mantém três árvores, cada uma consistente em si, das quais o comportamento correto de sync pode ser derivado.
Duas árvores descrevem o mundo real: a remota, com o estado mais recente na nuvem, e a local, com o último estado observado em disco.
A árvore sincronizada guarda o último estado conhecido como totalmente sincronizado entre as duas e pode ser vista como uma base de merge, como no controle de versão.
O planejador recebe as três árvores como entrada e produz operações para convergi-las de forma incremental.
Se a árvore local coincide com a base de merge, um arquivo presente só na remota foi adicionado na nuvem. Sem a árvore sincronizada não seria possível distinguir isso de uma exclusão local.
O objetivo do sistema é convergir as três árvores para o mesmo estado, e isso vira uma regra testável: quaisquer que sejam as árvores iniciais, elas devem convergir.
Identificar nós por um id único também muda o move. No legado, os nós eram indexados pelo caminho completo; no Nucleus, um move é apenas uma atualização nos atributos do nó movido, e uma invariante testada é que qualquer pasta movida é visível em exatamente um lugar. Outra invariante, de consistência: no banco, nenhum arquivo ou pasta existe, nem transitoriamente, sem um diretório pai. Esse é o princípio de projetar para eliminar estados inválidos.
No caso de duas pastas movidas offline que formariam um ciclo, o motor legado duplicava cada diretório. O Nucleus mantém as pastas originais, e a ordem final depende de qual motor envia o seu move primeiro.
04 · CONCORRÊNCIA E PROTOCOLO
Uma thread de controle e um protocolo estrito
No motor legado, o agendamento ficava a cargo do SO, e isso tornava os testes de integração não reproduzíveis. Os componentes podiam criar threads livremente e a coordenação usava uma série de locks globais.
Quase todo o código do Nucleus roda em uma única thread de controle, o que reduz o que o desenvolvedor precisa considerar ao adicionar uma funcionalidade.
A thread de controle é projetada para ser totalmente determinística quando entradas e decisões de agendamento são fixas.
O que não fica na thread de controle é delegado: IO de rede para um event loop, hashing para um pool de threads e IO de filesystem para uma thread dedicada.
O cliente conversa com o servidor por um protocolo que garante a mesma visão do sistema de arquivos remoto antes de considerar uma mutação.
No servidor, o backend inclui um banco de metadados e o armazenamento de conteúdo dos arquivos, entre outros serviços.
O protocolo cliente-servidor foi redesenhado com consistência forte: arquivos e pastas aceitam moves atômicos independentes do tamanho da subárvore, e qualquer discrepância entre a visão do cliente e a do servidor é um bug.
O Nucleus foi escrito em Rust, que permite codificar invariantes no sistema de tipos e fazer o compilador checá-las.
05 · TESTES DETERMINÍSTICOS
Testes aleatórios que sempre se reproduzem
A regra é que todos os frameworks de teste aleatório sejam totalmente determinísticos e fáceis de reproduzir. Cada execução gera uma seed, usa um gerador pseudoaleatório com essa seed para todas as decisões aleatórias e, se o teste falha, imprime a seed.
Toda noite rodam dezenas de milhões de execuções aleatórias. Quando uma regressão aparece, o CI cria automaticamente uma tarefa de acompanhamento para cada seed que falha. Para manter a garantia, até o HashMap padrão do Rust foi trocado por um hasher determinístico. O Dropbox também roda milhões de cenários por dia em sua infraestrutura de testes.
| Aspecto | CanopyCheck | Trinity |
|---|---|---|
| Alvo | Identificar bugs no planejador | Descobrir corridas (races) desse tipo antes do deploy |
| Como roda | Finge que cada operação teve sucesso, atualizando as árvores | Agita o sistema com falhas, reordenação de respostas e crashes |
| Minimização | Busca a menor entrada que reproduz a falha | Não consegue minimizar tão facilmente |
O CanopyCheck embaralha o conjunto de operações para verificar que a ordem não importa, e impõe invariantes adicionais além da convergência. Uma delas: um arquivo presente só na árvore remota deve existir nas três árvores ao fim do teste. Foi o CanopyCheck que expôs o bug do ciclo de diretórios Archives/Drafts/January.
O Trinity é essencialmente um executor customizado do future que representa o Nucleus, e assim agenda a si mesmo e ao motor na mesma thread.
O filesystem nativo é trocado por um mock em memória, o que permite cerca de dez vezes mais execuções de teste.
O backend do servidor é trocado por um mock em Rust, o que permite reordenar, atrasar e falhar qualquer RPC.
O relógio também é simulado: o Trinity pode interceptar um timeout, avançar o tempo e disparar o timeout quando quiser.
Com tudo sob controle, o Trinity modifica os sistemas de arquivos local e remoto, reordena respostas, injeta falhas de filesystem e de rede e simula crashes.
Se uma execução falha, a seed é impressa e o CI abre uma tarefa de acompanhamento para ela.
Um exemplo de corrida que o Trinity busca: o motor apaga um arquivo e sobrescreve a mudança recente do usuário. O Trinity ajuda a descobrir corridas desse tipo antes do deploy.
06 · LIMITES E LIÇÕES
O que os testes não cobrem
Simular tanto cobra um preço. Rodar o Trinity contra o filesystem nativo custa cerca de dez vezes em desempenho, e o Trinity não consegue reiniciar a máquina no meio do teste, então não valida o uso de fsync para durabilidade em crash.
Há também risco alto de o comportamento do mock do servidor se afastar da realidade. Para testar o protocolo existe uma suíte separada, o Heirloom, que fala com um servidor Dropbox real e roda cerca de cem vezes mais lento que o Trinity. Além disso, o Trinity não consegue minimizar casos de teste que falham tão facilmente quanto o CanopyCheck.
O alvo da reescrita teve mais de uma década para se estabilizar, e era preciso evitar regredir mais de dez anos de correções. Foram os frameworks determinísticos que permitiram fazer isso com segurança.
Perguntas frequentes
O que é Dropbox Nucleus: motor de sync reescrito em Rust?
Arquitetura real em produção. Pontos centrais: testes determinísticos.
O que o design de Dropbox Nucleus: motor de sync reescrito em Rust cobre?
Ele está organizado nestas seções: O que um sync engine faz e por que é difícil, Da dívida de modelo de dados ao Nucleus, Três árvores em vez de trabalho pendente, Uma thread de controle e um protocolo estrito, Testes aleatórios que sempre se reproduzem e O que os testes não cobrem.
Quanto tempo leva Dropbox Nucleus: motor de sync reescrito em Rust e qual é o nível?
Leva cerca de 17 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.