refactor: split large org creation transaction into smaller batches (#25688)

* refactor: split large org creation transaction into smaller batches

The prisma.team.create call at L418 was creating the organization along with
all orgProfiles and memberships in a single large transaction, which could
timeout when there are many members.

This change splits the operation into smaller, separate transactions:
1. Create the organization (team) with just metadata and organizationSettings
2. Create org profiles in batches of 50 using Promise.all
3. Create memberships in batches of 100 using createMany
4. Fetch created profiles to rebuild the member-profile mapping

This approach avoids transaction timeouts while maintaining the same
functionality and data integrity.

Co-Authored-By: alex@cal.com <me@alexvanandel.com>

* Apply suggestion from @cubic-dev-ai[bot]

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
This commit is contained in:
Alex van Andel
2026-01-13 14:56:46 +00:00
committed by GitHub
co-authored by cubic-dev-ai[bot] Devin AI Keith Williams Anik Dhabal Babu
parent 573756eaa2
commit 0b73491367
+57 -39
View File
@@ -480,7 +480,7 @@ async function createOrganizationAndAddMembersAndTeams({
const { organizationSettings, ...restOrgData } = orgData;
// Create organization with those users as members
// Step 1: Create organization (team) with just metadata and organizationSettings
const orgInDb = await prisma.team.create({
data: {
...restOrgData,
@@ -490,55 +490,73 @@ async function createOrganizationAndAddMembersAndTeams({
: {}),
isOrganization: true,
},
orgProfiles: {
create: orgMembersInDb.map((member) => ({
uid: uuid(),
username: member.orgProfile.username,
movedFromUser: {
connect: {
id: member.id,
},
},
user: {
connect: {
id: member.id,
},
},
})),
},
organizationSettings: {
create: {
...organizationSettings,
},
},
members: {
create: orgMembersInDb.map((member) => ({
user: {
connect: {
id: member.id,
},
},
role: member.orgMembership.role || "MEMBER",
accepted: member.orgMembership.accepted,
})),
},
},
select: {
id: true,
members: true,
orgProfiles: true,
},
});
const orgMembersInDBWithProfileId = await Promise.all(
orgMembersInDb.map(async (member) => ({
...member,
profile: {
...member.orgProfile,
id: orgInDb.orgProfiles.find((p) => p.userId === member.id)?.id,
},
}))
);
// Step 2: Create org profiles in batches to avoid large transactions
const profileBatchSize = 50;
for (let i = 0; i < orgMembersInDb.length; i += profileBatchSize) {
const batch = orgMembersInDb.slice(i, i + profileBatchSize);
await Promise.all(
batch.map((member) =>
prisma.profile.create({
data: {
uid: uuid(),
username: member.orgProfile.username,
organizationId: orgInDb.id,
userId: member.id,
movedFromUser: {
connect: {
id: member.id,
},
},
},
})
)
);
}
// Step 3: Create memberships using createMany for better performance
const membershipBatchSize = 100;
for (let i = 0; i < orgMembersInDb.length; i += membershipBatchSize) {
const batch = orgMembersInDb.slice(i, i + membershipBatchSize);
await prisma.membership.createMany({
data: batch.map((member) => ({
teamId: orgInDb.id,
userId: member.id,
role: member.orgMembership.role || "MEMBER",
accepted: member.orgMembership.accepted ?? false,
})),
});
}
// Step 4: Fetch created profiles to rebuild orgMembersInDBWithProfileId
const createdProfiles = await prisma.profile.findMany({
where: {
organizationId: orgInDb.id,
userId: { in: orgMembersInDb.map((m) => m.id) },
},
select: {
id: true,
userId: true,
},
});
const orgMembersInDBWithProfileId = orgMembersInDb.map((member) => ({
...member,
profile: {
...member.orgProfile,
id: createdProfiles.find((p) => p.userId === member.id)?.id,
},
}));
// For each member create one event
for (const member of orgMembersInDBWithProfileId) {