From 0efa2585fed4f26fa4812d6d3c876364eb08b5ae Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Tue, 15 Apr 2025 09:33:13 +0200 Subject: [PATCH] fix: improve status job performance (#9755) --- src/lib/db/project-stats-store.ts | 2 ++ src/lib/features/project/project-service.ts | 32 +++++++++++++-------- src/lib/util/index.ts | 1 + 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/lib/db/project-stats-store.ts b/src/lib/db/project-stats-store.ts index 0cc1c47e2f..7bbd6cd9ea 100644 --- a/src/lib/db/project-stats-store.ts +++ b/src/lib/db/project-stats-store.ts @@ -117,6 +117,7 @@ class ProjectStatsStore implements IProjectStatsStore { async getTimeToProdDates( projectId: string, ): Promise { + const stopTimer = this.timer('getTimeToProdDates'); const result = await this.db .select('events.feature_name') // select only first enabled event, distinct works with orderBy @@ -143,6 +144,7 @@ class ProjectStatsStore implements IProjectStatsStore { .orderBy('events.feature_name') // first enabled event .orderBy('events.created_at', 'asc'); + stopTimer(); return result; } diff --git a/src/lib/features/project/project-service.ts b/src/lib/features/project/project-service.ts index f794e3f974..e5317b0910 100644 --- a/src/lib/features/project/project-service.ts +++ b/src/lib/features/project/project-service.ts @@ -89,6 +89,9 @@ import type EventEmitter from 'events'; import type { ApiTokenService } from '../../services/api-token-service'; import type { ProjectForUi } from './project-read-model-type'; import { canGrantProjectRole } from './can-grant-project-role'; +import { batchExecute } from '../../util/batchExecute'; +import metricsHelper from '../../util/metrics-helper'; +import { FUNCTION_TIME } from '../../metric-events'; type Days = number; type Count = number; @@ -167,6 +170,8 @@ export default class ProjectService { private onboardingReadModel: IOnboardingReadModel; + private timer: Function; + constructor( { projectStore, @@ -226,6 +231,11 @@ export default class ProjectService { this.eventBus = config.eventBus; this.projectReadModel = projectReadModel; this.onboardingReadModel = onboardingReadModel; + this.timer = (functionName: string) => + metricsHelper.wrapTimer(config.eventBus, FUNCTION_TIME, { + className: 'ProjectService', + functionName, + }); } async getProjects( @@ -1282,21 +1292,18 @@ export default class ProjectService { async statusJob(): Promise { const projects = await this.projectStore.getAll(); - const statusUpdates = await Promise.all( - projects.map((project) => this.getStatusUpdates(project.id)), - ); - - await Promise.all( - statusUpdates.map((statusUpdate) => { - return this.projectStatsStore.updateProjectStats( - statusUpdate.projectId, - statusUpdate.updates, - ); - }), - ); + // run one project status update at a time every + void batchExecute(projects, 1, 30_000, async (project) => { + const statusUpdate = await this.getStatusUpdates(project.id); + await this.projectStatsStore.updateProjectStats( + statusUpdate.projectId, + statusUpdate.updates, + ); + }); } async getStatusUpdates(projectId: string): Promise { + const stopTimer = this.timer('getStatusUpdates'); const dateMinusThirtyDays = subDays(new Date(), 30).toISOString(); const dateMinusSixtyDays = subDays(new Date(), 60).toISOString(); @@ -1370,6 +1377,7 @@ export default class ProjectService { dateMinusThirtyDays, ); + stopTimer(); return { projectId, updates: { diff --git a/src/lib/util/index.ts b/src/lib/util/index.ts index ce8d637652..f1cb174128 100644 --- a/src/lib/util/index.ts +++ b/src/lib/util/index.ts @@ -30,3 +30,4 @@ export * from './arraysHaveSameItems'; export * from './constantTimeCompare'; export * from '../features/metrics/client-metrics/collapseHourlyMetrics'; export * from '../features/playground/offline-unleash-client'; +export * from './batchExecute';