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:
parent
dcce90ccb1
commit
f789378cab
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user