@@ -70,6 +70,71 @@ interface QueryLogRecord {
7070// drops unrelated requests while an asynchronous INSERT is still pending.
7171const queryLogContext = new AsyncLocalStorage < boolean > ( )
7272
73+ interface PendingQueryLog {
74+ record : QueryLogRecord
75+ resolve : ( ) => void
76+ }
77+
78+ const QUERY_LOG_BATCH_SIZE = 100
79+ const pendingQueryLogs : PendingQueryLog [ ] = [ ]
80+ let queryLogFlushScheduled = false
81+ let queryLogFlushInFlight = false
82+
83+ /** Persist query diagnostics in bounded multi-row inserts. */
84+ function enqueueQueryLog ( record : QueryLogRecord ) : Promise < void > {
85+ const settled = new Promise < void > ( ( resolve ) => {
86+ pendingQueryLogs . push ( { record, resolve } )
87+ } )
88+
89+ if ( pendingQueryLogs . length >= QUERY_LOG_BATCH_SIZE ) {
90+ void flushQueuedQueryLogs ( )
91+ }
92+ else if ( ! queryLogFlushScheduled ) {
93+ queryLogFlushScheduled = true
94+ setImmediate ( ( ) => {
95+ queryLogFlushScheduled = false
96+ void flushQueuedQueryLogs ( )
97+ } )
98+ }
99+
100+ return settled
101+ }
102+
103+ async function flushQueuedQueryLogs ( ) : Promise < void > {
104+ if ( queryLogFlushInFlight )
105+ return
106+
107+ queryLogFlushInFlight = true
108+ try {
109+ while ( pendingQueryLogs . length > 0 ) {
110+ const batch = pendingQueryLogs . splice ( 0 , QUERY_LOG_BATCH_SIZE )
111+ const recordsByShape = Map . groupBy (
112+ batch . map ( item => item . record ) ,
113+ record => Object . keys ( record ) . join ( '\0' ) ,
114+ )
115+ for ( const records of recordsByShape . values ( ) ) {
116+ const stored = await queryLogContext . run ( true , ( ) => storeQueryLogs ( records , records . length === 1 ) )
117+ if ( ! stored ) {
118+ for ( const record of records )
119+ await queryLogContext . run ( true , ( ) => storeQueryLogs ( [ record ] ) )
120+ }
121+ }
122+ for ( const item of batch )
123+ item . resolve ( )
124+ }
125+ }
126+ finally {
127+ queryLogFlushInFlight = false
128+ if ( pendingQueryLogs . length > 0 && ! queryLogFlushScheduled ) {
129+ queryLogFlushScheduled = true
130+ setImmediate ( ( ) => {
131+ queryLogFlushScheduled = false
132+ void flushQueuedQueryLogs ( )
133+ } )
134+ }
135+ }
136+ }
137+
73138/**
74139 * Process an executed query and store it in the database
75140 */
@@ -114,7 +179,7 @@ export async function logQuery(event: LogEvent): Promise<void> {
114179
115180 // Deferred query hooks inherit this context, so the INSERT cannot log
116181 // itself while other requests remain free to record their own queries.
117- await queryLogContext . run ( true , ( ) => storeQueryLog ( logRecord ) )
182+ await enqueueQueryLog ( logRecord )
118183
119184 // Log slow or failed queries to the application log
120185 if ( status !== 'completed' ) {
@@ -505,15 +570,28 @@ function generateOptimizationSuggestions(explainResult: any, logRecord: QueryLog
505570/**
506571 * Store the query log in the database
507572 */
508- async function storeQueryLog ( logRecord : QueryLogRecord ) : Promise < void > {
573+ async function storeQueryLogs ( logRecords : QueryLogRecord [ ] , reportFailure = true ) : Promise < boolean > {
509574 try {
510- await db . insertInto ( 'query_logs' ) . values ( logRecord as unknown as Record < string , unknown > ) . execute ( )
575+ const values = logRecords as unknown as Record < string , unknown > [ ]
576+ if ( values . length === 1 ) {
577+ await db . insertInto ( 'query_logs' ) . values ( values ) . execute ( )
578+ }
579+ else {
580+ await db . transaction ( async ( rawTrx ) => {
581+ const trx = rawTrx as unknown as typeof db
582+ await trx . insertInto ( 'query_logs' ) . values ( values ) . execute ( )
583+ } )
584+ }
585+ return true
511586 }
512587 catch ( error ) {
513- const message = error instanceof Error ? error . message : String ( error )
514- if ( / n o s u c h t a b l e | d o e s n o t e x i s t | d o e s n ' t e x i s t / i. test ( message ) && / q u e r y _ l o g s / i. test ( message ) )
515- log . debug ( 'Query logging will start after the query_logs table is migrated.' )
516- else
517- log . error ( 'Failed to store query log:' , error )
588+ if ( reportFailure ) {
589+ const message = error instanceof Error ? error . message : String ( error )
590+ if ( / n o s u c h t a b l e | d o e s n o t e x i s t | d o e s n ' t e x i s t / i. test ( message ) && / q u e r y _ l o g s / i. test ( message ) )
591+ log . debug ( 'Query logging will start after the query_logs table is migrated.' )
592+ else
593+ log . error ( 'Failed to store query log:' , error )
594+ }
595+ return false
518596 }
519597}
0 commit comments