From 68f87f7254dfaf592340eaf3bc888171ed397227 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 8 Jul 2025 18:01:18 -0400 Subject: [PATCH] perf: Move recalculation of memberships to async job (#9567) * perf: Move recalculation of memberships to async job * tsc --- server/commands/documentMover.ts | 66 +------------- .../processors/DocumentMovedProcessor.ts | 87 +++++++++++++++++++ server/types.ts | 21 +++-- 3 files changed, 100 insertions(+), 74 deletions(-) create mode 100644 server/queues/processors/DocumentMovedProcessor.ts diff --git a/server/commands/documentMover.ts b/server/commands/documentMover.ts index 09501c13da..82fc851408 100644 --- a/server/commands/documentMover.ts +++ b/server/commands/documentMover.ts @@ -1,15 +1,7 @@ import { Transaction } from "sequelize"; import { createContext } from "@server/context"; import { traceFunction } from "@server/logging/tracing"; -import { - User, - Document, - Collection, - Pin, - Event, - UserMembership, - GroupMembership, -} from "@server/models"; +import { User, Document, Collection, Pin, Event } from "@server/models"; type Props = { /** User attempting to move the document */ @@ -227,44 +219,6 @@ async function documentMover({ await document.save({ transaction }); result.documents.push(document); - // If there are any sourced memberships for this document, we need to go to the source - // memberships and recalculate the membership for the user or group. - const [ - userMemberships, - parentDocumentUserMemberships, - groupMemberships, - parentDocumentGroupMemberships, - ] = await Promise.all([ - UserMembership.findRootMembershipsForDocument(document.id, undefined, { - transaction, - }), - parentDocumentId - ? UserMembership.findRootMembershipsForDocument( - parentDocumentId, - undefined, - { transaction } - ) - : [], - GroupMembership.findRootMembershipsForDocument(document.id, undefined, { - transaction, - }), - parentDocumentId - ? GroupMembership.findRootMembershipsForDocument( - parentDocumentId, - undefined, - { transaction } - ) - : [], - ]); - - await recalculateUserMemberships(userMemberships, transaction); - await recalculateUserMemberships(parentDocumentUserMemberships, transaction); - await recalculateGroupMemberships(groupMemberships, transaction); - await recalculateGroupMemberships( - parentDocumentGroupMemberships, - transaction - ); - await Event.create( { name: "documents.move", @@ -288,24 +242,6 @@ async function documentMover({ return result; } -async function recalculateUserMemberships( - memberships: UserMembership[], - transaction?: Transaction -) { - for (const membership of memberships) { - await UserMembership.createSourcedMemberships(membership, { transaction }); - } -} - -async function recalculateGroupMemberships( - memberships: GroupMembership[], - transaction?: Transaction -) { - for (const membership of memberships) { - await GroupMembership.createSourcedMemberships(membership, { transaction }); - } -} - export default traceFunction({ spanName: "documentMover", })(documentMover); diff --git a/server/queues/processors/DocumentMovedProcessor.ts b/server/queues/processors/DocumentMovedProcessor.ts new file mode 100644 index 0000000000..445009efa0 --- /dev/null +++ b/server/queues/processors/DocumentMovedProcessor.ts @@ -0,0 +1,87 @@ +import { Transaction } from "sequelize"; +import { Document, GroupMembership, UserMembership } from "@server/models"; +import { sequelize } from "@server/storage/database"; +import { DocumentMovedEvent, Event } from "@server/types"; +import BaseProcessor from "./BaseProcessor"; + +export default class DocumentMovedProcessor extends BaseProcessor { + static applicableEvents: Event["name"][] = ["documents.move"]; + + async perform(event: DocumentMovedEvent) { + await sequelize.transaction(async (transaction) => { + const document = await Document.findByPk(event.documentId, { + transaction, + }); + if (!document) { + return; + } + + // If there are any sourced memberships for this document, we need to go to the source + // memberships and recalculate the membership for the user or group. + const [ + userMemberships, + parentDocumentUserMemberships, + groupMemberships, + parentDocumentGroupMemberships, + ] = await Promise.all([ + UserMembership.findRootMembershipsForDocument(document.id, undefined, { + transaction, + }), + document.parentDocumentId + ? UserMembership.findRootMembershipsForDocument( + document.parentDocumentId, + undefined, + { transaction } + ) + : [], + GroupMembership.findRootMembershipsForDocument(document.id, undefined, { + transaction, + }), + document.parentDocumentId + ? GroupMembership.findRootMembershipsForDocument( + document.parentDocumentId, + undefined, + { transaction } + ) + : [], + ]); + + await this.recalculateUserMemberships(userMemberships, transaction); + await this.recalculateUserMemberships( + parentDocumentUserMemberships, + transaction + ); + await this.recalculateGroupMemberships(groupMemberships, transaction); + await this.recalculateGroupMemberships( + parentDocumentGroupMemberships, + transaction + ); + }); + } + + private async recalculateUserMemberships( + memberships: UserMembership[], + transaction?: Transaction + ) { + await Promise.all( + memberships.map((membership) => + UserMembership.createSourcedMemberships(membership, { + transaction, + }) + ) + ); + } + + private async recalculateGroupMemberships( + memberships: GroupMembership[], + transaction?: Transaction + ) { + await Promise.all( + memberships.map((membership) => + GroupMembership.createSourcedMemberships(membership, { + transaction, + }) + ) + ); + } +} diff --git a/server/types.ts b/server/types.ts index a24b8a0198..dc0a8890b7 100644 --- a/server/types.ts +++ b/server/types.ts @@ -180,6 +180,16 @@ export type UserMembershipEvent = BaseEvent & { }; }; +export type DocumentMovedEvent = BaseEvent & { + name: "documents.move"; + documentId: string; + collectionId: string; + data: { + collectionIds: string[]; + documentIds: string[]; + }; +}; + export type DocumentEvent = BaseEvent & ( | { @@ -212,15 +222,6 @@ export type DocumentEvent = BaseEvent & sourceCollectionId: string; }; } - | { - name: "documents.move"; - documentId: string; - collectionId: string; - data: { - collectionIds: string[]; - documentIds: string[]; - }; - } | { name: | "documents.update" @@ -245,6 +246,7 @@ export type DocumentEvent = BaseEvent & previousTitle: string; }; } + | DocumentMovedEvent ); export type EmptyTrashEvent = { @@ -492,6 +494,7 @@ export type Event = | AuthenticationProviderEvent | DocumentEvent | DocumentUserEvent + | DocumentMovedEvent | DocumentGroupEvent | PinEvent | CommentEvent