From 4a18d7a5009d1438b5e299acbaba418acd73d402 Mon Sep 17 00:00:00 2001 From: Mahmoud Abas Date: Thu, 29 Oct 2026 00:45:13 +0300 Subject: [PATCH] perf(database): index the message key for chat and message-id lookups Message had only its primary key and instanceId, so every lookup by chat or by message id read all messages of the instance. Prisma JSON path filters compile to "key" #> '{field}' and the raw queries use "key"->>'field', so the migration adds an (instanceId, expression) index for both forms: id and remoteJid as text, id, remoteJid and remoteJidAlt as jsonb path. Prisma cannot declare expression indexes, so they live in a raw SQL migration, with a comment on the Message model in both PostgreSQL schemas. The indexes are built CONCURRENTLY, which prisma migrate deploy allows because it runs each statement outside a transaction, and IF NOT EXISTS keeps the migration a no-op where they already exist. Refs #2753 --- .../migration.sql | 29 +++++++++++++++++++ prisma/postgresql-schema.prisma | 2 ++ prisma/psql_bouncer-schema.prisma | 2 ++ 3 files changed, 33 insertions(+) create mode 100644 prisma/postgresql-migrations/20261028200000_add_message_key_indexes/migration.sql diff --git a/prisma/postgresql-migrations/20261028200000_add_message_key_indexes/migration.sql b/prisma/postgresql-migrations/20261028200000_add_message_key_indexes/migration.sql new file mode 100644 index 0000000000..b018f867ea --- /dev/null +++ b/prisma/postgresql-migrations/20261028200000_add_message_key_indexes/migration.sql @@ -0,0 +1,29 @@ +-- Index the message key (issue #2753). +-- +-- "Message" had only its primary key and "instanceId", so every lookup by chat or by message id +-- read all messages of the instance. The key is filtered in two forms, and PostgreSQL uses an +-- expression index only for the same expression: +-- * Prisma JSON path filters (key: { path: ['remoteJid'], equals }) compile to "key" #> '{remoteJid}'. +-- * Raw queries (getMessage, messages.update, the read and unread updates, fetchChats and the +-- Chatwoot service) use "key"->>'id' and "key"->>'remoteJid'. +-- Prisma cannot declare expression indexes in the schema, so they live in this migration. +-- +-- CONCURRENTLY keeps writes to "Message" going while each index builds. prisma migrate deploy runs +-- the statements of this file one by one, outside a transaction, so CONCURRENTLY is allowed here. +-- If a build fails part-way, PostgreSQL leaves an INVALID index under that name: drop it +-- (DROP INDEX CONCURRENTLY ""), mark the migration rolled back and deploy again. + +-- CreateIndex +CREATE INDEX CONCURRENTLY IF NOT EXISTS "Message_instanceId_keyId_idx" ON "Message"("instanceId", ("key" ->> 'id')); + +-- CreateIndex +CREATE INDEX CONCURRENTLY IF NOT EXISTS "Message_instanceId_keyRemoteJid_idx" ON "Message"("instanceId", ("key" ->> 'remoteJid')); + +-- CreateIndex +CREATE INDEX CONCURRENTLY IF NOT EXISTS "Message_instanceId_keyIdPath_idx" ON "Message"("instanceId", ("key" #> '{id}')); + +-- CreateIndex +CREATE INDEX CONCURRENTLY IF NOT EXISTS "Message_instanceId_keyRemoteJidPath_idx" ON "Message"("instanceId", ("key" #> '{remoteJid}')); + +-- CreateIndex +CREATE INDEX CONCURRENTLY IF NOT EXISTS "Message_instanceId_keyRemoteJidAltPath_idx" ON "Message"("instanceId", ("key" #> '{remoteJidAlt}')); diff --git a/prisma/postgresql-schema.prisma b/prisma/postgresql-schema.prisma index 661a564c04..21a3d9d909 100644 --- a/prisma/postgresql-schema.prisma +++ b/prisma/postgresql-schema.prisma @@ -179,6 +179,8 @@ model Message { session IntegrationSession? @relation(fields: [sessionId], references: [id]) @@index([instanceId]) + // Expression indexes on "key" (id, remoteJid, remoteJidAlt) are created in migration + // 20261028200000_add_message_key_indexes: Prisma cannot declare them here. } model MessageUpdate { diff --git a/prisma/psql_bouncer-schema.prisma b/prisma/psql_bouncer-schema.prisma index acc23b6723..2c846fa702 100644 --- a/prisma/psql_bouncer-schema.prisma +++ b/prisma/psql_bouncer-schema.prisma @@ -178,6 +178,8 @@ model Message { session IntegrationSession? @relation(fields: [sessionId], references: [id]) @@index([instanceId]) + // Expression indexes on "key" (id, remoteJid, remoteJidAlt) are created in migration + // 20261028200000_add_message_key_indexes: Prisma cannot declare them here. } model MessageUpdate {