forge-sql-orm 2.1.16 → 2.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +190 -1
- package/dist/async/PrintQueryConsumer.d.ts +98 -0
- package/dist/async/PrintQueryConsumer.d.ts.map +1 -0
- package/dist/async/PrintQueryConsumer.js +89 -0
- package/dist/async/PrintQueryConsumer.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/forgeDriverProxy.js +7 -5
- package/dist/utils/forgeDriverProxy.js.map +1 -1
- package/dist/utils/metadataContextUtils.d.ts +44 -4
- package/dist/utils/metadataContextUtils.d.ts.map +1 -1
- package/dist/utils/metadataContextUtils.js +102 -19
- package/dist/utils/metadataContextUtils.js.map +1 -1
- package/dist/utils/sqlUtils.d.ts +2 -1
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/utils/sqlUtils.js +47 -25
- package/dist/utils/sqlUtils.js.map +1 -1
- package/package.json +13 -12
- package/src/async/PrintQueryConsumer.ts +114 -0
- package/src/index.ts +1 -0
- package/src/utils/forgeDriverProxy.ts +9 -9
- package/src/utils/metadataContextUtils.ts +120 -26
- package/src/utils/sqlUtils.ts +74 -35
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { forgeDriver } from "./forgeDriver";
|
|
2
2
|
import { injectSqlHints, SqlHints } from "./sqlHints";
|
|
3
3
|
import { ForgeSqlOperation } from "../core/ForgeSQLQueryBuilder";
|
|
4
|
-
import {
|
|
4
|
+
import { handleErrorsWithPlan } from "./sqlUtils";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Error codes and constants for query analysis
|
|
@@ -57,22 +57,22 @@ export function createForgeDriverProxy(
|
|
|
57
57
|
error?.context?.debug?.errno === QUERY_ERROR_CODES.OUT_OF_MEMORY_ERRNO;
|
|
58
58
|
|
|
59
59
|
if (isTimeoutError || isOutOfMemoryError) {
|
|
60
|
+
// Wait for CLUSTER_STATEMENTS_SUMMARY to be populated with our failed query data
|
|
61
|
+
await new Promise((resolve) => setTimeout(resolve, STATEMENTS_SUMMARY_DELAY_MS));
|
|
62
|
+
|
|
63
|
+
const queryEndTime = Date.now();
|
|
64
|
+
const queryDuration = queryEndTime - queryStartTime;
|
|
65
|
+
let errorType: "OOM" | "TIMEOUT" = "TIMEOUT";
|
|
60
66
|
if (isTimeoutError) {
|
|
61
67
|
// eslint-disable-next-line no-console
|
|
62
68
|
console.error(` TIMEOUT detected - Query exceeded time limit`);
|
|
63
69
|
} else {
|
|
64
70
|
// eslint-disable-next-line no-console
|
|
65
71
|
console.error(`OUT OF MEMORY detected - Query exceeded memory limit`);
|
|
72
|
+
errorType = "OOM";
|
|
66
73
|
}
|
|
67
|
-
|
|
68
|
-
// Wait for CLUSTER_STATEMENTS_SUMMARY to be populated with our failed query data
|
|
69
|
-
await new Promise((resolve) => setTimeout(resolve, STATEMENTS_SUMMARY_DELAY_MS));
|
|
70
|
-
|
|
71
|
-
const queryEndTime = Date.now();
|
|
72
|
-
const queryDuration = queryEndTime - queryStartTime;
|
|
73
|
-
|
|
74
74
|
// Analyze the failed query using CLUSTER_STATEMENTS_SUMMARY
|
|
75
|
-
await
|
|
75
|
+
await handleErrorsWithPlan(forgeSqlOperation, queryDuration, errorType);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// Log SQL error details if requested
|
|
@@ -2,12 +2,15 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
2
2
|
import { ForgeSQLMetadata } from "./forgeDriver";
|
|
3
3
|
import { ForgeSqlOperation } from "../core/ForgeSQLQueryBuilder";
|
|
4
4
|
import { ExplainAnalyzeRow } from "../core/SystemTables";
|
|
5
|
-
import { printQueriesWithPlan } from "./sqlUtils";
|
|
5
|
+
import { printQueriesWithPlan, withTimeout } from "./sqlUtils";
|
|
6
6
|
import { Parser } from "node-sql-parser";
|
|
7
|
+
import { PushResult, Queue } from "@forge/events";
|
|
8
|
+
import { AsyncEventPrintQuery } from "../async/PrintQueryConsumer";
|
|
7
9
|
|
|
10
|
+
const TIMEOUT_ASYNC_EVENT_SENT = 1200;
|
|
8
11
|
const DEFAULT_WINDOW_SIZE = 15 * 1000;
|
|
9
12
|
|
|
10
|
-
type Statistic = { query: string; params: unknown[]; metadata: ForgeSQLMetadata };
|
|
13
|
+
export type Statistic = { query: string; params: unknown[]; metadata: ForgeSQLMetadata };
|
|
11
14
|
|
|
12
15
|
export type QueryPlanMode = "TopSlowest" | "SummaryTable";
|
|
13
16
|
|
|
@@ -17,6 +20,7 @@ export type MetadataQueryOptions = {
|
|
|
17
20
|
topQueries?: number;
|
|
18
21
|
showSlowestPlans?: boolean;
|
|
19
22
|
normalizeQuery?: boolean;
|
|
23
|
+
asyncQueueName?: string;
|
|
20
24
|
};
|
|
21
25
|
|
|
22
26
|
export type MetadataQueryContext = {
|
|
@@ -42,6 +46,7 @@ function createDefaultOptions(): Required<MetadataQueryOptions> {
|
|
|
42
46
|
summaryTableWindowTime: DEFAULT_WINDOW_SIZE,
|
|
43
47
|
showSlowestPlans: true,
|
|
44
48
|
normalizeQuery: true,
|
|
49
|
+
asyncQueueName: "",
|
|
45
50
|
};
|
|
46
51
|
}
|
|
47
52
|
|
|
@@ -58,6 +63,7 @@ function mergeOptionsWithDefaults(options?: MetadataQueryOptions): Required<Meta
|
|
|
58
63
|
summaryTableWindowTime: options?.summaryTableWindowTime ?? defaults.summaryTableWindowTime,
|
|
59
64
|
showSlowestPlans: options?.showSlowestPlans ?? defaults.showSlowestPlans,
|
|
60
65
|
normalizeQuery: options?.normalizeQuery ?? defaults.normalizeQuery,
|
|
66
|
+
asyncQueueName: options?.asyncQueueName ?? defaults.asyncQueueName,
|
|
61
67
|
};
|
|
62
68
|
}
|
|
63
69
|
|
|
@@ -196,16 +202,21 @@ function formatExplainPlan(planRows: ExplainAnalyzeRow[]): string {
|
|
|
196
202
|
|
|
197
203
|
/**
|
|
198
204
|
* Prints query plans using summary tables if mode is SummaryTable and within time window.
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
205
|
+
*
|
|
206
|
+
* Attempts to use CLUSTER_STATEMENTS_SUMMARY table for query analysis if:
|
|
207
|
+
* - Mode is set to "SummaryTable"
|
|
208
|
+
* - Time since query execution start is within the configured window
|
|
209
|
+
*
|
|
210
|
+
* @param context - The async event payload containing query statistics and options
|
|
211
|
+
* @param forgeSQLORM - The ForgeSQL operation instance for database access
|
|
212
|
+
* @returns Promise that resolves to true if summary tables were used, false otherwise
|
|
202
213
|
*/
|
|
203
214
|
async function printPlansUsingSummaryTables(
|
|
204
|
-
context:
|
|
205
|
-
|
|
215
|
+
context: AsyncEventPrintQuery,
|
|
216
|
+
forgeSQLORM: ForgeSqlOperation,
|
|
206
217
|
): Promise<boolean> {
|
|
207
218
|
const timeDiff = Date.now() - context.beginTime.getTime();
|
|
208
|
-
|
|
219
|
+
const options = context.options;
|
|
209
220
|
if (options.mode !== "SummaryTable") {
|
|
210
221
|
return false;
|
|
211
222
|
}
|
|
@@ -213,7 +224,7 @@ async function printPlansUsingSummaryTables(
|
|
|
213
224
|
if (timeDiff <= options.summaryTableWindowTime) {
|
|
214
225
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
215
226
|
const summaryTableDiffMs = Date.now() - context.beginTime.getTime();
|
|
216
|
-
await printQueriesWithPlan(
|
|
227
|
+
await printQueriesWithPlan(forgeSQLORM, summaryTableDiffMs);
|
|
217
228
|
return true;
|
|
218
229
|
}
|
|
219
230
|
// eslint-disable-next-line no-console
|
|
@@ -222,15 +233,20 @@ async function printPlansUsingSummaryTables(
|
|
|
222
233
|
}
|
|
223
234
|
|
|
224
235
|
/**
|
|
225
|
-
* Prints query plans for the top slowest queries.
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
236
|
+
* Prints query plans for the top slowest queries from the statistics.
|
|
237
|
+
*
|
|
238
|
+
* Sorts queries by execution time and prints the top N queries (based on topQueries option).
|
|
239
|
+
* For each query, it can optionally print the execution plan using EXPLAIN ANALYZE.
|
|
240
|
+
*
|
|
241
|
+
* @param context - The async event payload containing query statistics and options
|
|
242
|
+
* @param forgeSQLORM - The ForgeSQL operation instance for database access
|
|
243
|
+
* @returns Promise that resolves when all query plans are printed
|
|
229
244
|
*/
|
|
230
245
|
async function printTopQueriesPlans(
|
|
231
|
-
context:
|
|
232
|
-
|
|
246
|
+
context: AsyncEventPrintQuery,
|
|
247
|
+
forgeSQLORM: ForgeSqlOperation,
|
|
233
248
|
): Promise<void> {
|
|
249
|
+
const options = context.options;
|
|
234
250
|
const topQueries = context.statistics
|
|
235
251
|
.toSorted((a, b) => b.metadata.dbExecutionTime - a.metadata.dbExecutionTime)
|
|
236
252
|
.slice(0, options.topQueries);
|
|
@@ -240,7 +256,7 @@ async function printTopQueriesPlans(
|
|
|
240
256
|
? normalizeSqlForLogging(query.query)
|
|
241
257
|
: query.query;
|
|
242
258
|
if (options.showSlowestPlans) {
|
|
243
|
-
const explainAnalyzeRows = await
|
|
259
|
+
const explainAnalyzeRows = await forgeSQLORM
|
|
244
260
|
.analyze()
|
|
245
261
|
.explainAnalyzeRaw(query.query, query.params);
|
|
246
262
|
const formattedPlan = formatExplainPlan(explainAnalyzeRows);
|
|
@@ -257,9 +273,33 @@ async function printTopQueriesPlans(
|
|
|
257
273
|
|
|
258
274
|
/**
|
|
259
275
|
* Saves query metadata to the current context and sets up the printQueriesWithPlan function.
|
|
276
|
+
*
|
|
277
|
+
* This function accumulates query statistics in the async context. When printQueriesWithPlan
|
|
278
|
+
* is called, it can either:
|
|
279
|
+
* - Queue the analysis for async processing (if asyncQueueName is provided)
|
|
280
|
+
* - Execute the analysis synchronously (fallback or if asyncQueueName is not set)
|
|
281
|
+
*
|
|
282
|
+
* For async processing, the function sends an event to the specified queue with a timeout.
|
|
283
|
+
* If the event cannot be sent within the timeout, it falls back to synchronous execution.
|
|
284
|
+
*
|
|
260
285
|
* @param stringQuery - The SQL query string
|
|
261
|
-
* @param params - Query parameters
|
|
262
|
-
* @param metadata - Query execution metadata
|
|
286
|
+
* @param params - Query parameters used in the query
|
|
287
|
+
* @param metadata - Query execution metadata including execution time and response size
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```typescript
|
|
291
|
+
* await FORGE_SQL_ORM.executeWithMetadata(
|
|
292
|
+
* async () => {
|
|
293
|
+
* // ... queries ...
|
|
294
|
+
* },
|
|
295
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueries) => {
|
|
296
|
+
* if (totalDbExecutionTime > threshold) {
|
|
297
|
+
* await printQueries(); // Will use async queue if configured
|
|
298
|
+
* }
|
|
299
|
+
* },
|
|
300
|
+
* { asyncQueueName: "degradationQueue" }
|
|
301
|
+
* );
|
|
302
|
+
* ```
|
|
263
303
|
*/
|
|
264
304
|
export async function saveMetaDataToContext(
|
|
265
305
|
stringQuery: string,
|
|
@@ -285,15 +325,41 @@ export async function saveMetaDataToContext(
|
|
|
285
325
|
// Set up printQueriesWithPlan function
|
|
286
326
|
context.printQueriesWithPlan = async () => {
|
|
287
327
|
const options = mergeOptionsWithDefaults(context.options);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
328
|
+
const param: AsyncEventPrintQuery = {
|
|
329
|
+
statistics: context.statistics,
|
|
330
|
+
totalDbExecutionTime: context.totalDbExecutionTime,
|
|
331
|
+
totalResponseSize: context.totalResponseSize,
|
|
332
|
+
beginTime: context.beginTime,
|
|
333
|
+
options,
|
|
334
|
+
};
|
|
335
|
+
if (options.asyncQueueName) {
|
|
336
|
+
const queue = new Queue({ key: options.asyncQueueName });
|
|
337
|
+
try {
|
|
338
|
+
const eventInfo = await withTimeout<PushResult>(
|
|
339
|
+
queue.push({
|
|
340
|
+
body: param,
|
|
341
|
+
concurrency: {
|
|
342
|
+
key: "orm_" + options.asyncQueueName,
|
|
343
|
+
limit: 2,
|
|
344
|
+
},
|
|
345
|
+
}),
|
|
346
|
+
`Event was not sent within ${TIMEOUT_ASYNC_EVENT_SENT}ms`,
|
|
347
|
+
TIMEOUT_ASYNC_EVENT_SENT,
|
|
348
|
+
);
|
|
349
|
+
// eslint-disable-next-line no-console
|
|
350
|
+
console.warn(
|
|
351
|
+
`[Performance Analysis] Query degradation event queued for async processing | Job ID: ${eventInfo.jobId} | Total DB time: ${context.totalDbExecutionTime}ms | Queries: ${context.statistics.length} | Look for consumer log with jobId: ${eventInfo.jobId}`,
|
|
352
|
+
);
|
|
353
|
+
return;
|
|
354
|
+
} catch (e: any) {
|
|
355
|
+
// eslint-disable-next-line no-console
|
|
356
|
+
console.warn(
|
|
357
|
+
"Async printing failed — falling back to synchronous execution: " + e.message,
|
|
358
|
+
e,
|
|
359
|
+
);
|
|
360
|
+
}
|
|
293
361
|
}
|
|
294
|
-
|
|
295
|
-
// Fall back to printing top queries plans
|
|
296
|
-
await printTopQueriesPlans(context, options);
|
|
362
|
+
await printDegradationQueries(context.forgeSQLORM, param);
|
|
297
363
|
};
|
|
298
364
|
|
|
299
365
|
// Update aggregated metrics
|
|
@@ -303,6 +369,34 @@ export async function saveMetaDataToContext(
|
|
|
303
369
|
}
|
|
304
370
|
}
|
|
305
371
|
|
|
372
|
+
/**
|
|
373
|
+
* Prints query degradation analysis for the provided event payload.
|
|
374
|
+
*
|
|
375
|
+
* This function processes query degradation events (either from async queue or synchronous call).
|
|
376
|
+
* It first attempts to use summary tables (CLUSTER_STATEMENTS_SUMMARY) if configured and within
|
|
377
|
+
* the time window. Otherwise, it falls back to printing execution plans for the top slowest queries.
|
|
378
|
+
*
|
|
379
|
+
* @param forgeSQLORM - The ForgeSQL operation instance for database access
|
|
380
|
+
* @param params - The async event payload containing query statistics, options, and metadata
|
|
381
|
+
* @returns Promise that resolves when query analysis is complete
|
|
382
|
+
*
|
|
383
|
+
* @see printPlansUsingSummaryTables - For summary table analysis
|
|
384
|
+
* @see printTopQueriesPlans - For top slowest queries analysis
|
|
385
|
+
*/
|
|
386
|
+
export async function printDegradationQueries(
|
|
387
|
+
forgeSQLORM: ForgeSqlOperation,
|
|
388
|
+
params: AsyncEventPrintQuery,
|
|
389
|
+
): Promise<void> {
|
|
390
|
+
// Try to use summary tables first if enabled
|
|
391
|
+
const usedSummaryTables = await printPlansUsingSummaryTables(params, forgeSQLORM);
|
|
392
|
+
if (usedSummaryTables) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Fall back to printing top queries plans
|
|
397
|
+
await printTopQueriesPlans(params, forgeSQLORM);
|
|
398
|
+
}
|
|
399
|
+
|
|
306
400
|
/**
|
|
307
401
|
* Gets the latest metadata from the current context.
|
|
308
402
|
* @returns The current metadata context or undefined if not in a context
|
package/src/utils/sqlUtils.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
and,
|
|
3
3
|
AnyColumn,
|
|
4
4
|
Column,
|
|
5
|
+
desc,
|
|
5
6
|
gte,
|
|
6
7
|
ilike,
|
|
7
8
|
isNotNull,
|
|
@@ -711,6 +712,39 @@ export function nextVal(sequenceName: string): number {
|
|
|
711
712
|
return sql.raw(`NEXTVAL(${sequenceName})`) as unknown as number;
|
|
712
713
|
}
|
|
713
714
|
|
|
715
|
+
/**
|
|
716
|
+
* Helper function to build base query for CLUSTER_STATEMENTS_SUMMARY table
|
|
717
|
+
*/
|
|
718
|
+
function buildClusterStatementsSummaryQuery(forgeSQLORM: ForgeSqlOperation, timeDiffMs: number) {
|
|
719
|
+
const statementsTable = clusterStatementsSummary;
|
|
720
|
+
return forgeSQLORM
|
|
721
|
+
.getDrizzleQueryBuilder()
|
|
722
|
+
.select({
|
|
723
|
+
digestText: withTidbHint(statementsTable.digestText),
|
|
724
|
+
avgLatency: statementsTable.avgLatency,
|
|
725
|
+
avgMem: statementsTable.avgMem,
|
|
726
|
+
execCount: statementsTable.execCount,
|
|
727
|
+
plan: statementsTable.plan,
|
|
728
|
+
stmtType: statementsTable.stmtType,
|
|
729
|
+
})
|
|
730
|
+
.from(statementsTable)
|
|
731
|
+
.where(
|
|
732
|
+
and(
|
|
733
|
+
isNotNull(statementsTable.digest),
|
|
734
|
+
not(ilike(statementsTable.digestText, "%information_schema%")),
|
|
735
|
+
notInArray(statementsTable.stmtType, ["Use", "Set", "Show", "Commit", "Rollback", "Begin"]),
|
|
736
|
+
gte(
|
|
737
|
+
statementsTable.lastSeen,
|
|
738
|
+
sql`DATE_SUB
|
|
739
|
+
(NOW(), INTERVAL
|
|
740
|
+
${timeDiffMs * 1000}
|
|
741
|
+
MICROSECOND
|
|
742
|
+
)`,
|
|
743
|
+
),
|
|
744
|
+
),
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
|
|
714
748
|
/**
|
|
715
749
|
* Analyzes and prints query performance data from CLUSTER_STATEMENTS_SUMMARY table.
|
|
716
750
|
*
|
|
@@ -724,7 +758,7 @@ export function nextVal(sequenceName: string): number {
|
|
|
724
758
|
*
|
|
725
759
|
* @param forgeSQLORM - The ForgeSQL operation instance for database access
|
|
726
760
|
* @param timeDiffMs - Time window in milliseconds to look back for queries (e.g., 1500 for last 1.5 seconds)
|
|
727
|
-
* @param timeout - Optional timeout in milliseconds for the query execution (defaults to
|
|
761
|
+
* @param timeout - Optional timeout in milliseconds for the query execution (defaults to 3000ms)
|
|
728
762
|
*
|
|
729
763
|
* @example
|
|
730
764
|
* ```typescript
|
|
@@ -743,42 +777,9 @@ export async function printQueriesWithPlan(
|
|
|
743
777
|
timeout?: number,
|
|
744
778
|
) {
|
|
745
779
|
try {
|
|
746
|
-
const statementsTable = clusterStatementsSummary;
|
|
747
780
|
const timeoutMs = timeout ?? 3000;
|
|
748
781
|
const results = await withTimeout(
|
|
749
|
-
forgeSQLORM
|
|
750
|
-
.getDrizzleQueryBuilder()
|
|
751
|
-
.select({
|
|
752
|
-
digestText: withTidbHint(statementsTable.digestText),
|
|
753
|
-
avgLatency: statementsTable.avgLatency,
|
|
754
|
-
avgMem: statementsTable.avgMem,
|
|
755
|
-
execCount: statementsTable.execCount,
|
|
756
|
-
plan: statementsTable.plan,
|
|
757
|
-
stmtType: statementsTable.stmtType,
|
|
758
|
-
})
|
|
759
|
-
.from(statementsTable)
|
|
760
|
-
.where(
|
|
761
|
-
and(
|
|
762
|
-
isNotNull(statementsTable.digest),
|
|
763
|
-
not(ilike(statementsTable.digestText, "%information_schema%")),
|
|
764
|
-
notInArray(statementsTable.stmtType, [
|
|
765
|
-
"Use",
|
|
766
|
-
"Set",
|
|
767
|
-
"Show",
|
|
768
|
-
"Commit",
|
|
769
|
-
"Rollback",
|
|
770
|
-
"Begin",
|
|
771
|
-
]),
|
|
772
|
-
gte(
|
|
773
|
-
statementsTable.lastSeen,
|
|
774
|
-
sql`DATE_SUB
|
|
775
|
-
(NOW(), INTERVAL
|
|
776
|
-
${timeDiffMs * 1000}
|
|
777
|
-
MICROSECOND
|
|
778
|
-
)`,
|
|
779
|
-
),
|
|
780
|
-
),
|
|
781
|
-
),
|
|
782
|
+
buildClusterStatementsSummaryQuery(forgeSQLORM, timeDiffMs),
|
|
782
783
|
`Timeout ${timeoutMs}ms in printQueriesWithPlan - transient timeouts are usually fine; repeated timeouts mean this diagnostic query is consistently slow and should be investigated`,
|
|
783
784
|
timeoutMs + 200,
|
|
784
785
|
);
|
|
@@ -803,6 +804,44 @@ export async function printQueriesWithPlan(
|
|
|
803
804
|
}
|
|
804
805
|
}
|
|
805
806
|
|
|
807
|
+
export async function handleErrorsWithPlan(
|
|
808
|
+
forgeSQLORM: ForgeSqlOperation,
|
|
809
|
+
timeDiffMs: number,
|
|
810
|
+
type: "OOM" | "TIMEOUT",
|
|
811
|
+
) {
|
|
812
|
+
try {
|
|
813
|
+
const statementsTable = clusterStatementsSummary;
|
|
814
|
+
const timeoutMs = 3000;
|
|
815
|
+
const baseQuery = buildClusterStatementsSummaryQuery(forgeSQLORM, timeDiffMs);
|
|
816
|
+
const orderColumn = type === "OOM" ? statementsTable.avgMem : statementsTable.avgLatency;
|
|
817
|
+
const query = baseQuery.orderBy(desc(orderColumn)).limit(formatLimitOffset(1));
|
|
818
|
+
|
|
819
|
+
const results = await withTimeout(
|
|
820
|
+
query,
|
|
821
|
+
`Timeout ${timeoutMs}ms in handleErrorsWithPlan - transient timeouts are usually fine; repeated timeouts mean this diagnostic query is consistently slow and should be investigated`,
|
|
822
|
+
timeoutMs + 200,
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
for (const result of results) {
|
|
826
|
+
// Average execution time (convert from nanoseconds to milliseconds)
|
|
827
|
+
const avgTimeMs = Number(result.avgLatency) / 1_000_000;
|
|
828
|
+
const avgMemMB = Number(result.avgMem) / 1_000_000;
|
|
829
|
+
|
|
830
|
+
// 1. Query info: SQL, memory, time, executions
|
|
831
|
+
// eslint-disable-next-line no-console
|
|
832
|
+
console.warn(
|
|
833
|
+
`SQL: ${result.digestText} | Memory: ${avgMemMB.toFixed(2)} MB | Time: ${avgTimeMs.toFixed(2)} ms | stmtType: ${result.stmtType} | Executions: ${result.execCount}\n Plan:${result.plan}`,
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
} catch (error) {
|
|
837
|
+
// eslint-disable-next-line no-console
|
|
838
|
+
console.debug(
|
|
839
|
+
`Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}. Try again after some time`,
|
|
840
|
+
error,
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
806
845
|
const SESSION_ALIAS_NAME_ORM = "orm";
|
|
807
846
|
|
|
808
847
|
/**
|