Por que Migrar um Pipeline de Métricas?
Quando você opera uma plataforma como a Airbnb, as métricas são o sistema nervoso da sua infraestrutura. Por anos, eles usaram StatsD com um sidecar Veneur — uma configuração que funcionava, mas mostrava a idade conforme a escala crescia. Os problemas eram clássicos: perda de pacotes UDP em alta vazão, tipos de histograma limitados e dependência de fornecedor.
A equipe decidiu ir all-in no open source: OpenTelemetry (OTLP) para instrumentação e Prometheus para armazenamento. Mas o abismo entre onde estavam e onde queriam chegar era enorme. Esta é a história de como eles preencheram essa lacuna — sem quebrar a produção.
A Estratégia de Dual-Write: Não Migre, Paralelize
Uma das lições mais práticas desta migração é a abordagem de dual-write. Em vez de um corte arriscado, a Airbnb habilitou sua biblioteca de métricas compartilhada para emitir tanto StatsD (para o pipeline antigo) quanto OTLP (para o novo OTel Collector) simultaneamente. Isso permitiu:
- Validar a correção comparando ambos os fluxos
- Expor gargalos de escala cedo (e eles encontraram grandes)
- Deixar os usuários migrarem dashboards e alertas no seu próprio ritmo
Dica prática: Se você está planejando uma migração de métricas, comece instrumentando a emissão dupla. É mais trabalho no início, mas economiza meses de apagão depois.
A Surpresa de Performance do OTLP
Enquanto a maioria dos serviços lidou bem com o dual-write, os emissores de maior volume (10K+ amostras/segundo por instância) encontraram um muro. A pressão de memória disparou, a atividade de GC aumentou e o heap cresceu. O culpado? Temporalidade cumulativa no SDK OTLP, que mantém estado completo para cada combinação de rótulo entre exportações.
A correção foi simples, mas cirúrgica: trocar esses serviços para temporalidade delta. O modo delta só rastreia mudanças entre exportações, reduzindo drasticamente a memória em processo. A troca? Lacunas nos dados se o serviço falhar no meio do intervalo. Para a maioria dos serviços, o modo cumulativo permaneceu; apenas os 1% principais emissores precisaram da troca.
# Exemplo: Configurando temporaldade delta para métricas de alta cardinalidade no SDK OTel Python
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import AggregationTemporalitySelector
# Forçar delta para instrumentos específicos
delta_selector = AggregationTemporalitySelector.delta_preferred()
provider = MeterProvider(
metric_readers=[...],
aggregation_temporality_selector=delta_selector
)
Agregação Streaming na Escala da Airbnb
Métricas brutas são caras. Um único pod emite milhares de combinações de rótulos (pod, hostname, região, etc.). Armazenar todos eles multiplicaria os custos em 10x. A solução: agregação streaming — remover rótulos de nível de instância antes do armazenamento.
A Airbnb avaliou várias opções (reescrita do Veneur, recording rules, m3aggregator, OTel Collector, Vector) e escolheu o vmagent da VictoriaMetrics. Por que o vmagent venceu:
| Característica | vmagent | Alternativas (OTel Collector, Vector, etc.) |
|---|---|---|
| Agregação streaming | ✅ Nativo | ❌ Não suportado ou experimental |
| Escalonamento horizontal via sharding | ✅ Configuração simples | ❌ Complexo ou ausente |
| Tamanho do código | ~10K LOC (fácil de modificar) | 50K-100K+ LOC |
| Documentação | Excelente | Variável |
A arquitetura usa duas camadas de vmagents: routers (sem estado, hash de métricas por rótulos) e aggregators (com estado, mantêm totais correntes). Esse design escalou para centenas de agregadores ingerindo mais de 100 milhões de amostras por segundo.
Conselho prático: Se você está construindo um pipeline de métricas em escala, não subestime o valor de uma base de código pequena e focada. As ~10K linhas do vmagent tornaram fácil para a Airbnb adicionar recursos personalizados como suporte a histogramas nativos e multitenência estilo Mimir — e contribuir de volta upstream.
O Problema dos Contadores Esparsos (e a Correção Zero Injection)
Após a migração, um bug desagradável surgiu: consultas PromQL usando rate() estavam consistentemente subcontando em comparação com o antigo sistema StatsD.
A causa raiz é um caso de borda conhecido na semântica do Prometheus. Quando um contador é criado e incrementado antes do Prometheus coletá-lo, o incremento inicial é perdido. Para contadores de alta taxa, isso é insignificante, mas para contadores esparsos (ex.: requisições por moeda por usuário por região — talvez 1 incremento por dia), cada incremento importa.
A Airbnb considerou várias soluções alternativas (pré-inicializar todos os contadores, usar logs, emitir gauges, PromQL hacky), mas todas eram impraticáveis. Em vez disso, eles construíram uma correção transparente: zero injection na camada de agregação.
Como Funciona o Zero Injection
Quando o agregador descarrega um contador pela primeira vez, ele injeta um zero sintético em vez do valor real. O incremento real é atrasado em um intervalo de descarga. Isso garante que o rate() do Prometheus veja uma linha de base adequada e não perca o primeiro incremento.
# Pseudocódigo simplificado para a lógica de zero injection
class SparseCounterAggregator:
def __init__(self):
self.first_flush = True
self.running_total = 0
def flush(self):
if self.first_flush:
self.first_flush = False
return 0 # injeta zero
else:
value = self.running_total
self.running_total = 0
return value
A troca: o primeiro incremento é atrasado em um intervalo de descarga (ex.: 10 segundos). Para contadores esparsos, isso é invisível. O benefício: todos os contadores são implicitamente inicializados em zero, correspondendo perfeitamente à semântica do Prometheus.
Por que isso importa: Essa correção é um ótimo exemplo de resolver um problema na camada certa. Em vez de empurrar complexidade para cada equipe escrevendo dashboards e alertas, a Airbnb corrigiu uma vez no pipeline de agregação.
Limitações e Cuidados
- Troca da temporalidade delta: Serviços usando modo delta perdem dados se falharem no meio do intervalo. Isso é aceitável para serviços de alta cardinalidade, mas não para contadores críticos.
- Atraso do zero injection: O primeiro incremento de qualquer contador é atrasado em um intervalo de descarga. Para alertas em tempo real em contadores novos, isso pode ser um problema.
- Personalizações do vmagent: As alterações da Airbnb no vmagent (histogramas nativos, multitenência) exigiram conhecimento em Go. Se você não se sente confortável modificando a base de código, pode enfrentar limitações.
- Complexidade do dual-write: Executar dois pipelines simultaneamente dobra a sobrecarga operacional durante a migração. Planeje uma transição de vários meses.
Qual é o Próximo Passo? Roteiro de Aprendizado
Se você está considerando uma migração similar, aqui está seu roteiro:
- Comece pequeno: Escolha um serviço com métricas de baixa cardinalidade. Faça dual-write OTLP + StatsD por uma semana. Valide que as contagens batem.
- Escalone: Mova para serviços de maior volume. Monitore memória e latência. Troque para temporalidade delta se necessário.
- Construa agregação: Implante vmagent (ou outro agregador streaming) e teste com um subconjunto de métricas.
- Lide com casos de borda: Implemente zero injection ou correções similares para contadores esparsos. Teste com dashboards reais.
- Corte: Quando estiver confiante, remova o antigo pipeline StatsD.
Para mais sobre construção de sistemas escaláveis, confira nosso artigo sobre Por que a Airbnb Construiu Seu Próprio Motor de Workflow Embutido. E se você está procurando uma maneira rápida de prototipar automação, veja como Construir um Agente Slack em Menos de 10 Minutos com a Nova Skill de Agente Slack da Vercel.
Baseado no post original do blog de engenharia por Eugene Ma e Natasha Aleksandrova na Airbnb. Todos os nomes de produtos, logotipos e marcas são propriedade de seus respectivos proprietários.
