perf(whatsapp): índices em FKs + delete de instância em lotes
- Adiciona @@index nas FKs que sofrem cascade/SET NULL ao deletar instância: messages.replyToId (self-ref, evitava O(n²) no SET NULL), chats.instanceId, chats.contactId, contacts.instanceId, broadcasts.instanceId. - InstanceRepository.delete agora apaga 'messages' em lotes (5k) antes do cascade, evitando transação/locks gigantes em instâncias com dezenas de milhares de mensagens. O restante (chats/contacts/...) sai pelo cascade, agora rápido com os índices. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -175,6 +175,8 @@ model Contact {
|
|||||||
|
|
||||||
@@unique([tenantId, instanceId, jid])
|
@@unique([tenantId, instanceId, jid])
|
||||||
@@index([tenantId, instanceId, lidJid])
|
@@index([tenantId, instanceId, lidJid])
|
||||||
|
// FK dedicada — acelera o cascade ao deletar a instância
|
||||||
|
@@index([instanceId])
|
||||||
@@map("contacts")
|
@@map("contacts")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,6 +209,9 @@ model Chat {
|
|||||||
// WHERE tenantId + instanceId + isArchived (filtro)
|
// WHERE tenantId + instanceId + isArchived (filtro)
|
||||||
// ORDER BY isPinned DESC, lastMessageAt DESC (ordenação)
|
// ORDER BY isPinned DESC, lastMessageAt DESC (ordenação)
|
||||||
@@index([tenantId, instanceId, isArchived, isPinned(sort: Desc), lastMessageAt(sort: Desc)])
|
@@index([tenantId, instanceId, isArchived, isPinned(sort: Desc), lastMessageAt(sort: Desc)])
|
||||||
|
// FKs dedicadas — sem elas o cascade/SET NULL ao deletar instância/contato faz full-scan
|
||||||
|
@@index([instanceId])
|
||||||
|
@@index([contactId])
|
||||||
@@map("chats")
|
@@map("chats")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,6 +256,8 @@ model Message {
|
|||||||
@@index([instanceId, fromMe, remoteJid, timestamp(sort: Desc)])
|
@@index([instanceId, fromMe, remoteJid, timestamp(sort: Desc)])
|
||||||
// Índice para obter a última mensagem da instância — usado na verificação de gap de sincronização
|
// Índice para obter a última mensagem da instância — usado na verificação de gap de sincronização
|
||||||
@@index([instanceId, timestamp(sort: Desc)])
|
@@index([instanceId, timestamp(sort: Desc)])
|
||||||
|
// FK self-referente — sem índice o cascade roda UPDATE replyToId=NULL com full-scan (O(n²))
|
||||||
|
@@index([replyToId])
|
||||||
@@map("messages")
|
@@map("messages")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,6 +392,8 @@ model Broadcast {
|
|||||||
instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
recipients BroadcastRecipient[]
|
recipients BroadcastRecipient[]
|
||||||
|
|
||||||
|
// FK dedicada — acelera o cascade ao deletar a instância
|
||||||
|
@@index([instanceId])
|
||||||
@@map("broadcasts")
|
@@map("broadcasts")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
@@ -24,6 +24,22 @@ export class InstanceRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string) {
|
async delete(id: string) {
|
||||||
|
// Delete em lotes da maior tabela (messages) antes do cascade, para evitar
|
||||||
|
// uma transação única gigante (locks longos + WAL enorme) numa instância com
|
||||||
|
// dezenas de milhares de mensagens. O índice messages_replyToId_idx evita o
|
||||||
|
// O(n²) no SET NULL self-referente disparado pelo cascade.
|
||||||
|
const BATCH = 5000
|
||||||
|
for (;;) {
|
||||||
|
const removed = await prisma.$executeRaw`
|
||||||
|
DELETE FROM messages
|
||||||
|
WHERE ctid IN (
|
||||||
|
SELECT ctid FROM messages WHERE "instanceId" = ${id} LIMIT ${BATCH}
|
||||||
|
)`
|
||||||
|
if (removed < BATCH) break
|
||||||
|
}
|
||||||
|
|
||||||
|
// O cascade do banco cuida do resto (chats, contacts, protocols, broadcasts…),
|
||||||
|
// agora rápido graças aos índices nas FKs.
|
||||||
return prisma.instance.delete({ where: { id } })
|
return prisma.instance.delete({ where: { id } })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user