Pular para o conteúdo

Observabilidade e linhagem

As sete perguntas, métricas com percentil, tracing aninhado e de onde veio cada número.

Observabilidade é conseguir responder, sem abrir o código:

  • executou?
  • quanto tempo demorou?
  • quantos registros processou?
  • quantos falharam?
  • qual etapa falhou?
  • quando?
  • qual versão estava rodando?

Log sozinho responde a primeira e a sexta. As outras cinco exigem número — e é isso que separa observabilidade de logging.

O painel#

dataforge
adopt Arcane.Observar as O

p := O.painel("etl-vendas", "2.1.0")

O.contar(p, "linhas_lidas", 40000)      // só sobe
O.medir(p, "latencia_ms", 12.4)         // guarda a distribuição
O.marcar(p, "versao_do_esquema", 7)     // o último vale

out O.relatorio(p)

A média esconde#

text
  medida                         p50       p95       p99       máx
  latencia_ms                  2.000     2.000    40.000    40.000

Um pipeline com média de 3,9s e p99 de 40s tem um problema que a média nunca mostra — e é o p99 que o usuário sente. medir guarda a distribuição e devolve p50, p95 e p99.

O cálculo é interpolação linear — a mesma de numpy.percentile. Não é detalhe: um p99 calculado de outro jeito daria um número diferente do painel que a equipe já olha, e ninguém saberia qual acreditar.

Tracing: onde o tempo foi#

dataforge
extrair := O.abrir(p, "extrair")
// …
O.fechar(p, extrair)

transformar := O.abrir(p, "transformar")
limpar := O.abrir(p, "limpar", transformar)      // aninhado
O.fechar(p, limpar)
O.fechar(p, transformar)
text
    transformar                 0.048s  60.3% ██████████████
      enriquecer                  0.035s  44.4% ██████████
      limpar                      0.013s  15.9% ███
    extrair                     0.025s  31.7% ███████
  ✗ carregar                    0.006s   8.0% █

carregar levou 40s não ajuda. Dos 40s, 38 foram no INSERT resolve — e é o aninhamento que mostra isso.

A forma que não deixa trecho aberto#

dataforge
O.cronometrar(p, "carregar", lambda => carregar(dados))

Mesmo quando a ação estoura, o trecho é fechado — marcado como falha, e com a mensagem. E resumo avisa se sobrou trecho aberto no fim, que é quase sempre um ensure que faltou.

Linhagem: de onde veio esse número#

dataforge
O.derivar(p, "vendas_bruto",  ["api_erp"], "extração diária")
O.derivar(p, "vendas_prata",  ["vendas_bruto"], "limpeza e deduplicação")
O.derivar(p, "painel_diario", ["vendas_prata", "metas"], "agregação por dia")

Quando um número no painel está errado, a pergunta não é onde está o bug: é de onde veio esse número. Sem linhagem registrada, a resposta sai de ler o código de trás para a frente — e o código mudou desde que aquele número foi calculado.

dataforge
O.origem(p, "painel_diario")
//   vendas_prata → painel_diario   (agregação por dia)
//   metas        → painel_diario   (agregação por dia)
//   vendas_bruto → vendas_prata    (limpeza e deduplicação)
//   api_erp      → vendas_bruto    (extração diária)

E a pergunta inversa, que é a mais cara#

dataforge
O.impacto(p, "vendas_bruto")
// ["vendas_prata", "painel_diario"]

Se eu mexer aqui, o que quebra? — a pergunta que trava refatoração em pipeline grande.

Sair#

dataforge
O.relatorio(p)         // tudo em texto, para o log
O.prometheus(p)        // o formato de exposição, para o coletor
O.salvar(p, "obs/execucao.json")
O.alertar(p, regras)

Alertas#

dataforge
regras := {
    "latencia_ms": {"acima": 10, "estatistica": "p99", "texto": "a cauda está longa"},
    "erros":       {"acima": 0, "texto": "houve falha"},
}

Expor no Kiln#

dataforge
route GET "/metricas":
    respond text O.prometheus(painel)