perf(iios): listThreads fetches last-message-per-thread in one query

Replaced the per-thread findFirst inside Promise.all (N parallel queries) with a
single Postgres DISTINCT ON query. A caller with many threads no longer fans out
and exhausts the connection pool (the conversation.list 500 under accumulated data).
message.spec 16/16 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-13 21:28:37 +05:30
parent 9e0411bfe6
commit 3e66c7a5db
@@ -208,28 +208,41 @@ export class MessageService {
membersBy.set(p.threadId, [...(membersBy.get(p.threadId) ?? []), name]); membersBy.set(p.threadId, [...(membersBy.get(p.threadId) ?? []), name]);
} }
const summaries = await Promise.all( // Latest message per thread in ONE query (Postgres DISTINCT ON) instead of one findFirst
threads.map(async (t) => { // per thread — a caller with many threads no longer fans out N parallel queries and
const last = await this.prisma.iiosInteraction.findFirst({ // exhausts the connection pool.
where: { threadId: t.id }, const lastRows =
orderBy: { occurredAt: 'desc' }, threads.length === 0
include: { parts: { where: { kind: 'TEXT' }, take: 1 } }, ? []
}); : await this.prisma.$queryRaw<Array<{ threadId: string; occurredAt: Date; lastMessage: string | null }>>(Prisma.sql`
const members = membersBy.get(t.id) ?? []; SELECT DISTINCT ON (i."threadId")
return { i."threadId" AS "threadId",
threadId: t.id, i."occurredAt" AS "occurredAt",
subject: t.subject, (SELECT p."bodyText" FROM "IiosMessagePart" p
membership: (t.metadata as { membership?: string } | null)?.membership, WHERE p."interactionId" = i.id AND p.kind::text = 'TEXT'
metadata: (t.metadata as Record<string, unknown> | null) ?? null, ORDER BY p."partIndex" ASC LIMIT 1) AS "lastMessage"
participants: members, FROM "IiosInteraction" i
participantCount: members.length, WHERE i."threadId" IN (${Prisma.join(threads.map((t) => t.id))})
unread: unreadBy.get(t.id) ?? 0, ORDER BY i."threadId", i."occurredAt" DESC
muted: mutedBy.get(t.id) ?? false, `);
lastMessage: last?.parts[0]?.bodyText ?? undefined, const lastBy = new Map(lastRows.map((r) => [r.threadId, r]));
lastAt: last?.occurredAt,
}; const summaries = threads.map((t) => {
}), const last = lastBy.get(t.id);
); const members = membersBy.get(t.id) ?? [];
return {
threadId: t.id,
subject: t.subject,
membership: (t.metadata as { membership?: string } | null)?.membership,
metadata: (t.metadata as Record<string, unknown> | null) ?? null,
participants: members,
participantCount: members.length,
unread: unreadBy.get(t.id) ?? 0,
muted: mutedBy.get(t.id) ?? false,
lastMessage: last?.lastMessage ?? undefined,
lastAt: last?.occurredAt,
};
});
return summaries.sort((a, b) => (b.lastAt?.getTime() ?? 0) - (a.lastAt?.getTime() ?? 0)); return summaries.sort((a, b) => (b.lastAt?.getTime() ?? 0) - (a.lastAt?.getTime() ?? 0));
} }