Artigos aprofundados sobre a tecnologia que molda o que vem a seguir.

Quando o Git Quebra: Lições de Monorepo em Escala

Escalar monorepos Git falha de formas previsíveis em altas taxas de merge. Lições de clientes sempre online e commits feitos no servidor.

Ilustração de robôs entregadores lotando um balcão de checkout em um vasto arquivo de caixas de documentos.
O Git assume que cada cliente carrega o arquivo inteiro. Essa premissa funciona bem, até que milhares de clientes apareçam por segundo.

Se você mantém um monorepo git, existe um número no seu futuro em que tudo começa a desmoronar. Para a Anthropic, esse número foi algo em torno de 30.000 merges por dia, um histórico de 100 GB na branch principal e algo na casa de 25 milhões de checkouts de CI por dia, o que, buscado de forma ingênua, dá mais de 600 petabytes de tráfego git. Todos os dias, sem exceção. A resposta deles foi escrever o próprio sistema de controle de versão em cinco meses. A sua não deveria ser essa. Mas os erros que eles cometeram no caminho são os mesmos que vejo times cometerem em uma fração dessa escala, então vamos repassá-los.

Erro um: tratar um fluxo centralizado como se fosse distribuído

A grande aposta de design do Git é a descentralização. Cada clone é um repositório completo: histórico completo, armazenamento completo de objetos, totalmente funcional offline. Essa aposta colocou o git no mundo, e para a maioria dos repositórios ela ainda está correta. Mas tem uma coisa que quase ninguém admite: quase ninguém usa o git dessa forma. Sua organização tem um remote autoritativo no GitHub ou no GitLab. Seus desenvolvedores fazem push e pull o tempo todo. Seus runners de CI estão, por definição, online. Você está rodando um sistema centralizado vestido de distribuído, e pagando os custos de um sistema distribuído: cada job de CI puxando um histórico que nunca vai ler, cada sandbox de agente materializando packfiles de que não precisa.

Esta é a primeira grande lição do redesenho da Anthropic. Quando eles deram um passo atrás, perceberam que seus clientes eram “agentes e jobs que estão online por definição”, então projetaram em torno de um servidor sempre acessível. O cliente foi deliberadamente simplificado: um cache local limitado, uma cópia de trabalho em disco, um observador do sistema de arquivos. O servidor responde ao blame, calcula diffs e executa merges. É a mesma conclusão a que o Google chegou com o Piper e a Meta com o Sapling, e não é uma posição contra o git. É o reconhecimento de que a arquitetura deve refletir a carga de trabalho real, e não a carga para a qual a ferramenta foi projetada vinte anos atrás. Já vi esse padrão em ferramentas de segurança também: times implantam uma arquitetura de agente com capacidade offline e depois a executam num ambiente onde offline simplesmente nunca acontece, e se perguntam por que a camada de sincronização é um pesadelo.

A conclusão para o seu time não é “construa um VCS”. É: audite suas premissas. Anote quais propriedades do git você realmente depende. Trabalho offline? Forks de verdade? Assinatura criptográfica de commits? Se a resposta honesta for “um remote só, sempre online, o CI faz tudo”, pare de otimizar para um fluxo distribuído que você não tem e comece a otimizar o custo de checkout e de consultas.

Erro dois: esconder a escala com espelhos e seeds

Toda equipe com repositório grande passa pelas mesmas fases de sofrimento. Primeiro, os shallow clones. Depois, os sparse checkouts e os promisor remotes. Em seguida, espelhos internos do git e, por fim, “seeds” de snapshot no armazenamento de objetos, para que os jobs de CI possam inicializar a partir de algo próximo da ponta da main. A Anthropic usou todos esses recursos e, no pico, chegou a várias centenas de espelhos internos. E o modo de falha é sempre o mesmo: as gambiarras não falham de forma ruidosa, elas apodrecem. Os seeds se distanciam da ponta a cada atualização. Clones parciais quebram o git blame e o git log --follow de maneiras confusas. O bisect de alguém retorna resultados errados em silêncio porque o histórico foi truncado. Ninguém percebe até a revisão do incidente.

