Métricas de negócio no Prometheus: KPIs com prom-client
Métricas técnicas dizem que o servidor está vivo; métricas de negócio dizem se o produto está saudável. Taxa de conversão caindo? Pedidos parando? Churn aumentando? Essas perguntas têm resposta quando você instrumenta o código de negócio com os mesmos counters e gauges que usa para monitorar a API.
Definir os KPIs do produto como métricas
Mapear os eventos importantes de negócio para tipos de métrica:
// lib/metricas-negocio.ts — KPIs instrumentados com prom-client:
import { Counter, Gauge, Histogram } from 'prom-client'
import { registry } from './registry'
// ── Aquisição ──────────────────────────────────────────────────────
export const cadastrosTotal = new Counter({
name: 'negocio_cadastros_total',
help: 'Total de novos usuários cadastrados',
labelNames: ['canal', 'plano'], // canal: 'google_oauth', 'email', 'convite'
registers: [registry],
})
export const loginTotal = new Counter({
name: 'negocio_logins_total',
help: 'Total de logins realizados',
labelNames: ['metodo', 'sucesso'], // metodo: 'senha', 'google', 'github'
registers: [registry],
})
// ── Ativação / Engajamento ─────────────────────────────────────────
export const usuariosAtivos = new Gauge({
name: 'negocio_usuarios_ativos_gauge',
help: 'Usuários com sessão ativa agora',
registers: [registry],
})
export const acoesRealizadas = new Counter({
name: 'negocio_acoes_total',
help: 'Ações realizadas pelos usuários',
labelNames: ['tipo_acao', 'plano_usuario'],
registers: [registry],
})
// ── Receita ────────────────────────────────────────────────────────
export const receitaTotal = new Counter({
name: 'negocio_receita_brl_total',
help: 'Receita acumulada em BRL',
labelNames: ['plano', 'periodo', 'metodo_pagamento'],
registers: [registry],
})
export const assinaturas = new Gauge({
name: 'negocio_assinaturas_ativas',
help: 'Total de assinaturas ativas por plano',
labelNames: ['plano'],
registers: [registry],
})
// ── Retenção ───────────────────────────────────────────────────────
export const cancelamentos = new Counter({
name: 'negocio_cancelamentos_total',
help: 'Total de assinaturas canceladas',
labelNames: ['plano', 'motivo'],
registers: [registry],
})
// ── Conversão ─────────────────────────────────────────────────────
export const funil = new Counter({
name: 'negocio_funil_etapa_total',
help: 'Eventos de funil de conversão',
labelNames: ['etapa'], // 'landing', 'cadastro', 'trial', 'pagamento', 'ativo'
registers: [registry],
})Instrumentar os fluxos de negócio
Integrar as métricas nos handlers críticos da aplicação:
// handlers/auth.ts — instrumentar fluxo de cadastro e login:
app.post('/api/auth/register', async (req, res) => {
const usuario = await criarUsuario(req.body)
// Funil de conversão:
funil.inc({ etapa: 'cadastro' })
cadastrosTotal.inc({
canal: req.body.provider ?? 'email',
plano: usuario.plano ?? 'free',
})
res.status(201).json(usuario)
})
// handlers/pagamento.ts — instrumentar conversão de pagamento:
app.post('/api/assinaturas/checkout', autenticar, async (req, res) => {
const assinatura = await processarPagamento(req.body, req.usuario)
if (assinatura.status === 'ativo') {
// Receita:
receitaTotal.inc({
plano: assinatura.plano,
periodo: assinatura.periodo, // 'mensal', 'anual'
metodo_pagamento: assinatura.metodoPagamento,
}, assinatura.valor)
// Funil de conversão:
funil.inc({ etapa: 'pagamento' })
// Atualizar gauge de assinaturas ativas:
assinaturas.inc({ plano: assinatura.plano })
}
res.json(assinatura)
})
// handlers/cancelamento.ts:
app.post('/api/assinaturas/cancelar', autenticar, async (req, res) => {
const motivo = req.body.motivo ?? 'sem_motivo'
const assin = await cancelarAssinatura(req.usuario.userId)
cancelamentos.inc({ plano: assin.plano, motivo })
assinaturas.dec({ plano: assin.plano })
res.json({ status: 'cancelado' })
})Sincronizar métricas de gauge com o banco
Manter gauges atualizados consultando o banco periodicamente:
// Problema: gauges podem ficar desatualizados se o processo reiniciar.
// Solução: sincronizar com o banco a cada N minutos.
import { db } from './db'
async function sincronizarMetricasNegocio() {
// Buscar contagens atuais do banco:
const [assinaturasAtivas] = await db.query<{ plano: string; total: number }[]>(
`SELECT plano, COUNT(*) as total
FROM assinaturas
WHERE status = 'ativo'
GROUP BY plano`
)
// Resetar e recarregar os gauges:
for (const { plano, total } of assinaturasAtivas) {
assinaturas.set({ plano }, Number(total))
}
// Usuários ativos na última hora:
const [{ count }] = await db.query(
"SELECT COUNT(DISTINCT usuario_id) as count FROM sessoes WHERE updated_at > NOW() - INTERVAL '1 hour'"
)
usuariosAtivos.set(Number(count))
}
// Sincronizar na inicialização e a cada 5 minutos:
sincronizarMetricasNegocio()
setInterval(sincronizarMetricasNegocio, 5 * 60 * 1000)
// Coletor assíncrono (alternativa mais elegante):
// O prom-client suporta coletores que executam lógica assíncrona:
import { Gauge } from 'prom-client'
new Gauge({
name: 'negocio_pedidos_pendentes',
help: 'Pedidos aguardando processamento',
async collect() {
const [{ count }] = await db.query(
"SELECT COUNT(*) FROM pedidos WHERE status = 'pendente'"
)
this.set(Number(count))
},
registers: [registry],
})Dashboards de negócio no Grafana
Criar painéis de KPIs para o time de produto e gestão:
# Queries PromQL para dashboard de negócio:
# Taxa de cadastros (por canal) nos últimos 30 minutos:
sum by (canal) (
rate(negocio_cadastros_total[30m])
) * 60 * 60 # converter para por hora
# Receita acumulada hoje:
increase(negocio_receita_brl_total[24h])
# Receita por plano (donut chart):
sum by (plano) (
increase(negocio_receita_brl_total[24h])
)
# Taxa de conversão: cadastros → pagamento (últimas 24h):
increase(negocio_funil_etapa_total{etapa="pagamento"}[24h])
/
increase(negocio_funil_etapa_total{etapa="cadastro"}[24h]) * 100
# Churn rate (cancelamentos / assinaturas ativas):
sum(rate(negocio_cancelamentos_total[7d])) * 24 * 7
/
sum(negocio_assinaturas_ativas) * 100
# Assinaturas ativas por plano (stat panel):
sum by (plano) (negocio_assinaturas_ativas)
# Dicas de organização:
# - Usar variáveis de template para filtrar por período
# - Separar dashboards: Técnico (DevOps) vs Produto (CEO/PM)
# - Dashboard "Executive Summary" com os 4-5 KPIs principais em stat panels
# - Threshold coloridos: verde > meta, amarelo = atenção, vermelho = alertaAlertas de negócio críticos
Detectar anomalias no produto antes que o cliente reclame:
# alerts/negocio.yml:
groups:
- name: negocio
rules:
# Zero pedidos nos últimos 15 minutos (checkout pode estar quebrado):
- alert: ZeroPedidos
expr: |
rate(negocio_pedidos_criados_total[15m]) == 0
for: 15m
labels:
severity: critical
annotations:
summary: "Nenhum pedido nos últimos 15 minutos"
description: "Possível problema no fluxo de checkout. Verificar logs."
# Taxa de erro de pagamento acima de 10%:
- alert: AltaTaxaErroPagamento
expr: |
rate(negocio_pagamentos_total{status="falhou"}[30m])
/ rate(negocio_pagamentos_total[30m]) * 100 > 10
for: 5m
labels:
severity: critical
annotations:
summary: "Taxa de falha de pagamento acima de 10%"
description: "Taxa atual: {{ $value | printf "%.1f" }}%. Verificar integração com gateway."
# Pico de cancelamentos (possível problema de qualidade/cobrança):
- alert: PicoCancelamentos
expr: |
rate(negocio_cancelamentos_total[1h]) > 3
for: 15m
labels:
severity: warning
annotations:
summary: "Taxa de cancelamento acima do normal"
# Queda brusca de usuários ativos (> 30% em 1h):
- alert: QuedaUsuariosAtivos
expr: |
negocio_usuarios_ativos_gauge
< negocio_usuarios_ativos_gauge offset 1h * 0.7
for: 15m
labels:
severity: warning
annotations:
summary: "Queda de >30% em usuários ativos na última hora"$ runstack deploy --plan starter
Não quer configurar manualmente?
Não quer configurar manualmente? Implante o VPS para Node.js em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.
Perguntas frequentes
Conteúdos relacionados