Pular para o conteúdo principal

Debug e Observabilidade

🧠 O Objetivo

Entender o que cache está fazendo: hits? misses? deduplicação funcionando? Sem observabilidade, você tira conclusões erradas.

❓ Por que existe

  • Cache é "caixa preta" sem outputs
  • Hit rate baixo pode significar:
    • TTL muito baixo
    • Chave incorreta
    • Dados muito dinâmicos
  • Sem debugging, nunca você descobre

✅ Quando usar

  • Desenvolvimento
  • Produção (monitoramento)
  • Debug de performance

🚫 Quando NÃO usar

  • Logging excessivo em produção (mata performance)
  • Dados sensíveis nos logs

⚡ Exemplo mínimo funcional

import { createCache } from "flowcache";

const cache = createCache<any>({
defaultTTL: 10_000,
// Log cada evento (desenvolvimento)
onEvent: (event) => {
console.log(`[CACHE:${event.type}] ${event.key}`);
},
});

// Monitor a cada 5 segundos
setInterval(() => {
const stats = cache.stats();
const hitRate = stats.hits / (stats.hits + stats.misses) || 0;
console.log(`📊 Hit rate: ${(hitRate * 100).toFixed(1)}%`);
}, 5_000);

// Uso
cache.fetch("key1", async () => 1);
// Output: [CACHE:miss] key1
// [CACHE:set] key1

// Próxima chamada
cache.fetch("key1", async () => 1);
// Output: [CACHE:hit] key1

🧩 Exemplo real: Production Monitoring

import { createCache } from "flowcache";

type AppMetrics = {
hitRate: number;
missRate: number;
avgResponseTime: number;
pendingRequests: number;
cacheSize: number;
};

type CacheEvent = {
type: string;
key?: string;
timestamp: Date;
};

const cache = createCache<any>({
defaultTTL: 30_000,

onEvent: (event) => {
// Em produção: enviar para observabilidade (Datadog, NewRelic, etc)
if (process.env.NODE_ENV === "production") {
sendToMonitoring({
type: event.type,
key: event.key,
timestamp: new Date(),
});
} else {
console.log(`[${event.type}] ${event.key}`);
}
},

hooks: {
onHit: (key) => {
if (process.env.DEBUG_CACHE) {
console.log(`✅ HIT: ${key}`);
}
},
onMiss: (key) => {
if (process.env.DEBUG_CACHE) {
console.log(`❌ MISS: ${key}`);
}
},
},
});

// Monitoramento periódico
function monitorCache() {
const stats = cache.stats();
const totalRequests = stats.hits + stats.misses;
const hitRate = totalRequests > 0
? (stats.hits / totalRequests * 100).toFixed(1)
: "N/A";

const metrics: AppMetrics = {
hitRate: parseFloat(hitRate as string) || 0,
missRate: parseFloat((100 - parseFloat(hitRate as string)).toFixed(1)) || 100,
avgResponseTime: 0, // Calculado separadamente
pendingRequests: stats.pending,
cacheSize: stats.size,
};

return metrics;
}

// Emissão em tempo real
setInterval(() => {
const metrics = monitorCache();

if (process.env.NODE_ENV === "production") {
// Enviar pra Prometheus, CloudWatch, etc
sendMetrics({
"cache.hit_rate": metrics.hitRate,
"cache.miss_rate": metrics.missRate,
"cache.size": metrics.cacheSize,
"cache.pending": metrics.pendingRequests,
});
} else {
console.table(metrics);
}
}, 60_000); // A cada minuto