Esse último ponto é o que me tira o sono. Bugs de correção em histórico parcial são exatamente o tipo de erro sutil que vive na distância entre o que você acha que o sistema garante e o que ele realmente faz. Um shallow clone não dá erro quando você pede blame além da sua profundidade; ele simplesmente devolve uma resposta parcial. Se o seu pipeline automático de revert confia nessa resposta, você construiu uma máquina que reverte a mudança errada com toda a confiança do mundo.

  • Shallow clones quebram ferramentas que dependem do histórico (blame, bisect, merge-base) de formas que falham em silêncio, não em voz alta.
  • Seeds de snapshot se afastam da ponta da main; quanto maior a sua taxa de merge, mais rápido é o afastamento, então a gambiarra se degrada exatamente quando a carga aumenta.
  • Frotas de espelhos multiplicam a superfície operacional: centenas de cópias com estado que podem atrasar, corromper ou ficar sem disco de forma independente.
  • Toda gambiarra que mexe na semântica do histórico precisa de um teste de regressão, ou vai morder você durante um incidente.

A correção na escala da Anthropic foi estrutural: não existe uma cópia totalmente materializada do repositório em lugar nenhum, nem no servidor, e tudo é endereçado por conteúdo e pode ser cacheado horizontalmente. Na sua escala, a correção é mais modesta: meça quantas máquinas guardam cópias completas ou quase completas do seu repositório, e seja honesto sobre o afastamento e os modos de falha de cada uma.

Erro três: fazer cada job de CI manter sua própria cópia do repositório

Vale a pergunta sobre a sua frota de CI: quantos desses jobs precisam de uma cópia de trabalho? A resposta da Anthropic, depois de construir o sistema, foi “muito menos do que supúnhamos”. Um job que precisa do diff entre um commit e seu merge base, do blame de um arquivo ou da lista de commits que tocam um caminho pode perguntar diretamente ao servidor via API. Sem clone, sem fetch, sem disco. Os pipelines automáticos de bisect e revert deles “não seriam práticos em escala” sem esse acesso programático barato, e acredito nisso, porque já vi times tentarem rodar esses mesmos pipelines sobre clones locais e se afogarem em tempo de fetch.

Esse princípio vai muito além do controle de código-fonte. Trate o checkout como a exceção cara, não como o padrão. Quando um job realmente precisa de arquivos, checkouts esparsos e virtualizados, que materializam só os diretórios solicitados ou até só os arquivos efetivamente lidos, reduzem o custo em ordens de grandeza. O protocolo de fetch da Anthropic abandonou a negociação do git: o cliente pede árvores e blobs imutáveis específicos, qualquer cache pode responder, e tudo se comporta como uma CDN em camadas, e não como um servidor inteligente calculando packfiles para cada cliente. Se você já perfilou um servidor git derretendo sob carga de CI, vai reconhecer por que isso importa: a geração de packfiles consome muita CPU justamente porque o protocolo pede ao servidor que calcule um delta em relação ao que cada cliente já tem.

Se seus clientes estão sempre online e existe uma única cópia autoritativa, cada clone local que você mantém é um cache pelo qual você paga preço cheio e que, no fim, ainda entrega semântica de cache.

Erro quatro: deixar o custo de um commit crescer com o tamanho do repositório

A decisão de engenharia mais interessante do design deles é o changeset. Um commit git aponta para uma árvore que representa o estado inteiro do repositório; o changeset deles registra apenas a lista de arquivos alterados em relação ao primeiro pai, mais ponteiros endereçados por conteúdo para o armazenamento de blobs, anexados a um write-ahead log. Sem packfiles para aceitar, sem locks, sem coordenação entre servidores. As refs, como branches e tags, foram deliberadamente deixadas em segundo plano, porque “estado global que todo servidor precisa concordar é um problema de escala iminente”. Mover uma ref é um compare-and-swap em uma única linha do Spanner, desacoplado da criação do commit.

Ilustração estilizada de pacotes em uma esteira alimentando um log luminoso e estruturas de dados derivadas.
Registre apenas o que mudou em um write-ahead log; derive todo o resto, como blame, histórico de caminhos e índices do grafo, de forma preguiçosa a partir dele.

A ideia complementar são os “dados derivados”: índices de blame, histórico de caminhos, skip lists do commit-graph, tudo calculado recursivamente a partir dos pais de cada changeset, por meio de um mecanismo genérico único. Calcule de forma antecipada ou preguiçosa, por tipo. Adicione novos tipos de índice sem tocar no cliente. É um padrão genuinamente bom para qualquer sistema baseado em eventos: já recomendei a mesma forma para pipelines de logs de auditoria, em que o log append-only é a verdade e toda estrutura de consulta é uma projeção reconstruível. No momento em que alguém mantém uma projeção à mão, fora do mecanismo, você já tem divergência.

