Pular para o conteúdo

Papers

Dapper, a Large-Scale Distributed Systems Tracing Infrastructure

Benjamin H. Sigelman, Luiz André Barroso, Mike Burrows, Pat Stephenson, Manoj Plakal, Donald Beaver, Saul Jaspan, Chandan Shanbhag · Google Technical Report, 2010

O problema: um pedido, centenas de serviços, visibilidade zero

No Google do fim dos anos 2000, um único pedido de usuário (por exemplo, uma busca) podia atravessar dezenas ou centenas de serviços diferentes, escritos por times distintos, rodando em milhares de máquinas espalhadas por vários datacenters. Cada serviço individualmente tinha seus próprios logs e métricas, mas ninguém conseguia responder a uma pergunta simples: por que ESTE pedido específico demorou 2 segundos, e em qual dos serviços intermediários o tempo foi gasto? Olhar métricas agregadas por serviço não revelava problemas que só apareciam em combinações específicas de chamadas, e correlacionar logs manualmente entre times era inviável em produção contínua, onde a topologia do sistema muda o tempo todo. Faltava uma forma de reconstruir, depois do fato, o caminho completo que um pedido percorreu através do sistema distribuído — quem chamou quem, em que ordem, e quanto tempo cada etapa consumiu. Esse é o vácuo que o artigo do Dapper descreve como motivação central: sistemas anteriores como Pinpoint, Magpie e X-Trace já haviam explorado ideias parecidas, mas o desafio do Google era fazer isso funcionar de forma transparente e barata na escala de toda a infraestrutura de produção, sem exigir que cada equipe de desenvolvimento instrumentasse manualmente seu próprio código.

A ideia central: árvores de trace, spans e propagação automática de contexto

O Dapper representa o caminho de um pedido como uma árvore de causalidade chamada trace, identificada por um ID único. Cada nó dessa árvore é um span: uma unidade de trabalho com nome legível, um identificador próprio e o identificador do seu span pai, de modo que as arestas entre spans reconstroem quem chamou quem. Um span registra o intervalo de tempo em que aquele trabalho ocorreu (por exemplo, uma chamada RPC específica) e pode carregar anotações adicionais que a aplicação insere voluntariamente com informações de negócio. A parte crucial do design é como esse contexto — qual trace e qual span pai um determinado trecho de código pertence — é propagado sem que o desenvolvedor precise fazer nada: o Dapper instrumenta um pequeno número de bibliotecas comuns (a camada de RPC, threading, controle de fluxo) usadas por praticamente todo serviço do Google, e é essa instrumentação compartilhada que carrega o contexto de trace através de threads, callbacks assíncronos e chamadas de rede. Como resultado, a maior parte da árvore de um trace é montada automaticamente; anotações manuais são um complemento opcional, não um requisito. O artigo reporta que a maioria dos spans e a grande maioria dos traces coletados continha ao menos uma anotação específica de aplicação, mostrando que os times usavam esse mecanismo voluntário quando útil, mesmo sem serem obrigados.

O trade-off central: amostragem agressiva em troca de baixíssimo overhead

Rastrear cada pedido em detalhe teria um custo de CPU, memória e rede alto demais para rodar continuamente em toda a frota do Google — e um dos três objetivos de design explícitos do artigo era justamente baixo overhead, ao lado de transparência para a aplicação e implantação ubíqua em escala muito grande. A solução foi amostrar: em vez de gravar todo trace, o Dapper decide, cedo no ciclo de vida do pedido, se aquele trace inteiro será mantido ou descartado, e essa decisão vale para a árvore inteira, não span por span, preservando a coerência da amostra. As primeiras versões usavam uma taxa fixa (uma fração pequena e constante dos pedidos); versões posteriores passaram a um esquema adaptativo, parametrizado por uma taxa desejada de traces amostrados por unidade de tempo em vez de uma probabilidade uniforme, o que evita amostrar demais em serviços de tráfego altíssimo e de menos em serviços de baixo tráfego. O argumento central do artigo para justificar essa perda deliberada de completude é que, em sistemas da escala do Google, um padrão de execução problemático que ocorre uma vez tende a ocorrer milhares de vezes — então não é preciso capturar cada ocorrência para detectar o problema. Em contrapartida, o Dapper garante gravação em disco de forma assíncrona e em lote, e o próprio artigo reporta consumo de CPU e de banda de rede muito baixos mesmo nos piores casos medidos em produção.

Legado: o ancestral comum dos rastreadores distribuídos de hoje

O Dapper foi um relatório técnico interno do Google publicado em 2010, descrevendo um sistema que já rodava em produção havia mais de dois anos, e não um produto aberto ao público — mas suas ideias se espalharam rapidamente pela indústria. Poucos anos depois de sua publicação, o Twitter lançou o Zipkin como um rastreador distribuído de código aberto explicitamente inspirado no modelo de traces e spans do Dapper, e mais tarde o Uber lançou o Jaeger seguindo a mesma linhagem conceitual. Esses dois sistemas, por sua vez, influenciaram o esforço de padronização que resultou primeiro no OpenTracing e, depois, na fusão que deu origem ao OpenTelemetry, hoje um padrão amplamente adotado para instrumentação de observabilidade em sistemas de nuvem. O vocabulário que praticamente toda ferramenta de tracing usa hoje — trace ID, span, span pai, span filho, anotações/tags, propagação de contexto entre serviços — vem diretamente do modelo proposto pelo Dapper. Mesmo a decisão de projeto de amostrar traces em vez de capturar tudo, motivada originalmente pela escala do Google, se tornou prática padrão em sistemas de tracing modernos que lidam com alto volume de tráfego, ainda que hoje existam estratégias de amostragem mais sofisticadas do que as descritas no artigo original.

Veja as ideias em ação

Pratique na escola de System Design os desenhos que usam este paper.