fix(convex): prevent OOM by filtering large inventory fields

- Add INVENTORY_BLOCKLIST to filter 'software' and 'extended' fields
  from machine metadata (these fields can be 100KB+ each)
- Add compactMachineMetadata migration to clean existing large documents
- Preserve essential fields: metrics, postureAlerts, collaborator,
  inventory.os, cpu, memory, disks, network, services

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
esdrasrenan 2025-12-09 22:36:03 -03:00
parent 508f915cf9
commit cf28ad2ee4
2 changed files with 122 additions and 0 deletions

View file

@ -870,6 +870,122 @@ export const syncMachineCompanyReferences = mutation({
},
})
/**
* Migracao para compactar metadata de machines, removendo dados volumosos
* como inventory.software (lista de programas instalados) que podem ter
* centenas de KBs por maquina.
*
* Esta migracao preserva:
* - metrics (metricas de sistema)
* - postureAlerts (alertas de postura)
* - lastPostureAt (timestamp)
* - collaborator (email do colaborador)
* - inventory.os, inventory.cpu, inventory.memory, inventory.disks, inventory.network
* - inventory.services (lista de servicos - usado para alertas de postura)
*
* Remove:
* - inventory.software (lista completa de programas instalados - muito grande)
* - inventory.extended (dados estendidos do Linux - muito grande)
* - cpuWindow (janela de CPU - pode ser reconstruida)
*/
export const compactMachineMetadata = mutation({
args: {
tenantId: v.optional(v.string()),
limit: v.optional(v.number()),
dryRun: v.optional(v.boolean()),
},
handler: async (ctx, { tenantId, limit, dryRun }) => {
const effectiveDryRun = Boolean(dryRun)
const effectiveLimit = limit && limit > 0 ? Math.min(limit, 200) : 200
// Busca maquinas em lotes pequenos para evitar OOM
const machines = tenantId && tenantId.trim().length > 0
? await ctx.db
.query("machines")
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
.take(effectiveLimit)
: await ctx.db.query("machines").take(effectiveLimit)
let processed = 0
let compacted = 0
let bytesSavedEstimate = 0
for (const machine of machines) {
processed += 1
const metadata = machine.metadata
if (!metadata || typeof metadata !== "object") continue
const meta = metadata as Record<string, unknown>
const inventory = meta["inventory"]
const cpuWindow = meta["cpuWindow"]
// Verificar se precisa compactar
let needsCompact = false
const sizeBefore = JSON.stringify(meta).length
const newMeta: Record<string, unknown> = {}
// Preservar campos essenciais
if (meta["metrics"]) newMeta["metrics"] = meta["metrics"]
if (meta["postureAlerts"]) newMeta["postureAlerts"] = meta["postureAlerts"]
if (meta["lastPostureAt"]) newMeta["lastPostureAt"] = meta["lastPostureAt"]
if (meta["collaborator"]) newMeta["collaborator"] = meta["collaborator"]
if (meta["remoteAccessSnapshot"]) newMeta["remoteAccessSnapshot"] = meta["remoteAccessSnapshot"]
// Compactar cpuWindow para apenas ultimas 30 amostras (em vez de 120)
if (Array.isArray(cpuWindow) && cpuWindow.length > 30) {
newMeta["cpuWindow"] = cpuWindow.slice(-30)
needsCompact = true
} else if (cpuWindow) {
newMeta["cpuWindow"] = cpuWindow
}
// Compactar inventory - remover software e extended
if (inventory && typeof inventory === "object") {
const inv = inventory as Record<string, unknown>
const newInv: Record<string, unknown> = {}
// Preservar apenas campos essenciais do inventory
if (inv["os"]) newInv["os"] = inv["os"]
if (inv["cpu"]) newInv["cpu"] = inv["cpu"]
if (inv["memory"]) newInv["memory"] = inv["memory"]
if (inv["disks"]) newInv["disks"] = inv["disks"]
if (inv["network"]) newInv["network"] = inv["network"]
if (inv["services"]) newInv["services"] = inv["services"]
if (inv["bios"]) newInv["bios"] = inv["bios"]
if (inv["motherboard"]) newInv["motherboard"] = inv["motherboard"]
// Verificar se tinha software ou extended (campos volumosos)
if (inv["software"] || inv["extended"]) {
needsCompact = true
}
if (Object.keys(newInv).length > 0) {
newMeta["inventory"] = newInv
}
}
if (!needsCompact) continue
const sizeAfter = JSON.stringify(newMeta).length
bytesSavedEstimate += sizeBefore - sizeAfter
if (!effectiveDryRun) {
await ctx.db.patch(machine._id, { metadata: newMeta, updatedAt: Date.now() })
}
compacted += 1
}
return {
dryRun: effectiveDryRun,
processed,
compacted,
bytesSavedEstimate,
bytesSavedMB: Math.round(bytesSavedEstimate / 1024 / 1024 * 100) / 100,
}
},
})
export const backfillTicketSnapshots = mutation({
args: { tenantId: v.string(), limit: v.optional(v.number()) },
handler: async (ctx, { tenantId, limit }) => {