diff --git a/packages/twenty-server/src/engine/core-modules/metrics/constants/workspace-migration-action-count-bucket-boundaries.constant.ts b/packages/twenty-server/src/engine/core-modules/metrics/constants/workspace-migration-action-count-bucket-boundaries.constant.ts new file mode 100644 index 0000000000..56396ba2db --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/metrics/constants/workspace-migration-action-count-bucket-boundaries.constant.ts @@ -0,0 +1,3 @@ +export const WORKSPACE_MIGRATION_ACTION_COUNT_BUCKET_BOUNDARIES = [ + 1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, +] as const; diff --git a/packages/twenty-server/src/engine/core-modules/metrics/constants/workspace-migration-duration-ms-bucket-boundaries.constant.ts b/packages/twenty-server/src/engine/core-modules/metrics/constants/workspace-migration-duration-ms-bucket-boundaries.constant.ts new file mode 100644 index 0000000000..80c701c515 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/metrics/constants/workspace-migration-duration-ms-bucket-boundaries.constant.ts @@ -0,0 +1,3 @@ +export const WORKSPACE_MIGRATION_DURATION_MS_BUCKET_BOUNDARIES = [ + 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000, 60000, 120000, +] as const; diff --git a/packages/twenty-server/src/engine/core-modules/metrics/types/metrics-keys.type.ts b/packages/twenty-server/src/engine/core-modules/metrics/types/metrics-keys.type.ts index 736b484e0c..8aa0d5b1ca 100644 --- a/packages/twenty-server/src/engine/core-modules/metrics/types/metrics-keys.type.ts +++ b/packages/twenty-server/src/engine/core-modules/metrics/types/metrics-keys.type.ts @@ -83,4 +83,11 @@ export enum MetricsKeys { ConnectedAccountWebhookSubscriptionRenewalFailed = 'connected-account-webhook-subscription/renewal-failed', ConnectedAccountWebhookSubscriptionDeleted = 'connected-account-webhook-subscription/deleted', ConnectedAccountWebhookSubscriptionDeletionFailed = 'connected-account-webhook-subscription/deletion-failed', + WorkspaceMigrationBuildDurationMs = 'workspace-migration/build-duration-ms', + WorkspaceMigrationBuildEntityDurationMs = 'workspace-migration/build-entity-duration-ms', + WorkspaceMigrationBuildEntityPhaseDurationMs = 'workspace-migration/build-entity-phase-duration-ms', + WorkspaceMigrationRunDurationMs = 'workspace-migration/run-duration-ms', + WorkspaceMigrationRunPhaseDurationMs = 'workspace-migration/run-phase-duration-ms', + WorkspaceMigrationActionDurationMs = 'workspace-migration/action-duration-ms', + WorkspaceMigrationActionCount = 'workspace-migration/action-count', } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service.ts index a9ad88fc56..8d38daee74 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service.ts @@ -6,6 +6,10 @@ import { } from 'twenty-shared/metadata'; import { LoggerService } from 'src/engine/core-modules/logger/logger.service'; +import { WORKSPACE_MIGRATION_ACTION_COUNT_BUCKET_BOUNDARIES } from 'src/engine/core-modules/metrics/constants/workspace-migration-action-count-bucket-boundaries.constant'; +import { WORKSPACE_MIGRATION_DURATION_MS_BUCKET_BOUNDARIES } from 'src/engine/core-modules/metrics/constants/workspace-migration-duration-ms-bucket-boundaries.constant'; +import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; +import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { AllFlatEntityOperationRecordByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-operation-record-by-metadata-name.type'; import { AllFlatEntityOperationByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-to-create-delete-update.type'; @@ -71,6 +75,7 @@ export class WorkspaceMigrationValidateBuildAndRunService { private readonly workspaceMigrationFlatEntityMapsService: WorkspaceMigrationFlatEntityMapsService, private readonly metadataEventEmitter: MetadataEventEmitter, private readonly metadataSideEffectEngineService: MetadataSideEffectEngineService, + private readonly metricsService: MetricsService, private readonly logger: LoggerService, twentyConfigService: TwentyConfigService, ) { @@ -98,6 +103,14 @@ export class WorkspaceMigrationValidateBuildAndRunService { await this.workspaceMigrationBuildOrchestratorService .buildWorkspaceMigration(buildArgs) .catch((error) => { + this.metricsService.recordHistogram({ + key: MetricsKeys.WorkspaceMigrationBuildDurationMs, + value: performance.now() - buildStart, + unit: 'ms', + attributes: { status: 'error' }, + bucketBoundaries: WORKSPACE_MIGRATION_DURATION_MS_BUCKET_BOUNDARIES, + }); + this.logger.error( error, WorkspaceMigrationValidateBuildAndRunService.name, @@ -110,6 +123,14 @@ export class WorkspaceMigrationValidateBuildAndRunService { }); const buildMs = performance.now() - buildStart; + this.metricsService.recordHistogram({ + key: MetricsKeys.WorkspaceMigrationBuildDurationMs, + value: buildMs, + unit: 'ms', + attributes: { status: validateAndBuildResult.status }, + bucketBoundaries: WORKSPACE_MIGRATION_DURATION_MS_BUCKET_BOUNDARIES, + }); + this.logger.perf( `[install-perf] buildWorkspaceMigration took ${buildMs.toFixed(1)}ms (status=${validateAndBuildResult.status})`, WorkspaceMigrationValidateBuildAndRunService.name, @@ -154,6 +175,12 @@ export class WorkspaceMigrationValidateBuildAndRunService { WorkspaceMigrationValidateBuildAndRunService.name, ); + this.metricsService.recordHistogram({ + key: MetricsKeys.WorkspaceMigrationActionCount, + value: workspaceMigration.actions.length, + bucketBoundaries: WORKSPACE_MIGRATION_ACTION_COUNT_BUCKET_BOUNDARIES, + }); + const runStart = performance.now(); const { hasSchemaMetadataChanged, metadataEvents } = await this.workspaceMigrationRunnerService.run({ diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service.ts index 59c98990d6..f4384fe6a1 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service.ts @@ -6,6 +6,9 @@ import { isDefined } from 'twenty-shared/utils'; import { validate as uuidValidate, version as uuidVersion } from 'uuid'; import { LoggerService } from 'src/engine/core-modules/logger/logger.service'; +import { WORKSPACE_MIGRATION_DURATION_MS_BUCKET_BOUNDARIES } from 'src/engine/core-modules/metrics/constants/workspace-migration-duration-ms-bucket-boundaries.constant'; +import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; +import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type'; import { FlatEntityMapsException, FlatEntityMapsExceptionCode, @@ -56,6 +59,10 @@ export abstract class WorkspaceEntityMigrationBuilderService< > { @Inject(LoggerService) protected readonly logger: LoggerService; + + @Inject(MetricsService) + protected readonly metricsService: MetricsService; + private metadataName: T; constructor(metadataName: T) { @@ -80,6 +87,9 @@ export abstract class WorkspaceEntityMigrationBuilderService< 'matrix computation', ); + const validateAndBuildStart = performance.now(); + const matrixComputationStart = performance.now(); + const fromFlatEntities = Object.values( fromFlatEntityMaps.byUniversalIdentifier, ).filter(isDefined); @@ -107,6 +117,11 @@ export abstract class WorkspaceEntityMigrationBuilderService< 'entity processing', ); + this.recordBuildEntityPhaseMetric({ + phase: 'matrix-computation', + startedAt: matrixComputationStart, + }); + const flatEntityMapsKey = getMetadataFlatEntityMapsKey(this.metadataName); const actionsResult = getMetadataEmptyWorkspaceMigrationActionRecord( this.metadataName, @@ -119,6 +134,8 @@ export abstract class WorkspaceEntityMigrationBuilderService< 'deletion validation', ); + const deletionValidationStart = performance.now(); + const remainingFlatEntityMapsToDelete = structuredClone( deletedFlatEntityMaps, ); @@ -195,6 +212,13 @@ export abstract class WorkspaceEntityMigrationBuilderService< 'creation validation', ); + this.recordBuildEntityPhaseMetric({ + phase: 'deletion-validation', + startedAt: deletionValidationStart, + }); + + const creationValidationStart = performance.now(); + const remainingFlatEntityMapsToCreate = structuredClone( createdFlatEntityMaps, ); @@ -275,6 +299,13 @@ export abstract class WorkspaceEntityMigrationBuilderService< 'update validation', ); + this.recordBuildEntityPhaseMetric({ + phase: 'creation-validation', + startedAt: creationValidationStart, + }); + + const updateValidationStart = performance.now(); + for (const flatEntityToUpdateUniversalIdentifier in updatedFlatEntityMaps.byUniversalIdentifier) { const flatEntityUpdate = updatedFlatEntityMaps.byUniversalIdentifier[ @@ -362,13 +393,28 @@ export abstract class WorkspaceEntityMigrationBuilderService< 'entity processing', ); + this.recordBuildEntityPhaseMetric({ + phase: 'update-validation', + startedAt: updateValidationStart, + }); + if (allValidationResult.length > 0) { + this.recordBuildEntityDurationMetric({ + status: 'fail', + startedAt: validateAndBuildStart, + }); + return { status: 'fail', errors: allValidationResult, }; } + this.recordBuildEntityDurationMetric({ + status: 'success', + startedAt: validateAndBuildStart, + }); + this.logger.perfTimeEnd( `EntityBuilder ${this.metadataName}`, 'validateAndBuild', @@ -380,6 +426,42 @@ export abstract class WorkspaceEntityMigrationBuilderService< }; } + private recordBuildEntityDurationMetric({ + status, + startedAt, + }: { + status: 'success' | 'fail'; + startedAt: number; + }): void { + this.metricsService.recordHistogram({ + key: MetricsKeys.WorkspaceMigrationBuildEntityDurationMs, + value: performance.now() - startedAt, + unit: 'ms', + attributes: { metadataName: this.metadataName, status }, + bucketBoundaries: WORKSPACE_MIGRATION_DURATION_MS_BUCKET_BOUNDARIES, + }); + } + + private recordBuildEntityPhaseMetric({ + phase, + startedAt, + }: { + phase: + | 'matrix-computation' + | 'deletion-validation' + | 'creation-validation' + | 'update-validation'; + startedAt: number; + }): void { + this.metricsService.recordHistogram({ + key: MetricsKeys.WorkspaceMigrationBuildEntityPhaseDurationMs, + value: performance.now() - startedAt, + unit: 'ms', + attributes: { metadataName: this.metadataName, phase }, + bucketBoundaries: WORKSPACE_MIGRATION_DURATION_MS_BUCKET_BOUNDARIES, + }); + } + private validateUniversalIdentifier({ flatEntityToValidate: { universalIdentifier }, }: UniversalFlatEntityValidationArgs): FlatEntityValidationError[] { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/workspace-migration-builder.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/workspace-migration-builder.module.ts index fb7406e2b6..176d67f7c2 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/workspace-migration-builder.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/workspace-migration-builder.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; +import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service'; import { WorkspaceMigrationAgentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/agent/workspace-migration-agent-actions-builder.service'; import { WorkspaceMigrationCommandMenuItemActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/command-menu-item/workspace-migration-command-menu-item-actions-builder.service'; @@ -36,7 +37,11 @@ import { WorkspaceMigrationSearchFieldMetadataActionsBuilderService } from 'src/ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/workspace-migration-builder-validators.module'; @Module({ - imports: [FeatureFlagModule, WorkspaceMigrationBuilderValidatorsModule], + imports: [ + FeatureFlagModule, + WorkspaceMigrationBuilderValidatorsModule, + MetricsModule, + ], providers: [ FlatFieldMetadataTypeValidatorService, WorkspaceMigrationObjectActionsBuilderService, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts index 8fa05ac9eb..dc8ed7c32f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; +import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; import { SecretEncryptionModule } from 'src/engine/core-modules/secret-encryption/secret-encryption.module'; import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module'; import { CreateAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/agent/services/create-agent-action-handler.service'; @@ -103,6 +104,7 @@ import { UpdateSearchFieldMetadataActionHandlerService } from 'src/engine/worksp TypeOrmModule.forFeature([ApplicationEntity]), WorkspaceSchemaManagerModule, SecretEncryptionModule, + MetricsModule, ], providers: [ CreateFieldActionHandlerService, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface.ts index ccc0635e70..d4807fb4c5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface.ts @@ -4,6 +4,9 @@ import { AllMetadataName } from 'twenty-shared/metadata'; import { QueryRunner } from 'typeorm'; import { LoggerService } from 'src/engine/core-modules/logger/logger.service'; +import { WORKSPACE_MIGRATION_DURATION_MS_BUCKET_BOUNDARIES } from 'src/engine/core-modules/metrics/constants/workspace-migration-duration-ms-bucket-boundaries.constant'; +import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; +import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type'; import { ALL_METADATA_ENTITY_BY_METADATA_NAME } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-entity-by-metadata-name.constant'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; @@ -75,6 +78,9 @@ export abstract class BaseWorkspaceMigrationRunnerActionHandlerService< @Inject(LoggerService) protected readonly logger: LoggerService; + @Inject(MetricsService) + protected readonly metricsService: MetricsService; + public abstract transpileUniversalActionToFlatAction( context: WorkspaceMigrationActionRunnerArgs, ): Promise; @@ -320,18 +326,43 @@ export abstract class BaseWorkspaceMigrationRunnerActionHandlerService< label, method, }: { - label: string; + label: 'executeForMetadata' | 'executeForWorkspaceSchema'; method: () => Promise; }): Promise { + const startedAt = performance.now(); + + const recordActionDuration = (status: 'success' | 'fail') => + this.metricsService.recordHistogram({ + key: MetricsKeys.WorkspaceMigrationActionDurationMs, + value: performance.now() - startedAt, + unit: 'ms', + attributes: { + actionType: this.actionType, + metadataName: this.metadataName, + step: label, + status, + }, + bucketBoundaries: WORKSPACE_MIGRATION_DURATION_MS_BUCKET_BOUNDARIES, + }); + this.logger.perfTime( 'BaseWorkspaceMigrationRunnerActionHandlerService', `${this.actionType}_${this.metadataName} ${label}`, ); - await method(); + + try { + await method(); + } catch (error) { + recordActionDuration('fail'); + throw error; + } + this.logger.perfTimeEnd( 'BaseWorkspaceMigrationRunnerActionHandlerService', `${this.actionType}_${this.metadataName} ${label}`, ); + + recordActionDuration('success'); } } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts index 7434cbb771..2c55e328e4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts @@ -6,6 +6,9 @@ import { isDefined } from 'twenty-shared/utils'; import { DataSource } from 'typeorm'; import { LoggerService } from 'src/engine/core-modules/logger/logger.service'; +import { WORKSPACE_MIGRATION_DURATION_MS_BUCKET_BOUNDARIES } from 'src/engine/core-modules/metrics/constants/workspace-migration-duration-ms-bucket-boundaries.constant'; +import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; +import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; @@ -36,6 +39,7 @@ export class WorkspaceMigrationRunnerService { private readonly workspaceMigrationRunnerActionHandlerRegistry: WorkspaceMigrationRunnerActionHandlerRegistryService, private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, private readonly workspaceCacheService: WorkspaceCacheService, + private readonly metricsService: MetricsService, private readonly logger: LoggerService, private readonly twentyConfigService: TwentyConfigService, ) {} @@ -149,6 +153,28 @@ export class WorkspaceMigrationRunnerService { ); } + private recordRunPhaseMetric({ + phase, + status, + value, + }: { + phase: + | 'initial-cache-retrieval' + | 'action-execution' + | 'commit' + | 'cache-invalidation'; + status: 'success' | 'fail'; + value: number; + }): void { + this.metricsService.recordHistogram({ + key: MetricsKeys.WorkspaceMigrationRunPhaseDurationMs, + value, + unit: 'ms', + attributes: { phase, status }, + bucketBoundaries: WORKSPACE_MIGRATION_DURATION_MS_BUCKET_BOUNDARIES, + }); + } + private async logBlockingDbActivity(): Promise { try { // Metadata only (no query text) to avoid logging literals from other sessions. @@ -178,7 +204,42 @@ export class WorkspaceMigrationRunnerService { } } - run = async ({ + run = async (args: { + workspaceMigration: WorkspaceMigration; + workspaceId: string; + }): Promise<{ + allFlatEntityMaps: AllFlatEntityMaps; + metadataEvents: MetadataEvent[]; + hasSchemaMetadataChanged: boolean; + }> => { + const runStart = performance.now(); + + try { + const result = await this.executeRun(args); + + this.metricsService.recordHistogram({ + key: MetricsKeys.WorkspaceMigrationRunDurationMs, + value: performance.now() - runStart, + unit: 'ms', + attributes: { status: 'success' }, + bucketBoundaries: WORKSPACE_MIGRATION_DURATION_MS_BUCKET_BOUNDARIES, + }); + + return result; + } catch (error) { + this.metricsService.recordHistogram({ + key: MetricsKeys.WorkspaceMigrationRunDurationMs, + value: performance.now() - runStart, + unit: 'ms', + attributes: { status: 'fail' }, + bucketBoundaries: WORKSPACE_MIGRATION_DURATION_MS_BUCKET_BOUNDARIES, + }); + + throw error; + } + }; + + private executeRun = async ({ workspaceMigration: { actions, applicationUniversalIdentifier }, workspaceId, }: { @@ -246,6 +307,12 @@ export class WorkspaceMigrationRunnerService { const initialCacheRetrievalMs = performance.now() - initialCacheRetrievalStart; + this.recordRunPhaseMetric({ + phase: 'initial-cache-retrieval', + status: 'success', + value: initialCacheRetrievalMs, + }); + this.logger.perf( `[install-perf] Runner initial cache retrieval (getOrRecomputeManyOrAllFlatEntityMaps) took ${initialCacheRetrievalMs.toFixed(1)}ms for ${allFlatEntityMapsKeys.length} flat-maps keys`, 'Runner', @@ -354,6 +421,18 @@ export class WorkspaceMigrationRunnerService { const commitMs = performance.now() - commitStart; const transactionMs = performance.now() - transactionStart; + this.recordRunPhaseMetric({ + phase: 'action-execution', + status: 'success', + value: transactionMs - commitMs, + }); + + this.recordRunPhaseMetric({ + phase: 'commit', + status: 'success', + value: commitMs, + }); + this.logger.perf( `[install-perf] Runner transaction summary: ${actionCount} actions, total transaction ${transactionMs.toFixed(1)}ms (commit ${commitMs.toFixed(1)}ms), slowest action ${slowestActionLabel} ${slowestActionMs.toFixed(1)}ms`, 'Runner', @@ -361,6 +440,12 @@ export class WorkspaceMigrationRunnerService { this.logger.perfTimeEnd('Runner', 'Transaction execution'); } catch (error) { + this.recordRunPhaseMetric({ + phase: 'action-execution', + status: 'fail', + value: performance.now() - transactionStart, + }); + this.logger.error( `[install-perf] migration failed after ${actionCount} action(s): ${ error instanceof Error ? error.message : String(error) @@ -433,7 +518,19 @@ export class WorkspaceMigrationRunnerService { allFlatEntityMapsKeys, workspaceId, }); + + this.recordRunPhaseMetric({ + phase: 'cache-invalidation', + status: 'success', + value: performance.now() - postCommitInvalidateStart, + }); } catch (cacheError) { + this.recordRunPhaseMetric({ + phase: 'cache-invalidation', + status: 'fail', + value: performance.now() - postCommitInvalidateStart, + }); + this.logger.error( `Cache invalidation failed after committed transaction: ${cacheError}`, 'Runner', diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module.ts index 2812a3c067..8986b11bc0 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module.ts @@ -5,6 +5,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; +import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; @@ -27,6 +28,7 @@ import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/wo WorkspaceCacheModule, TypeOrmModule.forFeature([WorkspaceEntity]), WorkspaceIteratorModule, + MetricsModule, ], providers: [ WorkspaceMigrationRunnerService, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration.module.ts index feb4325fbe..aeb6e1ff40 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration.module.ts @@ -1,5 +1,6 @@ import { Module } from '@nestjs/common'; +import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; import { MetadataSideEffectModule } from 'src/engine/metadata-modules/metadata-side-effect/metadata-side-effect.module'; import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; import { WorkspaceMigrationBuildOrchestratorService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service'; @@ -14,6 +15,7 @@ import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/wor WorkspaceMigrationRunnerModule, WorkspaceCacheModule, MetadataSideEffectModule, + MetricsModule, ], providers: [ WorkspaceMigrationValidateBuildAndRunService,