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:
@@ -208,28 +208,41 @@ export class MessageService {
|
||||
membersBy.set(p.threadId, [...(membersBy.get(p.threadId) ?? []), name]);
|
||||
}
|
||||
|
||||
const summaries = await Promise.all(
|
||||
threads.map(async (t) => {
|
||||
const last = await this.prisma.iiosInteraction.findFirst({
|
||||
where: { threadId: t.id },
|
||||
orderBy: { occurredAt: 'desc' },
|
||||
include: { parts: { where: { kind: 'TEXT' }, take: 1 } },
|
||||
});
|
||||
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?.parts[0]?.bodyText ?? undefined,
|
||||
lastAt: last?.occurredAt,
|
||||
};
|
||||
}),
|
||||
);
|
||||
// Latest message per thread in ONE query (Postgres DISTINCT ON) instead of one findFirst
|
||||
// per thread — a caller with many threads no longer fans out N parallel queries and
|
||||
// exhausts the connection pool.
|
||||
const lastRows =
|
||||
threads.length === 0
|
||||
? []
|
||||
: await this.prisma.$queryRaw<Array<{ threadId: string; occurredAt: Date; lastMessage: string | null }>>(Prisma.sql`
|
||||
SELECT DISTINCT ON (i."threadId")
|
||||
i."threadId" AS "threadId",
|
||||
i."occurredAt" AS "occurredAt",
|
||||
(SELECT p."bodyText" FROM "IiosMessagePart" p
|
||||
WHERE p."interactionId" = i.id AND p.kind::text = 'TEXT'
|
||||
ORDER BY p."partIndex" ASC LIMIT 1) AS "lastMessage"
|
||||
FROM "IiosInteraction" i
|
||||
WHERE i."threadId" IN (${Prisma.join(threads.map((t) => t.id))})
|
||||
ORDER BY i."threadId", i."occurredAt" DESC
|
||||
`);
|
||||
const lastBy = new Map(lastRows.map((r) => [r.threadId, r]));
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user