fix(heartbeat): evitar versoes desnecessarias comparando dados antes de atualizar

- Compara inventory/metrics com valores atuais antes de incluir no patch
- Usa JSON.stringify para comparacao eficiente de objetos
- Filtra campos de metadata que realmente mudaram
- Evita criacao de versoes quando heartbeat envia dados identicos

Isso previne o acumulo de versoes no Convex que causava OOM.

Reducao de memoria apos limpeza manual:
- DB: 450MB -> 16MB (96%)
- RAM: 7GB -> 189MB (97%)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rever-tecnologia 2025-12-10 10:21:35 -03:00
parent 178c7d7341
commit b6f69d7046

View file

@ -854,20 +854,44 @@ export const heartbeat = mutation({
await ctx.db.insert("machineHeartbeats", { machineId: machine._id, lastHeartbeatAt: now }) await ctx.db.insert("machineHeartbeats", { machineId: machine._id, lastHeartbeatAt: now })
} }
// 2. Preparar patch de metadata (se houver mudancas) // 2. Preparar patch de metadata (se houver mudancas REAIS)
// IMPORTANTE: So incluimos no patch se os dados realmente mudaram
// Isso evita criar versoes desnecessarias do documento machines
const metadataPatch: Record<string, unknown> = {} const metadataPatch: Record<string, unknown> = {}
const currentMetadata = (machine.metadata ?? {}) as Record<string, unknown>
if (args.metadata && typeof args.metadata === "object") { if (args.metadata && typeof args.metadata === "object") {
Object.assign(metadataPatch, args.metadata as Record<string, unknown>) // Filtrar apenas campos que realmente mudaram
} const incomingMeta = args.metadata as Record<string, unknown>
const remoteAccessSnapshot = metadataPatch["remoteAccessSnapshot"] for (const key of Object.keys(incomingMeta)) {
if (remoteAccessSnapshot !== undefined) { if (key !== "inventory" && key !== "metrics" && key !== "remoteAccessSnapshot") {
delete metadataPatch["remoteAccessSnapshot"] if (JSON.stringify(incomingMeta[key]) !== JSON.stringify(currentMetadata[key])) {
metadataPatch[key] = incomingMeta[key]
}
}
}
} }
const remoteAccessSnapshot = (args.metadata as Record<string, unknown> | undefined)?.["remoteAccessSnapshot"]
// Inventory: so incluir se realmente mudou
if (args.inventory && typeof args.inventory === "object") { if (args.inventory && typeof args.inventory === "object") {
metadataPatch.inventory = mergeInventory(metadataPatch.inventory, args.inventory as Record<string, unknown>) const currentInventory = currentMetadata.inventory as Record<string, unknown> | undefined
const newInventoryStr = JSON.stringify(args.inventory)
const currentInventoryStr = JSON.stringify(currentInventory ?? {})
if (newInventoryStr !== currentInventoryStr) {
metadataPatch.inventory = mergeInventory(currentInventory, args.inventory as Record<string, unknown>)
}
} }
// Metrics: so incluir se realmente mudou
if (args.metrics && typeof args.metrics === "object") { if (args.metrics && typeof args.metrics === "object") {
metadataPatch.metrics = args.metrics as Record<string, unknown> const currentMetrics = currentMetadata.metrics as Record<string, unknown> | undefined
const newMetricsStr = JSON.stringify(args.metrics)
const currentMetricsStr = JSON.stringify(currentMetrics ?? {})
if (newMetricsStr !== currentMetricsStr) {
metadataPatch.metrics = args.metrics as Record<string, unknown>
}
} }
// 3. Verificar se ha mudancas reais nos dados que justifiquem atualizar o documento machines // 3. Verificar se ha mudancas reais nos dados que justifiquem atualizar o documento machines