From b8e7179a855da125cebcbe6fcdbb435bf4733bae Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Thu, 2 Apr 2026 13:57:27 +0200 Subject: [PATCH] Add missing index on viewField.viewFieldGroupId (#19253) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Adds an index on `viewField.viewFieldGroupId` to fix a ~28s `DELETE FROM core.viewFieldGroup WHERE workspaceId = $1` query observed in production - The slow query is triggered during workspace hard deletion (`deleteWorkspaceSyncableMetadataEntities` in `workspace.service.ts`) - Root cause: the FK constraint `viewField.viewFieldGroupId → viewFieldGroup.id` with `ON DELETE SET NULL` forces PostgreSQL to sequentially scan the entire `viewField` table for every deleted `viewFieldGroup` row, because no index exists on the referencing column - Uses `CREATE INDEX CONCURRENTLY` in the migration to avoid locking the table on production --- ...view-field-group-id-index-on-view-field.ts | 19 +++++++++++++++++++ .../view-field/entities/view-field.entity.ts | 1 + 2 files changed, 20 insertions(+) create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1775129420309-add-view-field-group-id-index-on-view-field.ts diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1775129420309-add-view-field-group-id-index-on-view-field.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1775129420309-add-view-field-group-id-index-on-view-field.ts new file mode 100644 index 0000000000..de55c0f4c1 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1775129420309-add-view-field-group-id-index-on-view-field.ts @@ -0,0 +1,19 @@ +import { type MigrationInterface, type QueryRunner } from 'typeorm'; + +export class AddViewFieldGroupIdIndexOnViewField1775129420309 + implements MigrationInterface +{ + name = 'AddViewFieldGroupIdIndexOnViewField1775129420309'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE INDEX IF NOT EXISTS "IDX_VIEW_FIELD_VIEW_FIELD_GROUP_ID" ON "core"."viewField" ("viewFieldGroupId")`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `DROP INDEX IF EXISTS "core"."IDX_VIEW_FIELD_VIEW_FIELD_GROUP_ID"`, + ); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/view-field/entities/view-field.entity.ts b/packages/twenty-server/src/engine/metadata-modules/view-field/entities/view-field.entity.ts index ee68fe648d..af2c36c46d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-field/entities/view-field.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-field/entities/view-field.entity.ts @@ -32,6 +32,7 @@ export type ViewFieldOverrides = { @Index('IDX_VIEW_FIELD_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId']) @Index('IDX_VIEW_FIELD_VIEW_ID', ['viewId']) @Index('IDX_VIEW_FIELD_FIELD_METADATA_ID', ['fieldMetadataId']) +@Index('IDX_VIEW_FIELD_VIEW_FIELD_GROUP_ID', ['viewFieldGroupId']) @Index( 'IDX_VIEW_FIELD_FIELD_METADATA_ID_VIEW_ID_UNIQUE', ['fieldMetadataId', 'viewId'],