1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-26 01:17:00 +02:00

fix: make project ui query optimized (#8961)

From 13 seconds to 0.1 seconds.

1. Joining 1 million events to projects/features is slow. **Solved by
using CTE.**
2. Running grouping on 1 million rows is slow. **Solved by adding
index.**
This commit is contained in:
Jaanus Sellin 2024-12-12 08:41:10 +02:00 committed by GitHub
parent c3f50263c8
commit 17d3b5c2fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 36 additions and 7 deletions

View File

@ -46,6 +46,7 @@ afterAll(async () => {
beforeEach(async () => { beforeEach(async () => {
await projectStore.deleteAll(); await projectStore.deleteAll();
await flagStore.deleteAll(); await flagStore.deleteAll();
await eventStore.deleteAll();
}); });
test("it doesn't count flags multiple times when they have multiple events associated with them", async () => { test("it doesn't count flags multiple times when they have multiple events associated with them", async () => {

View File

@ -83,7 +83,14 @@ export class ProjectReadModel implements IProjectReadModel {
userId?: number, userId?: number,
): Promise<ProjectForUi[]> { ): Promise<ProjectForUi[]> {
const projectTimer = this.timer('getProjectsForAdminUi'); const projectTimer = this.timer('getProjectsForAdminUi');
let projects = this.db(TABLE) let projects = this.db
.with('latest_events', (qb) => {
qb.select('project', 'feature_name')
.max('created_at as last_updated')
.whereNotNull('feature_name')
.from('events')
.groupBy('project', 'feature_name');
})
.leftJoin('features', 'features.project', 'projects.id') .leftJoin('features', 'features.project', 'projects.id')
.leftJoin( .leftJoin(
'last_seen_at_metrics', 'last_seen_at_metrics',
@ -95,13 +102,14 @@ export class ProjectReadModel implements IProjectReadModel {
'project_settings.project', 'project_settings.project',
'projects.id', 'projects.id',
) )
.leftJoin('events', (join) => { .leftJoin('latest_events', (join) => {
join.on('events.feature_name', '=', 'features.name').andOn( join.on(
'events.project', 'latest_events.feature_name',
'=', '=',
'projects.id', 'features.name',
); ).andOn('latest_events.project', '=', 'projects.id');
}) })
.from(TABLE)
.orderBy('projects.name', 'asc'); .orderBy('projects.name', 'asc');
if (query?.archived === true) { if (query?.archived === true) {
@ -122,7 +130,7 @@ export class ProjectReadModel implements IProjectReadModel {
'projects.id, projects.name, projects.description, projects.health, projects.created_at, ' + 'projects.id, projects.name, projects.description, projects.health, projects.created_at, ' +
'count(DISTINCT features.name) FILTER (WHERE features.archived_at is null) AS number_of_features, ' + 'count(DISTINCT features.name) FILTER (WHERE features.archived_at is null) AS number_of_features, ' +
'MAX(last_seen_at_metrics.last_seen_at) AS last_usage, ' + 'MAX(last_seen_at_metrics.last_seen_at) AS last_usage, ' +
'MAX(events.created_at) AS last_updated', 'MAX(latest_events.last_updated) AS last_updated',
), ),
'project_settings.project_mode', 'project_settings.project_mode',
'projects.archived_at', 'projects.archived_at',

View File

@ -0,0 +1,20 @@
'use strict';
exports.up = function (db, callback) {
db.runSql(
`
CREATE INDEX idx_events_project_feature_created
ON events (project, feature_name, created_at DESC);
`,
callback,
);
};
exports.down = function (db, callback) {
db.runSql(
`
DROP INDEX idx_events_project_feature_created;
`,
callback,
);
};