From d03480472ccb74f0e9556111c2767816ce42f7d9 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 18 May 2026 16:31:03 +0200 Subject: [PATCH] perf(server): index messageChannel/calendarChannel for per-workspace sync crons (#20678) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary The messaging/calendar import crons each iterate every active workspace and execute one `find` per workspace against `core."messageChannel"` / `core."calendarChannel"` with the shape: ``` WHERE "workspaceId" = $1 AND "isSyncEnabled" = true AND "syncStage" = $2 [AND "type" <> $3] ``` There is currently no index supporting that shape, so the planner does a seq scan on each table for every iteration. On prod-eu (RDS Performance Insights, `rds-prod-eu-one`), these two queries are the top two by load — together ~12 AAS, ~12 calls/sec — and have been the primary contributor to the sustained 100% CPU since active workspace count grew. This PR adds composite indexes on `(workspaceId, isSyncEnabled, syncStage)` for both tables as an instance migration in 2.6.0. --- ...00010000-add-channel-sync-stage-indexes.ts | 32 +++++++++++++++++++ .../instance-commands.constant.ts | 2 ++ .../entities/calendar-channel.entity.ts | 6 ++++ .../entities/message-channel.entity.ts | 6 ++++ 4 files changed, 46 insertions(+) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1798000010000-add-channel-sync-stage-indexes.ts diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1798000010000-add-channel-sync-stage-indexes.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1798000010000-add-channel-sync-stage-indexes.ts new file mode 100644 index 0000000000..fcafd59ae5 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1798000010000-add-channel-sync-stage-indexes.ts @@ -0,0 +1,32 @@ +import { QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface'; + +const MESSAGE_CHANNEL_INDEX_NAME = + 'IDX_MESSAGE_CHANNEL_WORKSPACE_ID_SYNC_ENABLED_SYNC_STAGE'; +const CALENDAR_CHANNEL_INDEX_NAME = + 'IDX_CALENDAR_CHANNEL_WORKSPACE_ID_SYNC_ENABLED_SYNC_STAGE'; + +@RegisteredInstanceCommand('2.6.0', 1798000010000) +export class AddChannelSyncStageIndexesFastInstanceCommand + implements FastInstanceCommand +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE INDEX IF NOT EXISTS "${MESSAGE_CHANNEL_INDEX_NAME}" ON "core"."messageChannel" ("workspaceId", "isSyncEnabled", "syncStage")`, + ); + await queryRunner.query( + `CREATE INDEX IF NOT EXISTS "${CALENDAR_CHANNEL_INDEX_NAME}" ON "core"."calendarChannel" ("workspaceId", "isSyncEnabled", "syncStage")`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `DROP INDEX IF EXISTS "core"."${CALENDAR_CHANNEL_INDEX_NAME}"`, + ); + await queryRunner.query( + `DROP INDEX IF EXISTS "core"."${MESSAGE_CHANNEL_INDEX_NAME}"`, + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts index 86823ad489..50cb916256 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts @@ -48,6 +48,7 @@ import { EncryptTotpSecretsSlowInstanceCommand } from 'src/database/commands/upg import { AddSubFieldNameToViewSortFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1778502963794-add-sub-field-name-to-view-sort'; import { DropPostgresCredentialsTableFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1798500000000-drop-postgres-credentials-table'; import { AddRelationTargetFieldMetadataIdToViewFilterFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1798000005000-add-relation-target-field-metadata-id-to-view-filter'; +import { AddChannelSyncStageIndexesFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1798000010000-add-channel-sync-stage-indexes'; export const INSTANCE_COMMANDS = [ AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand, @@ -98,4 +99,5 @@ export const INSTANCE_COMMANDS = [ AddSubFieldNameToViewSortFastInstanceCommand, DropPostgresCredentialsTableFastInstanceCommand, AddRelationTargetFieldMetadataIdToViewFilterFastInstanceCommand, + AddChannelSyncStageIndexesFastInstanceCommand, ]; diff --git a/packages/twenty-server/src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity.ts b/packages/twenty-server/src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity.ts index 62cb4306f5..6f1dd65189 100644 --- a/packages/twenty-server/src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity.ts @@ -4,6 +4,7 @@ import { Column, CreateDateColumn, Entity, + Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn, @@ -35,6 +36,11 @@ registerEnumType(CalendarChannelContactAutoCreationPolicy, { }); @Entity({ name: 'calendarChannel', schema: 'core' }) +@Index('IDX_CALENDAR_CHANNEL_WORKSPACE_ID_SYNC_ENABLED_SYNC_STAGE', [ + 'workspaceId', + 'isSyncEnabled', + 'syncStage', +]) export class CalendarChannelEntity extends WorkspaceRelatedEntity { @PrimaryGeneratedColumn('uuid') id: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/message-channel/entities/message-channel.entity.ts b/packages/twenty-server/src/engine/metadata-modules/message-channel/entities/message-channel.entity.ts index bebf10f42a..36f55714fb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/message-channel/entities/message-channel.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/message-channel/entities/message-channel.entity.ts @@ -4,6 +4,7 @@ import { Column, CreateDateColumn, Entity, + Index, JoinColumn, ManyToOne, OneToMany, @@ -47,6 +48,11 @@ registerEnumType(MessageChannelPendingGroupEmailsAction, { }); @Entity({ name: 'messageChannel', schema: 'core' }) +@Index('IDX_MESSAGE_CHANNEL_WORKSPACE_ID_SYNC_ENABLED_SYNC_STAGE', [ + 'workspaceId', + 'isSyncEnabled', + 'syncStage', +]) export class MessageChannelEntity extends WorkspaceRelatedEntity { @PrimaryGeneratedColumn('uuid') id: string;