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