Uma ressalva do ponto de vista de segurança: quem se preocupa com centralização tem um argumento que merece ser levado a sério. Um único sistema no servidor, onde moram commits, merges e consultas de histórico, é um alvo de alto valor, e agentes de IA com permissão de escrita em “o único repositório que governa tudo” concentram risco de forma perigosa. A Anthropic construiu junto um “sistema rico de IAM para controle de código-fonte”, e os detalhes importam mais do que as estruturas de dados. Se você centralizar, sua conta de raio de explosão muda: reserve orçamento para isolamento, credenciais por agente e auditoria desde o primeiro dia, não no terceiro trimestre. Times que rodam agentes com estado sobre infraestrutura compartilhada já conhecem bem essa dor.

Erro cinco: achar que 30.000 merges por dia significam 30.000 boas decisões por dia

A engenharia é impressionante. A pergunta em aberto é o que passa por ela. Um profissional resumiu sem rodeios: toda vez que ele para de revisar o código gerado por IA, “depois encontro porcaria mal feita. Nem toda revisão encontra porcaria, mas com frequência suficiente para que eu não consiga parar de revisar tudo”. Multiplique essa taxa de acerto por 30.000 merges por dia e você entende por que a Anthropic também precisou construir “formas inéditas de gerenciar e integrar grandes volumes de código revisado”: a superfície de revisão, a fila de integração, os pipelines automáticos de revert. O VCS não era o produto. O pipeline de integração era. O controle de código só virou gargalo depois que a capacidade de revisão já tinha virado gargalo antes dele.

Há também um detalhe sincero escondido nas histórias de guerra deles: engenheiros do projeto descreveram grande parte do próprio papel como dizer “não” ao agente de programação. O pipeline de ingestão que escreveram à mão funcionava, até que um modelo mais novo o reescreveu com 20x menos uso de memória e 4x mais rápido, mas chegar lá exigiu humanos com bom gosto decidindo o que aceitar. Isso bate com tudo que vi: a IA desloca o trabalho do engenheiro de escrever para revisar, e a vazão de revisão virou o recurso escasso. Já escrevemos sobre como cada camada de revisão deixa um time mais lento. O difícil é que, com código gerado por agentes, remover camadas de revisão também não é seguro. Você precisa de melhores ferramentas de revisão, não de menos revisão.

O que você deveria fazer de fato

Você não é a Anthropic. Não tem o orçamento de tokens deles, algo que eles mesmos admitem, e não tem 25 milhões de checkouts diários. Mas ferramentas de programação com IA estão empurrando as taxas de merge de todo mundo para cima, e os pontos de pressão chegam numa ordem previsível. Esta é a ordem de execução que eu seguiria, na Lei de Priya do menor arrependimento:

  1. Meça primeiro. Conte os checkouts diários, os bytes buscados por job de CI e quantas máquinas guardam cópias completas do repositório. Você provavelmente tem um problema de desperdício antes de ter um problema de escala.
  2. Elimine checkouts desnecessários. Leve perguntas de metadados (diffs, blame, ancestralidade) para APIs, como as do GitHub/GitLab ou um serviço interno enxuto, antes de comprar qualquer infraestrutura.
  3. Torne seguros os clones esparsos e parciais. Teste explicitamente seus ferramentas de bisect, blame e revert contra shallow clones, ou proíba-os na automação.
  4. Padronize o CI para buscas no servidor, com objetos imutáveis, sempre que possível, e trate frotas de espelhos como dívida técnica com juros compostos.
  5. Invista no pipeline de integração, não no formato do repositório. Capacidade de revisão, filas de merge e revert automatizado são onde o desenvolvimento em escala de agentes realmente quebra primeiro.
  6. Só então considere arquiteturas alternativas de VCS. O Sapling é open source e entrega a maior parte dos benefícios cliente-servidor sem que você precise escrever o seu próprio.

A lição mais profunda aqui não é sobre o git. É que toda ferramenta codifica premissas sobre sua carga de trabalho (humanos offline, um commit por vez, código escrito mais devagar do que é revisado), e essas premissas estão sendo violadas por agentes agora, na infraestrutura, na revisão e nas fronteiras de segurança. Os times que saem na frente não serão os que reconstruírem tudo do zero. Serão os que perceberem quais premissas quebraram primeiro e corrigirem exatamente essas.