1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-04 13:48:56 +02:00

feat: add query timer to event store (#10306)

I noticed event search, as it is doing `ILIKE` search, is slow
sometimes. Lets get some statistics about it.

Meanwhile added timers for other interesting queries.
This commit is contained in:
Jaanus Sellin 2025-07-03 15:32:56 +03:00 committed by GitHub
parent dcce90ccb1
commit f789378cab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -28,6 +28,8 @@ import type { ProjectActivitySchema } from '../../openapi/index.js';
import type { IQueryParam } from '../feature-toggle/types/feature-toggle-strategies-store-type.js';
import { applyGenericQueryParams } from '../feature-search/search-utils.js';
import type { ITag } from '../../tags/index.js';
import metricsHelper from '../../util/metrics-helper.js';
import { DB_TIME } from '../../metric-events.js';
const EVENT_COLUMNS = [
'id',
@ -113,26 +115,38 @@ export class EventStore implements IEventStore {
private logger: Logger;
private metricTimer: Function;
// a new DB has to be injected per transaction
constructor(db: Db, getLogger: LogProvider) {
this.db = db;
this.logger = getLogger('event-store');
this.metricTimer = (action) =>
metricsHelper.wrapTimer(this.eventEmitter, DB_TIME, {
store: 'event',
action,
});
}
async store(event: IBaseEvent): Promise<void> {
const stopTimer = this.metricTimer('store');
try {
await this.db(TABLE)
.insert(this.eventToDbRow(event))
.returning(EVENT_COLUMNS);
} catch (error: unknown) {
this.logger.warn(`Failed to store "${event.type}" event: ${error}`);
} finally {
stopTimer();
}
}
async count(): Promise<number> {
const stopTimer = this.metricTimer('count');
const count = await this.db(TABLE)
.count<Record<string, number>>()
.first();
stopTimer();
if (!count) {
return 0;
}
@ -147,8 +161,10 @@ export class EventStore implements IEventStore {
queryParams: IQueryParam[],
query?: IEventSearchParams['query'],
): Promise<number> {
const stopTimer = this.metricTimer('searchEventsCount');
const searchQuery = this.buildSearchQuery(queryParams, query);
const count = await searchQuery.count().first();
stopTimer();
if (!count) {
return 0;
}
@ -160,6 +176,7 @@ export class EventStore implements IEventStore {
}
async batchStore(events: IBaseEvent[]): Promise<void> {
const stopTimer = this.metricTimer('batchStore');
try {
await this.db(TABLE).insert(
events.map((event) => this.eventToDbRow(event)),
@ -169,10 +186,13 @@ export class EventStore implements IEventStore {
`Failed to store events: ${JSON.stringify(events)}`,
error,
);
} finally {
stopTimer();
}
}
async getMaxRevisionId(largerThan: number = 0): Promise<number> {
const stopTimer = this.metricTimer('getMaxRevisionId');
const row = await this.db(TABLE)
.max('id')
.where((builder) =>
@ -193,10 +213,12 @@ export class EventStore implements IEventStore {
)
.andWhere('id', '>=', largerThan)
.first();
stopTimer();
return row?.max ?? 0;
}
async getRevisionRange(start: number, end: number): Promise<IEvent[]> {
const stopTimer = this.metricTimer('getRevisionRange');
const query = this.db
.select(EVENT_COLUMNS)
.from(TABLE)
@ -246,6 +268,7 @@ export class EventStore implements IEventStore {
}
async query(operations: IQueryOperations[]): Promise<IEvent[]> {
const stopTimer = this.metricTimer('query');
try {
let query: Knex.QueryBuilder = this.select();
@ -271,10 +294,13 @@ export class EventStore implements IEventStore {
return rows.map(this.rowToEvent);
} catch (e) {
return [];
} finally {
stopTimer();
}
}
async queryCount(operations: IQueryOperations[]): Promise<number> {
const stopTimer = this.metricTimer('queryCount');
try {
let query: Knex.QueryBuilder = this.db.count().from(TABLE);
@ -300,6 +326,8 @@ export class EventStore implements IEventStore {
return Number.parseInt(queryResult.count || 0);
} catch (e) {
return 0;
} finally {
stopTimer();
}
}
@ -355,6 +383,7 @@ export class EventStore implements IEventStore {
}
async getEvents(query?: Object): Promise<IEvent[]> {
const stopTimer = this.metricTimer('getEvents');
try {
let qB = this.db
.select(EVENT_COLUMNS)
@ -371,6 +400,8 @@ export class EventStore implements IEventStore {
return rows.map(this.rowToEvent);
} catch (err) {
return [];
} finally {
stopTimer();
}
}
@ -379,6 +410,7 @@ export class EventStore implements IEventStore {
queryParams: IQueryParam[],
options?: { withIp?: boolean },
): Promise<IEvent[]> {
const stopTimer = this.metricTimer('searchEvents');
const query = this.buildSearchQuery(queryParams, params.query)
.select(options?.withIp ? [...EVENT_COLUMNS, 'ip'] : EVENT_COLUMNS)
.orderBy([
@ -396,6 +428,8 @@ export class EventStore implements IEventStore {
);
} catch (err) {
return [];
} finally {
stopTimer();
}
}
@ -420,6 +454,7 @@ export class EventStore implements IEventStore {
}
async getEventCreators(): Promise<Array<{ id: number; name: string }>> {
const stopTimer = this.metricTimer('getEventCreators');
const query = this.db('events')
.distinctOn('events.created_by_user_id')
.leftJoin('users', 'users.id', '=', 'events.created_by_user_id')
@ -437,6 +472,7 @@ export class EventStore implements IEventStore {
]);
const result = await query;
stopTimer();
return result
.filter((row: any) => row.name || row.username || row.email)
.map((row: any) => ({
@ -448,6 +484,7 @@ export class EventStore implements IEventStore {
async getProjectRecentEventActivity(
project: string,
): Promise<ProjectActivitySchema> {
const stopTimer = this.metricTimer('getProjectRecentEventActivity');
const result = await this.db('events')
.select(
this.db.raw("TO_CHAR(created_at::date, 'YYYY-MM-DD') AS date"),
@ -462,6 +499,7 @@ export class EventStore implements IEventStore {
.groupBy(this.db.raw("TO_CHAR(created_at::date, 'YYYY-MM-DD')"))
.orderBy('date', 'asc');
stopTimer();
return result.map((row) => ({
date: row.date,
count: Number(row.count),
@ -531,10 +569,12 @@ export class EventStore implements IEventStore {
}
async setUnannouncedToAnnounced(): Promise<IEvent[]> {
const stopTimer = this.metricTimer('setUnannouncedToAnnounced');
const rows = await this.db(TABLE)
.update({ announced: true })
.where('announced', false)
.returning(EVENT_COLUMNS);
stopTimer();
return rows.map(this.rowToEvent);
}
@ -545,6 +585,7 @@ export class EventStore implements IEventStore {
}
async setCreatedByUserId(batchSize: number): Promise<number | undefined> {
const stopTimer = this.metricTimer('setCreatedByUserId');
const API_TOKEN_TABLE = 'api_tokens';
const toUpdate = await this.db(`${TABLE} as e`)
@ -592,6 +633,7 @@ export class EventStore implements IEventStore {
});
await Promise.all(updatePromises);
stopTimer();
return toUpdate.length;
}
}