App health check: Optimize pending migration query (#11049)

## Optimization: Efficient Health Check Query

This PR optimizes the workspace health check system by replacing the N+1
query pattern with efficient database queries.

### Key Improvements

- **Eliminated N+1 Query Problem**: Instead of fetching all workspaces
and then querying each one individually for pending migrations (which
caused slowness in production), we now use a single optimized query to
directly identify workspaces with pending migrations

- **Better Performance**: Reduced the number of database queries from
potentially hundreds/thousands (previous implementation) to just 2 fixed
queries regardless of workspace count

- **Full Coverage Instead of Sampling**: Rather than implementing a cap
on workspace checks at 100 (which was a workaround for performance
issues), this solution addresses the root cause by optimizing the query
pattern. We can now efficiently check all workspaces with pending
migrations without performance penalties.

@FelixMalfait This addresses the "always eager-load when you can"
feedback by handling the problem at the database level rather than just
applying a limit. The optimized query should solve both the performance
issues and provide more accurate health status information.
This commit is contained in:
nitin
2025-03-21 21:00:38 +05:30
committed by GitHub
parent 081f5fa766
commit 3960b0f99d
4 changed files with 170 additions and 87 deletions

View File

@ -49,6 +49,42 @@ export class WorkspaceMigrationService {
});
}
/**
* Find workspaces with pending migrations
*
* @returns Promise<{ workspaceId: string; pendingMigrations: number }[]>
*/
public async getWorkspacesWithPendingMigrations(limit: number) {
const results = await this.workspaceMigrationRepository
.createQueryBuilder('workspaceMigration')
.select('workspaceMigration.workspaceId', 'workspaceId')
.addSelect('COUNT(*)', 'pendingCount')
.where('workspaceMigration.appliedAt IS NULL')
.groupBy('workspaceMigration.workspaceId')
.limit(limit)
.getRawMany();
return results.map((result) => ({
workspaceId: result.workspaceId,
pendingMigrations: Number(result.pendingCount) || 0,
}));
}
/**
* Count total number of workspaces with pending migrations
*
* @returns Promise<number>
*/
public async countWorkspacesWithPendingMigrations(): Promise<number> {
const result = await this.workspaceMigrationRepository
.createQueryBuilder('workspaceMigration')
.select('COUNT(DISTINCT workspaceMigration.workspaceId)', 'count')
.where('workspaceMigration.appliedAt IS NULL')
.getRawOne();
return Number(result.count) || 0;
}
/**
* Set appliedAt as current date for a given migration.
* Should be called once the migration has been applied