From 01ba57976635744174b0511c0950faf3c9bcfc18 Mon Sep 17 00:00:00 2001 From: bo0tzz Date: Tue, 20 Oct 2026 22:21:51 +0200 Subject: [PATCH] perf(server): index updateId on asset_face, stack and workflow (#32432) asset_face, stack and workflow were the only tables with an updateId column but no index on it. Face and stack sync (AssetFacesV1/V2, StacksV1, PartnerStacksV1) filter and order on updateId, so every mobile sync seq-scanned every face on the instance and walked all of the user's (or partners') stacks, even when nothing had changed. workflow isn't synced; it gets the index for consistency. Incremental sync with nothing changed, PG 14: | query | before | after | | ---------------------------------- | -------- | -------- | | assetFace.getUpserts (425k faces) | 33-37 ms | 0.15 ms | | stack.getUpserts (6k stacks) | 0.8 ms | 0.004 ms | | partnerStack.getUpserts (6k) | 0.6 ms | 0.007 ms | The face scan grows with the instance's face count (~10x that at 4M faces); the stack one with the user's stack count. (cherry picked from commit 9df807ed4a9536a28dad463270082095ec1fe94d) --- .../migrations/1792517020180-AddUpdateIdIndexes.ts | 13 +++++++++++++ server/src/schema/migrations/ORDER | 1 + server/src/schema/tables/asset-face.table.ts | 2 +- server/src/schema/tables/stack.table.ts | 2 +- server/src/schema/tables/workflow.table.ts | 2 +- 5 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 server/src/schema/migrations/1792517020180-AddUpdateIdIndexes.ts diff --git a/server/src/schema/migrations/1792517020180-AddUpdateIdIndexes.ts b/server/src/schema/migrations/1792517020180-AddUpdateIdIndexes.ts new file mode 100644 index 000000000..881729998 --- /dev/null +++ b/server/src/schema/migrations/1792517020180-AddUpdateIdIndexes.ts @@ -0,0 +1,13 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await sql`CREATE INDEX "stack_updateId_idx" ON "stack" ("updateId");`.execute(db); + await sql`CREATE INDEX "asset_face_updateId_idx" ON "asset_face" ("updateId");`.execute(db); + await sql`CREATE INDEX "workflow_updateId_idx" ON "workflow" ("updateId");`.execute(db); +} + +export async function down(db: Kysely): Promise { + await sql`DROP INDEX "stack_updateId_idx";`.execute(db); + await sql`DROP INDEX "workflow_updateId_idx";`.execute(db); + await sql`DROP INDEX "asset_face_updateId_idx";`.execute(db); +} diff --git a/server/src/schema/migrations/ORDER b/server/src/schema/migrations/ORDER index 29bc7eaf7..a334973b4 100644 --- a/server/src/schema/migrations/ORDER +++ b/server/src/schema/migrations/ORDER @@ -100,3 +100,4 @@ 1790693088454-AddPersonUserTableSharedBySharedWithConstraint 1792213725597-AddAssetFaceAssetState 1792511451454-AddPasskeyTable +1792517020180-AddUpdateIdIndexes diff --git a/server/src/schema/tables/asset-face.table.ts b/server/src/schema/tables/asset-face.table.ts index a318cc728..a613d0c45 100644 --- a/server/src/schema/tables/asset-face.table.ts +++ b/server/src/schema/tables/asset-face.table.ts @@ -95,7 +95,7 @@ export class AssetFaceTable { @UpdateDateColumn() updatedAt!: Generated; - @UpdateIdColumn() + @UpdateIdColumn({ index: true }) updateId!: Generated; @Column({ type: 'boolean', default: true }) diff --git a/server/src/schema/tables/stack.table.ts b/server/src/schema/tables/stack.table.ts index a38b0350d..520338ee2 100644 --- a/server/src/schema/tables/stack.table.ts +++ b/server/src/schema/tables/stack.table.ts @@ -31,7 +31,7 @@ export class StackTable { @UpdateDateColumn() updatedAt!: Generated; - @UpdateIdColumn() + @UpdateIdColumn({ index: true }) updateId!: Generated; //TODO: Add constraint to ensure primary asset exists in the assets array diff --git a/server/src/schema/tables/workflow.table.ts b/server/src/schema/tables/workflow.table.ts index f27f63ef8..a313a2bb6 100644 --- a/server/src/schema/tables/workflow.table.ts +++ b/server/src/schema/tables/workflow.table.ts @@ -36,7 +36,7 @@ export class WorkflowTable { @UpdateDateColumn() updatedAt!: Generated; - @UpdateIdColumn() + @UpdateIdColumn({ index: true }) updateId!: Generated; @Column({ type: 'boolean', default: true })