perf: Single query user enrichment calls by array of userIds instead (#17190)

* single query

* getPublicEvent

* perf: use Map for faster lookup for large objects

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
This commit is contained in:
Syed Ali Shahbaz
2024-10-19 19:17:40 -03:00
committed by GitHub
co-authored by Udit Takkar
parent fcc2e0f2ae
commit f70e8c5189
3 changed files with 114 additions and 21 deletions
@@ -356,15 +356,16 @@ export const getPublicEvent = async (
const eventMetaData = EventTypeMetaDataSchema.parse(event.metadata || {});
const teamMetadata = teamMetadataSchema.parse(event.team?.metadata || {});
const hosts = [];
for (const host of event.hosts) {
hosts.push({
...host,
user: await UserRepository.enrichUserWithItsProfile({
user: host.user,
}),
});
}
const usersAsHosts = event.hosts.map((host) => host.user);
// Enrich users in a single batch call
const enrichedUsers = await UserRepository.enrichUsersWithTheirProfiles(usersAsHosts);
// Map enriched users back to the hosts
const hosts = event.hosts.map((host, index) => ({
...host,
user: enrichedUsers[index],
}));
const eventWithUserProfiles = {
...event,
@@ -558,18 +559,18 @@ async function getOwnerFromUsersArray(prisma: PrismaClient, eventTypeId: number)
},
});
if (!users.length) return null;
const usersWithUserProfile = [];
for (const user of users) {
const { profile } = await UserRepository.enrichUserWithItsProfile({
user: user,
});
usersWithUserProfile.push({
...user,
organizationId: profile?.organization?.id ?? null,
organization: profile?.organization,
profile,
});
}
// Batch enrich users in a single call
const enrichedUsers = await UserRepository.enrichUsersWithTheirProfiles(users);
// Map the enriched users back to include the organization info
const usersWithUserProfile = enrichedUsers.map((user) => ({
...user,
organizationId: user.profile?.organization?.id ?? null,
organization: user.profile?.organization,
profile: user.profile,
}));
return [
{
...usersWithUserProfile[0],
+33
View File
@@ -437,6 +437,39 @@ export class ProfileRepository {
return profiles;
}
static async findManyForUsers(userIds: number[]) {
const profiles = await prisma.profile.findMany({
where: {
userId: {
in: userIds,
},
},
include: {
organization: {
select: organizationSelect,
},
},
});
return profiles.map((profile) => {
const parsedOrganization = getParsedTeam(profile.organization);
return normalizeProfile({
username: profile.username,
id: profile.id,
userId: profile.userId,
uid: profile.uid,
name: parsedOrganization.name,
organizationId: profile.organizationId,
organization: {
...parsedOrganization,
requestedSlug: parsedOrganization.metadata?.requestedSlug ?? null,
metadata: parsedOrganization.metadata,
},
});
});
}
static async findManyForUser(user: { id: number }) {
const profiles = (
await prisma.profile.findMany({
+59
View File
@@ -380,6 +380,65 @@ export class UserRepository {
};
}
static async enrichUsersWithTheirProfiles<T extends { id: number; username: string | null }>(
users: T[]
): Promise<
Array<
T & {
nonProfileUsername: string | null;
profile: UserProfile;
}
>
> {
if (users.length === 0) return [];
const userIds = users.map((user) => user.id);
const profiles = await ProfileRepository.findManyForUsers(userIds);
// Create a Map for faster lookups, preserving arrays of profiles per user
const profileMap = new Map<number, UserProfile[]>();
profiles.forEach((profile) => {
if (!profileMap.has(profile.userId)) {
profileMap.set(profile.userId, []);
}
profileMap.get(profile.userId)!.push(profile);
});
// Precompute personal profiles for all users
const personalProfileMap = new Map<number, UserProfile>();
users.forEach((user) => {
personalProfileMap.set(user.id, ProfileRepository.buildPersonalProfileFromUser({ user }));
});
return users.map((user) => {
const userProfiles = profileMap.get(user.id) || [];
if (userProfiles.length > 0) {
const profile = userProfiles[0];
if (profile?.organization?.isPlatform) {
return {
...user,
nonProfileUsername: user.username,
profile: personalProfileMap.get(user.id)!,
};
}
return {
...user,
username: profile.username,
nonProfileUsername: user.username,
profile,
};
}
// If no organization profile exists, use the precomputed personal profile
return {
...user,
nonProfileUsername: user.username,
profile: personalProfileMap.get(user.id)!,
};
});
}
static enrichUserWithItsProfileBuiltFromUser<T extends { id: number; username: string | null }>({
user,
}: {