mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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