// Alertar se hit rate baixo
function checkCacheHealth() {
const stats = cache.stats();
const hitRate = stats.hits / (stats.hits + stats.misses);

if (hitRate < 0.5) { // < 50%
console.warn("⚠️ ALERT: Cache hit rate é baixo!");
console.warn(` Hit rate: ${(hitRate * 100).toFixed(1)}%`);
console.warn(` Causas possíveis:");
console.warn(` 1. TTL muito baixo (${cache.config().defaultTTL}ms)");
console.warn(` 2. Chaves incorretas (mudam a cada requisição)");
console.warn(` 3. Dados muito dinâmicos");
}

if (stats.pending > 100) {
console.warn("⚠️ ALERT: Muitas promises pendentes!");
console.warn(` Pending: ${stats.pending}");
console.warn(` Causas possíveis:");
console.warn(` 1. Upstream lento");
console.warn(` 2. Timeout insuficiente");
}
}

setInterval(checkCacheHealth, 30_000);

// Endpoint de debug
app.get("/api/admin/cache-debug", (req, res) => {
const stats = cache.stats();
res.json({
stats,
metrics: monitorCache(),
health: stats.hits / (stats.hits + stats.misses) > 0.5 ? "✅ OK" : "❌ PROBLEMA",
});
});

⚠️ Erros comuns

Erro 1: Logging demais em produção

// ❌ Perigoso: Cada event causa filesystem I/O
onEvent: (event) => {
console.log(`[CACHE] ${event.type} ${event.key}`); // Muito!
// 1000 events/s = 1000 logs/s = performance péssima
}

// ✅ Melhor: Batch ou amostrar
let eventCount = 0;
setInterval(() => {
if (eventCount > 100) {
console.log(`Cache: ${eventCount} events no último minuto`);
eventCount = 0;
}
}, 60_000);

onEvent: (event) => {
eventCount++;
}

Erro 2: Interpretar errado

// ❌ Errado: "Hit rate 50%, aumentar TTL"
// Pode ser:
// - Chave muda por timestamp
// - 1000 usuários, cache máximo 500 entradas
// - Dados muito dinâmicos

// ✅ Antes de mudar TTL:
// 1. Verificar chave (não tem Date.now()? timestamp?)
// 2. Verificar maxEntries vs número de dados únicos
// 3. Verificar se dados realmente são dinâmicos

Erro 3: Sem thresholds de alerta

// ❌ Sem alert:
setInterval(() => {
console.log(cache.stats()); // Imprime, mas não alerta
}, 60_000);

// ✅ Com alert:
setInterval(() => {
const stats = cache.stats();
const hitRate = stats.hits / (stats.hits + stats.misses);

if (hitRate < 0.3) {
sendAlert("Cache hit rate crítico: " + hitRate);
}
}, 60_000);

💡 Boas práticas

1. Desenvolvimento: Verbose logging

const cache = createCache({
onEvent: (event) => {
if (process.env.NODE_ENV === "development") {
console.log(`[CACHE:${event.type}] ${event.key}`);
}
},
});

2. Produção: Métricas agregadas

setInterval(() => {
const stats = cache.stats();
const metric = {
timestamp: new Date(),
hitRate: stats.hits / (stats.hits + stats.misses),
size: stats.size,
pending: stats.pending,
};

// Enviar para observabilidade
observability.gauge("cache.hit_rate", metric.hitRate);
observability.gauge("cache.size", metric.size);
}, 60_000);

3. Monitorar deduplicação

const stats = cache.stats();
const dedup = stats.pending; // Requisições dedup'd que estão aguardando

if (dedup > 50) {
console.log(`✅ Dedup funcionando: ${dedup} requests compartilhando 1 fetch`);
}

4. Debug de chave

// Para encontrar typo em chave:
const keys = new Set<string>();

onEvent: (event) => {
if (event.key) keys.add(event.key);
}

// Depois:
console.log("Cache keys:", Array.from(keys).sort());
// Procura por patterns estranhos

📊 Checklist de debugging

SintomaCausa provávelSolução
Hit rate < 30%Chave mudaRemover timestamp/random
Hit rate < 50%TTL baixoAumentar TTL
Hit rate < 70%maxEntries cheioAumentar ou diminuir dados
Pending alto (> 100)Upstream lentoAumentar timeout
Memory cresceSem dispose/sweepAdicionar sweepIntervalMs

🔗 Próximos passos