forge-sql-orm 2.1.10 → 2.1.12
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 +356 -263
- package/dist/ForgeSQLORM.js +3263 -3226
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +3261 -3224
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts +66 -12
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +66 -11
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/SystemTables.d.ts +82 -82
- package/dist/utils/cacheUtils.d.ts.map +1 -1
- package/dist/utils/forgeDriver.d.ts.map +1 -1
- package/dist/utils/forgeDriverProxy.d.ts +6 -2
- package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
- package/dist/utils/metadataContextUtils.d.ts +5 -2
- package/dist/utils/metadataContextUtils.d.ts.map +1 -1
- package/dist/utils/sqlUtils.d.ts +72 -1
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/webtriggers/index.d.ts +1 -1
- package/dist/webtriggers/index.d.ts.map +1 -1
- package/dist/webtriggers/slowQuerySchedulerTrigger.d.ts +67 -0
- package/dist/webtriggers/slowQuerySchedulerTrigger.d.ts.map +1 -0
- package/package.json +12 -12
- package/src/core/ForgeSQLORM.ts +166 -29
- package/src/core/ForgeSQLQueryBuilder.ts +65 -11
- package/src/core/SystemTables.ts +1 -1
- package/src/utils/cacheUtils.ts +26 -3
- package/src/utils/forgeDriver.ts +15 -34
- package/src/utils/forgeDriverProxy.ts +58 -6
- package/src/utils/metadataContextUtils.ts +18 -6
- package/src/utils/sqlUtils.ts +241 -2
- package/src/webtriggers/index.ts +1 -1
- package/src/webtriggers/slowQuerySchedulerTrigger.ts +89 -0
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts +0 -114
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts.map +0 -1
- package/src/webtriggers/topSlowestStatementLastHourTrigger.ts +0 -563
package/README.md
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
|
|
10
10
|
[](https://github.com/vzakharchenko/forge-sql-orm/actions/workflows/node.js.yml)
|
|
11
11
|
[](https://coveralls.io/github/vzakharchenko/forge-sql-orm?branch=master)
|
|
12
|
-
[](https://sonarcloud.io/summary/new_code?id=vzakharchenko_forge-sql-orm)
|
|
13
12
|
[](https://deepscan.io/dashboard#view=project&tid=26652&pid=29272&bid=940614)
|
|
14
13
|
|
|
15
14
|
|
|
@@ -19,11 +18,11 @@
|
|
|
19
18
|
- ✅ **Custom Drizzle Driver** for direct integration with @forge/sql
|
|
20
19
|
- ✅ **Local Cache System (Level 1)** for in-memory query optimization within single resolver invocation scope
|
|
21
20
|
- ✅ **Global Cache System (Level 2)** with cross-invocation caching, automatic cache invalidation and context-aware operations (using [@forge/kvs](https://developer.atlassian.com/platform/forge/storage-reference/storage-api-custom-entities/) )
|
|
22
|
-
- ✅ **Performance Monitoring**:
|
|
21
|
+
- ✅ **Performance Monitoring**: Query execution metrics and analysis capabilities with automatic error analysis for timeout and OOM errors, plus scheduled slow query monitoring with execution plans
|
|
23
22
|
- ✅ **Type-Safe Query Building**: Write SQL queries with full TypeScript support
|
|
24
23
|
- ✅ **Supports complex SQL queries** with joins and filtering using Drizzle ORM
|
|
25
24
|
- ✅ **Advanced Query Methods**: `selectFrom()`, `selectDistinctFrom()`, `selectCacheableFrom()`, `selectDistinctCacheableFrom()` for all-column queries with field aliasing
|
|
26
|
-
- ✅ **Query Execution with Metadata**: `executeWithMetadata()` method for capturing detailed execution metrics including database execution time, response size, and
|
|
25
|
+
- ✅ **Query Execution with Metadata**: `executeWithMetadata()` method for capturing detailed execution metrics including database execution time, response size, and query analysis capabilities with performance monitoring
|
|
27
26
|
- ✅ **Raw SQL Execution**: `execute()`, `executeCacheable()`, `executeDDL()`, and `executeDDLActions()` methods for direct SQL queries with local and global caching
|
|
28
27
|
- ✅ **Common Table Expressions (CTEs)**: `with()` method for complex queries with subqueries
|
|
29
28
|
- ✅ **Schema migration support**, allowing automatic schema evolution
|
|
@@ -65,7 +64,8 @@
|
|
|
65
64
|
### 🔒 Advanced Features
|
|
66
65
|
- [Optimistic Locking](#optimistic-locking)
|
|
67
66
|
- [Query Analysis and Performance Optimization](#query-analysis-and-performance-optimization)
|
|
68
|
-
- [
|
|
67
|
+
- [Automatic Error Analysis](#automatic-error-analysis) - Automatic timeout and OOM error detection with execution plans
|
|
68
|
+
- [Slow Query Monitoring](#slow-query-monitoring) - Scheduled monitoring of slow queries with execution plans
|
|
69
69
|
- [Date and Time Types](#date-and-time-types)
|
|
70
70
|
|
|
71
71
|
### 🛠️ Development Tools
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
- [Query Analysis Example](examples/forge-sql-orm-example-query-analyses)
|
|
83
83
|
- [Organization Tracker Example](examples/forge-sql-orm-example-org-tracker)
|
|
84
84
|
- [Checklist Example](examples/forge-sql-orm-example-checklist)
|
|
85
|
-
- [Cache Example](examples/forge-sql-orm-example-cache) - Advanced caching capabilities with
|
|
85
|
+
- [Cache Example](examples/forge-sql-orm-example-cache) - Advanced caching capabilities with performance monitoring
|
|
86
86
|
|
|
87
87
|
### 📚 Reference
|
|
88
88
|
- [ForgeSqlOrmOptions](#forgesqlormoptions)
|
|
@@ -99,7 +99,6 @@
|
|
|
99
99
|
- [Global Cache System (Level 2)](#global-cache-system-level-2) - Cross-invocation persistent caching
|
|
100
100
|
- [Local Cache System (Level 1)](#local-cache-operations-level-1) - In-memory invocation caching
|
|
101
101
|
- [Optimistic Locking](#optimistic-locking) - Data consistency
|
|
102
|
-
- [Memory Usage Monitoring](#memory-usage-monitoring) - Memory-intensive query detection
|
|
103
102
|
- [Migration Tools](#web-triggers-for-migrations) - Database migrations
|
|
104
103
|
- [Query Analysis](#query-analysis-and-performance-optimization) - Performance optimization
|
|
105
104
|
|
|
@@ -108,7 +107,7 @@
|
|
|
108
107
|
- [Optimistic Locking Example](examples/forge-sql-orm-example-optimistic-locking) - Real-world conflict handling
|
|
109
108
|
- [Organization Tracker Example](examples/forge-sql-orm-example-org-tracker) - Complex relationships
|
|
110
109
|
- [Checklist Example](examples/forge-sql-orm-example-checklist) - Jira integration
|
|
111
|
-
- [Cache Example](examples/forge-sql-orm-example-cache) - Advanced caching
|
|
110
|
+
- [Cache Example](examples/forge-sql-orm-example-cache) - Advanced caching capabilities
|
|
112
111
|
|
|
113
112
|
## Usage Approaches
|
|
114
113
|
|
|
@@ -276,7 +275,39 @@ await forgeSQL.executeWithLocalContext(async () => {
|
|
|
276
275
|
});
|
|
277
276
|
```
|
|
278
277
|
|
|
279
|
-
### 4.
|
|
278
|
+
### 4. Resolver Performance Monitoring
|
|
279
|
+
```typescript
|
|
280
|
+
// Resolver with performance monitoring
|
|
281
|
+
resolver.define("fetch", async (req: Request) => {
|
|
282
|
+
try {
|
|
283
|
+
return await forgeSQL.executeWithMetadata(
|
|
284
|
+
async () => {
|
|
285
|
+
// Resolver logic with multiple queries
|
|
286
|
+
const users = await forgeSQL.selectFrom(demoUsers);
|
|
287
|
+
const orders = await forgeSQL.selectFrom(demoOrders)
|
|
288
|
+
.where(eq(demoOrders.userId, demoUsers.id));
|
|
289
|
+
return { users, orders };
|
|
290
|
+
},
|
|
291
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
292
|
+
const threshold = 500; // ms baseline for this resolver
|
|
293
|
+
|
|
294
|
+
if (totalDbExecutionTime > threshold * 1.5) {
|
|
295
|
+
console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
296
|
+
await printQueriesWithPlan(); // Optionally log or capture diagnostics for further analysis
|
|
297
|
+
} else if (totalDbExecutionTime > threshold) {
|
|
298
|
+
console.debug(`[Performance Debug fetch] High DB time: ${totalDbExecutionTime} ms`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
} catch (e) {
|
|
303
|
+
const error = e?.cause?.debug?.sqlMessage ?? e?.cause;
|
|
304
|
+
console.error(error, e);
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### 5. Next Steps
|
|
280
311
|
- [Full Installation Guide](#installation) - Complete setup instructions
|
|
281
312
|
- [Core Features](#core-features) - Learn about key capabilities
|
|
282
313
|
- [Global Cache System (Level 2)](#global-cache-system-level-2) - Cross-invocation caching features
|
|
@@ -328,19 +359,31 @@ const rawUsers = await forgeSQL.execute(
|
|
|
328
359
|
);
|
|
329
360
|
|
|
330
361
|
// Raw SQL with caching
|
|
362
|
+
// ⚠️ IMPORTANT: When using executeCacheable(), all table names must be wrapped with backticks (`)
|
|
331
363
|
const cachedRawUsers = await forgeSQL.executeCacheable(
|
|
332
|
-
"SELECT * FROM users WHERE active = ?",
|
|
364
|
+
"SELECT * FROM `users` WHERE active = ?",
|
|
333
365
|
[true],
|
|
334
366
|
300
|
|
335
367
|
);
|
|
336
368
|
|
|
337
|
-
// Raw SQL with execution metadata
|
|
369
|
+
// Raw SQL with execution metadata and performance monitoring
|
|
338
370
|
const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
339
|
-
async () =>
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
371
|
+
async () => {
|
|
372
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
373
|
+
const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
374
|
+
return { users, orders };
|
|
375
|
+
},
|
|
376
|
+
(totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
377
|
+
const threshold = 500; // ms baseline for this resolver
|
|
378
|
+
|
|
379
|
+
if (totalDbExecutionTime > threshold * 1.5) {
|
|
380
|
+
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
381
|
+
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
382
|
+
} else if (totalDbExecutionTime > threshold) {
|
|
383
|
+
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
344
387
|
}
|
|
345
388
|
);
|
|
346
389
|
|
|
@@ -438,19 +481,31 @@ const rawUsers = await forgeSQL.execute(
|
|
|
438
481
|
);
|
|
439
482
|
|
|
440
483
|
// Raw SQL with caching
|
|
484
|
+
// ⚠️ IMPORTANT: When using executeCacheable(), all table names must be wrapped with backticks (`)
|
|
441
485
|
const cachedRawUsers = await forgeSQL.executeCacheable(
|
|
442
|
-
"SELECT * FROM users WHERE active = ?",
|
|
486
|
+
"SELECT * FROM `users` WHERE active = ?",
|
|
443
487
|
[true],
|
|
444
488
|
300
|
|
445
489
|
);
|
|
446
490
|
|
|
447
|
-
// Raw SQL with execution metadata
|
|
491
|
+
// Raw SQL with execution metadata and performance monitoring
|
|
448
492
|
const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
449
|
-
async () =>
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
493
|
+
async () => {
|
|
494
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
495
|
+
const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
496
|
+
return { users, orders };
|
|
497
|
+
},
|
|
498
|
+
(totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
499
|
+
const threshold = 500; // ms baseline for this resolver
|
|
500
|
+
|
|
501
|
+
if (totalDbExecutionTime > threshold * 1.5) {
|
|
502
|
+
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
503
|
+
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
504
|
+
} else if (totalDbExecutionTime > threshold) {
|
|
505
|
+
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
454
509
|
}
|
|
455
510
|
);
|
|
456
511
|
```
|
|
@@ -563,6 +618,9 @@ Please review the [official @forge/kvs quotas and limits](https://developer.atla
|
|
|
563
618
|
- Monitor cache usage to stay within quotas
|
|
564
619
|
- Use appropriate TTL values
|
|
565
620
|
|
|
621
|
+
**⚠️ Important Cache Limitations:**
|
|
622
|
+
- **Table names starting with `a_`**: Tables whose names start with `a_` (case-insensitive) are automatically ignored in cache operations. KVS Cache will not work with such tables, and they will be excluded from cache invalidation and cache key generation. This is by design to support special system tables or temporary tables.
|
|
623
|
+
|
|
566
624
|
### Step 1: Install Dependencies
|
|
567
625
|
|
|
568
626
|
```bash
|
|
@@ -1082,8 +1140,10 @@ const user = await forgeSQL
|
|
|
1082
1140
|
.execute("SELECT * FROM users WHERE id = ?", [1]);
|
|
1083
1141
|
|
|
1084
1142
|
// Using forgeSQL.executeCacheable() - Execute raw SQL with local and global caching
|
|
1143
|
+
// ⚠️ IMPORTANT: When using executeCacheable(), all table names in SQL queries must be wrapped with backticks (`)
|
|
1144
|
+
// Example: SELECT * FROM `users` WHERE id = ? (NOT: SELECT * FROM users WHERE id = ?)
|
|
1085
1145
|
const user = await forgeSQL
|
|
1086
|
-
.executeCacheable("SELECT * FROM users WHERE id = ?", [1], 300);
|
|
1146
|
+
.executeCacheable("SELECT * FROM `users` WHERE id = ?", [1], 300);
|
|
1087
1147
|
|
|
1088
1148
|
// Using forgeSQL.getDrizzleQueryBuilder()
|
|
1089
1149
|
const user = await forgeSQL
|
|
@@ -1212,19 +1272,31 @@ const users = await forgeSQL
|
|
|
1212
1272
|
.execute("SELECT * FROM users WHERE active = ?", [true]);
|
|
1213
1273
|
|
|
1214
1274
|
// Using executeCacheable() for raw SQL with local and global caching
|
|
1275
|
+
// ⚠️ IMPORTANT: When using executeCacheable(), all table names in SQL queries must be wrapped with backticks (`)
|
|
1276
|
+
// Example: SELECT * FROM `users` WHERE active = ? (NOT: SELECT * FROM users WHERE active = ?)
|
|
1215
1277
|
const users = await forgeSQL
|
|
1216
|
-
.executeCacheable("SELECT * FROM users WHERE active = ?", [true], 300);
|
|
1217
|
-
|
|
1218
|
-
// Using executeWithMetadata() for capturing execution metrics
|
|
1219
|
-
const usersWithMetadata = await forgeSQL
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1278
|
+
.executeCacheable("SELECT * FROM `users` WHERE active = ?", [true], 300);
|
|
1279
|
+
|
|
1280
|
+
// Using executeWithMetadata() for capturing execution metrics and performance monitoring
|
|
1281
|
+
const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
1282
|
+
async () => {
|
|
1283
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
1284
|
+
const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
1285
|
+
return { users, orders };
|
|
1286
|
+
},
|
|
1287
|
+
(totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
1288
|
+
const threshold = 500; // ms baseline for this resolver
|
|
1289
|
+
|
|
1290
|
+
if (totalDbExecutionTime > threshold * 1.5) {
|
|
1291
|
+
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
1292
|
+
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
1293
|
+
} else if (totalDbExecutionTime > threshold) {
|
|
1294
|
+
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
1298
|
+
}
|
|
1299
|
+
);
|
|
1228
1300
|
|
|
1229
1301
|
// Using executeDDL() for DDL operations (CREATE, ALTER, DROP, etc.)
|
|
1230
1302
|
await forgeSQL.executeDDL(`
|
|
@@ -1575,13 +1647,24 @@ await forgeSQL.executeWithLocalContext(async () => {
|
|
|
1575
1647
|
[true]
|
|
1576
1648
|
);
|
|
1577
1649
|
|
|
1578
|
-
// Raw SQL with execution metadata and
|
|
1650
|
+
// Raw SQL with execution metadata and performance monitoring
|
|
1579
1651
|
const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
1580
|
-
async () =>
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1652
|
+
async () => {
|
|
1653
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
1654
|
+
const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
1655
|
+
return { users, orders };
|
|
1656
|
+
},
|
|
1657
|
+
(totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
1658
|
+
const threshold = 500; // ms baseline for this resolver
|
|
1659
|
+
|
|
1660
|
+
if (totalDbExecutionTime > threshold * 1.5) {
|
|
1661
|
+
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
1662
|
+
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
1663
|
+
} else if (totalDbExecutionTime > threshold) {
|
|
1664
|
+
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
1585
1668
|
}
|
|
1586
1669
|
);
|
|
1587
1670
|
|
|
@@ -1685,8 +1768,9 @@ await forgeSQL.executeWithLocalContext(async () => {
|
|
|
1685
1768
|
.where(eq(users.active, true));
|
|
1686
1769
|
|
|
1687
1770
|
// Raw SQL with multi-level caching
|
|
1771
|
+
// ⚠️ IMPORTANT: When using executeCacheable(), all table names must be wrapped with backticks (`)
|
|
1688
1772
|
const rawUsers = await forgeSQL.executeCacheable(
|
|
1689
|
-
"SELECT id, name FROM users WHERE active = ?",
|
|
1773
|
+
"SELECT id, name FROM `users` WHERE active = ?",
|
|
1690
1774
|
[true],
|
|
1691
1775
|
300 // TTL in seconds
|
|
1692
1776
|
);
|
|
@@ -1735,8 +1819,9 @@ const usersDistinct = await forgeSQL.selectDistinctCacheableFrom(Users)
|
|
|
1735
1819
|
.where(eq(Users.active, true));
|
|
1736
1820
|
|
|
1737
1821
|
// Raw SQL with local and global caching
|
|
1822
|
+
// ⚠️ IMPORTANT: When using executeCacheable(), all table names must be wrapped with backticks (`)
|
|
1738
1823
|
const rawUsers = await forgeSQL.executeCacheable(
|
|
1739
|
-
"SELECT * FROM users WHERE active = ?",
|
|
1824
|
+
"SELECT * FROM `users` WHERE active = ?",
|
|
1740
1825
|
[true],
|
|
1741
1826
|
300 // TTL in seconds
|
|
1742
1827
|
);
|
|
@@ -1754,13 +1839,24 @@ const userStats = await forgeSQL
|
|
|
1754
1839
|
.from(sql`activeUsers au`)
|
|
1755
1840
|
.leftJoin(sql`completedOrders co`, eq(sql`au.id`, sql`co.userId`));
|
|
1756
1841
|
|
|
1757
|
-
// Using executeWithMetadata() for capturing execution metrics with
|
|
1842
|
+
// Using executeWithMetadata() for capturing execution metrics with performance monitoring
|
|
1758
1843
|
const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
1759
|
-
async () =>
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1844
|
+
async () => {
|
|
1845
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
1846
|
+
const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
1847
|
+
return { users, orders };
|
|
1848
|
+
},
|
|
1849
|
+
(totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
1850
|
+
const threshold = 500; // ms baseline for this resolver
|
|
1851
|
+
|
|
1852
|
+
if (totalDbExecutionTime > threshold * 1.5) {
|
|
1853
|
+
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
1854
|
+
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
1855
|
+
} else if (totalDbExecutionTime > threshold) {
|
|
1856
|
+
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
1764
1860
|
}
|
|
1765
1861
|
);
|
|
1766
1862
|
```
|
|
@@ -2036,6 +2132,34 @@ Configure in `manifest.yml`:
|
|
|
2036
2132
|
- `hour` - Every hour
|
|
2037
2133
|
- `day` - Every day
|
|
2038
2134
|
|
|
2135
|
+
### 5. Slow Query Scheduler Trigger
|
|
2136
|
+
|
|
2137
|
+
This scheduler trigger automatically monitors and analyzes slow queries on a scheduled basis. For detailed information, see the [Slow Query Monitoring](#slow-query-monitoring) section.
|
|
2138
|
+
|
|
2139
|
+
**Quick Setup:**
|
|
2140
|
+
|
|
2141
|
+
```typescript
|
|
2142
|
+
import ForgeSQL, { slowQuerySchedulerTrigger } from "forge-sql-orm";
|
|
2143
|
+
|
|
2144
|
+
const forgeSQL = new ForgeSQL();
|
|
2145
|
+
|
|
2146
|
+
export const slowQueryTrigger = () =>
|
|
2147
|
+
slowQuerySchedulerTrigger(forgeSQL, { hours: 1, timeout: 3000 });
|
|
2148
|
+
```
|
|
2149
|
+
|
|
2150
|
+
Configure in `manifest.yml`:
|
|
2151
|
+
```yaml
|
|
2152
|
+
scheduledTrigger:
|
|
2153
|
+
- key: slow-query-trigger
|
|
2154
|
+
function: slowQueryTrigger
|
|
2155
|
+
interval: hour
|
|
2156
|
+
function:
|
|
2157
|
+
- key: slowQueryTrigger
|
|
2158
|
+
handler: index.slowQueryTrigger
|
|
2159
|
+
```
|
|
2160
|
+
|
|
2161
|
+
> **💡 Note**: For complete documentation, examples, and configuration options, see the [Slow Query Monitoring](#slow-query-monitoring) section.
|
|
2162
|
+
|
|
2039
2163
|
### Important Notes
|
|
2040
2164
|
|
|
2041
2165
|
**Security Considerations**:
|
|
@@ -2065,6 +2189,170 @@ Atlassian provides comprehensive query analysis tools in the development console
|
|
|
2065
2189
|
|
|
2066
2190
|
Our analysis tools complement these built-in features by providing additional insights directly from TiDB's system schemas.
|
|
2067
2191
|
|
|
2192
|
+
### Automatic Error Analysis
|
|
2193
|
+
|
|
2194
|
+
Forge-SQL-ORM automatically intercepts and analyzes critical query errors to help you diagnose performance issues. When a query fails due to **timeout** or **out-of-memory** errors, the library automatically:
|
|
2195
|
+
|
|
2196
|
+
1. **Detects the error type** (SQL_QUERY_TIMEOUT or Out of Memory)
|
|
2197
|
+
2. **Logs detailed error information** to the Forge Developer Console
|
|
2198
|
+
3. **Waits for system tables to populate** (200ms delay)
|
|
2199
|
+
4. **Retrieves and logs the execution plan** for the failed query
|
|
2200
|
+
5. **Provides performance metrics** including memory usage, execution time, and query details
|
|
2201
|
+
|
|
2202
|
+
This automatic analysis happens transparently - no additional code is required on your part.
|
|
2203
|
+
|
|
2204
|
+
#### Supported Error Types
|
|
2205
|
+
|
|
2206
|
+
- **SQL_QUERY_TIMEOUT**: Queries that exceed the execution time limit
|
|
2207
|
+
- **Out of Memory (OOM)**: Queries that exceed the 16 MiB memory limit (errno: 8175)
|
|
2208
|
+
|
|
2209
|
+
#### Example Console Output
|
|
2210
|
+
|
|
2211
|
+
When a query fails, you'll see output like this in the Forge Developer Console:
|
|
2212
|
+
|
|
2213
|
+
```
|
|
2214
|
+
❌ TIMEOUT detected - Query exceeded time limit
|
|
2215
|
+
⏳ Waiting 200ms for CLUSTER_STATEMENTS_SUMMARY to populate...
|
|
2216
|
+
📊 Analyzing query performance and execution plan...
|
|
2217
|
+
⏱️ Query duration: 10500ms
|
|
2218
|
+
|
|
2219
|
+
SQL: SELECT * FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE u.active = ? | Memory: 12.45 MB | Time: 10500.00 ms | stmtType: Select | Executions: 1
|
|
2220
|
+
Plan:
|
|
2221
|
+
id task estRows operator info actRows execution info memory disk
|
|
2222
|
+
Projection_7 root 1000.00 forge_38dd1c6156b94bb59c2c9a45582bbfc7.users.id, ... 1000 time:10.5s, loops:1 12.45 MB N/A
|
|
2223
|
+
└─IndexHashJoin_14 root 1000.00 inner join, ... 1000 time:10.2s, loops:1 11.98 MB N/A
|
|
2224
|
+
```
|
|
2225
|
+
|
|
2226
|
+
#### How It Works
|
|
2227
|
+
|
|
2228
|
+
The error analysis mechanism:
|
|
2229
|
+
|
|
2230
|
+
1. **Error Detection**: When a query fails, the driver proxy checks the error code/errno
|
|
2231
|
+
2. **Error Logging**: Logs the specific error type to console.error
|
|
2232
|
+
3. **Data Population Wait**: Waits 200ms for TiDB's `CLUSTER_STATEMENTS_SUMMARY` table to be populated with the failed query's metadata
|
|
2233
|
+
4. **Query Analysis**: Automatically calls `printQueriesWithPlan()` to retrieve and display:
|
|
2234
|
+
- SQL query text
|
|
2235
|
+
- Memory consumption (average and max in MB)
|
|
2236
|
+
- Execution time (average in ms)
|
|
2237
|
+
- Statement type
|
|
2238
|
+
- Number of executions
|
|
2239
|
+
- Detailed execution plan
|
|
2240
|
+
|
|
2241
|
+
#### Benefits
|
|
2242
|
+
|
|
2243
|
+
- **Zero Configuration**: Works automatically - no setup required
|
|
2244
|
+
- **Immediate Insights**: Get execution plans for failed queries instantly
|
|
2245
|
+
- **Performance Debugging**: Identify bottlenecks without manual investigation
|
|
2246
|
+
- **Development Console Integration**: All logs appear in Atlassian Forge Developer Console
|
|
2247
|
+
- **No Code Changes**: Existing code automatically benefits from error analysis
|
|
2248
|
+
|
|
2249
|
+
> **💡 Tip**: The automatic error analysis only triggers for timeout and OOM errors. Other errors are logged normally without plan analysis.
|
|
2250
|
+
|
|
2251
|
+
### Slow Query Monitoring
|
|
2252
|
+
|
|
2253
|
+
Forge-SQL-ORM provides a scheduler trigger (`slowQuerySchedulerTrigger`) that automatically monitors and analyzes slow queries on an hourly basis. This trigger queries TiDB's slow query log system table and provides detailed performance information including SQL query text, memory usage, execution time, and execution plans.
|
|
2254
|
+
|
|
2255
|
+
#### Key Features
|
|
2256
|
+
|
|
2257
|
+
- **Automatic Monitoring**: Runs on a scheduled interval (recommended: hourly)
|
|
2258
|
+
- **Detailed Performance Metrics**: Memory usage, execution time, and execution plans
|
|
2259
|
+
- **Console Logging**: Results are automatically logged to the Forge Developer Console
|
|
2260
|
+
- **Configurable Time Window**: Analyze queries from the last N hours (default: 1 hour)
|
|
2261
|
+
- **Automatic Plan Retrieval**: Execution plans are included for all slow queries
|
|
2262
|
+
|
|
2263
|
+
#### Basic Setup
|
|
2264
|
+
|
|
2265
|
+
**1. Create the trigger function:**
|
|
2266
|
+
|
|
2267
|
+
```typescript
|
|
2268
|
+
import ForgeSQL, { slowQuerySchedulerTrigger } from "forge-sql-orm";
|
|
2269
|
+
|
|
2270
|
+
const forgeSQL = new ForgeSQL();
|
|
2271
|
+
|
|
2272
|
+
// Monitor slow queries from the last hour (recommended for hourly schedule)
|
|
2273
|
+
export const slowQueryTrigger = () =>
|
|
2274
|
+
slowQuerySchedulerTrigger(forgeSQL, { hours: 1, timeout: 3000 });
|
|
2275
|
+
```
|
|
2276
|
+
|
|
2277
|
+
**2. Configure in `manifest.yml`:**
|
|
2278
|
+
|
|
2279
|
+
```yaml
|
|
2280
|
+
modules:
|
|
2281
|
+
scheduledTrigger:
|
|
2282
|
+
- key: slow-query-trigger
|
|
2283
|
+
function: slowQueryTrigger
|
|
2284
|
+
interval: hour # Run every hour
|
|
2285
|
+
|
|
2286
|
+
function:
|
|
2287
|
+
- key: slowQueryTrigger
|
|
2288
|
+
handler: index.slowQueryTrigger
|
|
2289
|
+
```
|
|
2290
|
+
|
|
2291
|
+
#### Configuration Options
|
|
2292
|
+
|
|
2293
|
+
| Option | Type | Default | Description |
|
|
2294
|
+
|--------|------|---------|-------------|
|
|
2295
|
+
| `hours` | `number` | `1` | Number of hours to look back for slow queries |
|
|
2296
|
+
| `timeout` | `number` | `3000` | Timeout in milliseconds for the diagnostic query execution |
|
|
2297
|
+
|
|
2298
|
+
#### Example Console Output
|
|
2299
|
+
|
|
2300
|
+
When slow queries are detected, you'll see output like this in the Forge Developer Console:
|
|
2301
|
+
|
|
2302
|
+
```
|
|
2303
|
+
Found SlowQuery SQL: SELECT * FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE u.active = ? | Memory: 8.50 MB | Time: 2500.00 ms
|
|
2304
|
+
Plan:
|
|
2305
|
+
id task estRows operator info actRows execution info memory disk
|
|
2306
|
+
Projection_7 root 1000.00 forge_38dd1c6156b94bb59c2c9a45582bbfc7.users.id, ... 1000 time:2.5s, loops:1 8.50 MB N/A
|
|
2307
|
+
└─IndexHashJoin_14 root 1000.00 inner join, ... 1000 time:2.2s, loops:1 7.98 MB N/A
|
|
2308
|
+
|
|
2309
|
+
Found SlowQuery SQL: SELECT * FROM products WHERE category = ? ORDER BY created_at DESC | Memory: 6.25 MB | Time: 1800.00 ms
|
|
2310
|
+
Plan:
|
|
2311
|
+
...
|
|
2312
|
+
```
|
|
2313
|
+
|
|
2314
|
+
#### Advanced Configuration
|
|
2315
|
+
|
|
2316
|
+
```typescript
|
|
2317
|
+
import ForgeSQL, { slowQuerySchedulerTrigger } from "forge-sql-orm";
|
|
2318
|
+
|
|
2319
|
+
const forgeSQL = new ForgeSQL();
|
|
2320
|
+
|
|
2321
|
+
// Monitor queries from the last 6 hours (for less frequent checks)
|
|
2322
|
+
export const sixHourSlowQueryTrigger = () =>
|
|
2323
|
+
slowQuerySchedulerTrigger(forgeSQL, { hours: 6, timeout: 5000 });
|
|
2324
|
+
|
|
2325
|
+
// Monitor queries from the last 24 hours (daily monitoring)
|
|
2326
|
+
export const dailySlowQueryTrigger = () =>
|
|
2327
|
+
slowQuerySchedulerTrigger(forgeSQL, { hours: 24, timeout: 3000 });
|
|
2328
|
+
```
|
|
2329
|
+
|
|
2330
|
+
#### How It Works
|
|
2331
|
+
|
|
2332
|
+
1. **Scheduled Execution**: The trigger runs automatically on the configured interval (hourly recommended)
|
|
2333
|
+
2. **Query Analysis**: Queries TiDB's slow query log system table for queries executed within the specified time window
|
|
2334
|
+
3. **Performance Metrics**: Extracts and logs:
|
|
2335
|
+
- SQL query text (sanitized for readability)
|
|
2336
|
+
- Maximum memory usage (in MB)
|
|
2337
|
+
- Query execution time (in ms)
|
|
2338
|
+
- Detailed execution plan
|
|
2339
|
+
4. **Console Logging**: Results are logged to the Forge Developer Console via `console.warn()` for easy monitoring
|
|
2340
|
+
|
|
2341
|
+
#### Best Practices
|
|
2342
|
+
|
|
2343
|
+
- **Hourly Intervals**: Use `interval: hour` for timely detection of slow queries
|
|
2344
|
+
- **Default Time Window**: 1 hour is recommended for hourly schedules to avoid overlap
|
|
2345
|
+
- **Monitor Regularly**: Check console logs regularly to identify patterns in slow queries
|
|
2346
|
+
|
|
2347
|
+
#### Benefits
|
|
2348
|
+
|
|
2349
|
+
- **Proactive Monitoring**: Catch slow queries before they become critical issues
|
|
2350
|
+
- **Performance Trends**: Track query performance over time
|
|
2351
|
+
- **Optimization Insights**: Execution plans help identify optimization opportunities
|
|
2352
|
+
- **Zero Manual Intervention**: Fully automated monitoring with scheduled execution
|
|
2353
|
+
- **Production Safe**: Works silently in the background, only logs when slow queries are found
|
|
2354
|
+
|
|
2355
|
+
> **💡 Tip**: The trigger queries up to 50 slow queries to prevent excessive logging. Transient timeouts are usually fine; repeated timeouts indicate the diagnostic query itself is slow and should be investigated.
|
|
2068
2356
|
|
|
2069
2357
|
### Available Analysis Tools
|
|
2070
2358
|
|
|
@@ -2138,213 +2426,6 @@ This analysis provides insights into:
|
|
|
2138
2426
|
- Resource usage at each step
|
|
2139
2427
|
- Performance optimization opportunities
|
|
2140
2428
|
|
|
2141
|
-
## Performance Monitoring
|
|
2142
|
-
|
|
2143
|
-
[↑ Back to Top](#table-of-contents)
|
|
2144
|
-
|
|
2145
|
-
Forge-SQL-ORM provides automated performance monitoring capabilities to help you identify and track memory-intensive queries in your Forge SQL instance. This feature is **essential for Atlassian Forge applications** as it helps you stay within the **16 MiB per query memory limit** and provides detailed insights for optimization.
|
|
2146
|
-
|
|
2147
|
-
### Why Performance Monitoring is Critical
|
|
2148
|
-
|
|
2149
|
-
Atlassian Forge SQL has a strict **16 MiB memory limit per query**. Unlike slow query detection (which is available in the Forge Developer Console), there's **no built-in way to monitor memory usage** of your queries. This monitoring system fills that gap by:
|
|
2150
|
-
|
|
2151
|
-
- **Detecting memory-intensive queries** before they hit the 16 MiB limit
|
|
2152
|
-
- **Providing detailed memory metrics** including average and maximum memory usage
|
|
2153
|
-
- **Showing execution plans** to help optimize memory consumption
|
|
2154
|
-
- **Configurable thresholds** to match your application's memory requirements
|
|
2155
|
-
- **Scheduled monitoring** via Forge scheduler triggers
|
|
2156
|
-
|
|
2157
|
-
### Overview
|
|
2158
|
-
|
|
2159
|
-
The performance monitoring system:
|
|
2160
|
-
- **Automatically detects memory-intensive queries** based on configurable memory thresholds
|
|
2161
|
-
- **Provides detailed memory metrics** including execution time, memory usage, and execution plans
|
|
2162
|
-
- **Logs memory issues** to the Forge Developer Console for easy debugging
|
|
2163
|
-
- **Supports scheduled monitoring** via Forge scheduler triggers
|
|
2164
|
-
- **Filters out system queries** to focus on your application's performance
|
|
2165
|
-
|
|
2166
|
-
### Key Features
|
|
2167
|
-
|
|
2168
|
-
- **Memory-Focused Performance Monitoring**: Primary focus on memory usage with configurable thresholds
|
|
2169
|
-
- **Atlassian 16 MiB Limit Awareness**: Designed specifically for Forge SQL's memory constraints
|
|
2170
|
-
- **Execution Plan Analysis**: Shows detailed query plans to help optimize memory consumption
|
|
2171
|
-
- **Configurable Thresholds**: Set custom memory usage thresholds (default: 4MB warning)
|
|
2172
|
-
- **Automatic Filtering**: Excludes system queries (`Use`, `Set`, `Show`) and empty queries
|
|
2173
|
-
- **Scheduled Monitoring**: Run automatically on configurable intervals
|
|
2174
|
-
|
|
2175
|
-
### Basic Usage
|
|
2176
|
-
|
|
2177
|
-
#### 1. Import the Trigger
|
|
2178
|
-
|
|
2179
|
-
```typescript
|
|
2180
|
-
import ForgeSQL, { topSlowestStatementLastHourTrigger } from "forge-sql-orm";
|
|
2181
|
-
```
|
|
2182
|
-
|
|
2183
|
-
#### 2. Create a Scheduler Function
|
|
2184
|
-
|
|
2185
|
-
```typescript
|
|
2186
|
-
import ForgeSQL, { topSlowestStatementLastHourTrigger } from 'forge-sql-orm';
|
|
2187
|
-
|
|
2188
|
-
// Initialize ForgeSQL ORM instance
|
|
2189
|
-
const forgeSQL = new ForgeSQL();
|
|
2190
|
-
|
|
2191
|
-
// Basic usage with default thresholds (300ms latency, 8MB memory warning)
|
|
2192
|
-
export const performanceTrigger = () =>
|
|
2193
|
-
topSlowestStatementLastHourTrigger(forgeSQL);
|
|
2194
|
-
|
|
2195
|
-
// Conservative performance monitoring: 4MB warning (well below 16MB limit)
|
|
2196
|
-
export const conservativeMemoryTrigger = () =>
|
|
2197
|
-
topSlowestStatementLastHourTrigger(forgeSQL, { memoryThresholdBytes: 4 * 1024 * 1024 });
|
|
2198
|
-
|
|
2199
|
-
// Aggressive performance monitoring: 12MB warning (75% of 16MB limit)
|
|
2200
|
-
export const aggressiveMemoryTrigger = () =>
|
|
2201
|
-
topSlowestStatementLastHourTrigger(forgeSQL, { memoryThresholdBytes: 12 * 1024 * 1024 });
|
|
2202
|
-
|
|
2203
|
-
// Memory-only performance monitoring: Only trigger on memory usage (latency effectively disabled)
|
|
2204
|
-
export const memoryOnlyTrigger = () =>
|
|
2205
|
-
topSlowestStatementLastHourTrigger(forgeSQL, { warnThresholdMs: 10000, memoryThresholdBytes: 4 * 1024 * 1024 });
|
|
2206
|
-
|
|
2207
|
-
// Latency-only monitoring: Only trigger on slow queries (memory effectively disabled)
|
|
2208
|
-
export const latencyOnlyTrigger = () =>
|
|
2209
|
-
topSlowestStatementLastHourTrigger(forgeSQL, { warnThresholdMs: 500, memoryThresholdBytes: 16 * 1024 * 1024 });
|
|
2210
|
-
|
|
2211
|
-
// With execution plan in logs
|
|
2212
|
-
export const performanceWithPlanTrigger = () =>
|
|
2213
|
-
topSlowestStatementLastHourTrigger(forgeSQL, { showPlan: true });
|
|
2214
|
-
```
|
|
2215
|
-
|
|
2216
|
-
#### 3. Configure in manifest.yml
|
|
2217
|
-
|
|
2218
|
-
**As Scheduler Trigger (Recommended for Production):**
|
|
2219
|
-
```yaml
|
|
2220
|
-
scheduledTrigger:
|
|
2221
|
-
- key: performance-trigger
|
|
2222
|
-
function: perfTrigger
|
|
2223
|
-
interval: hour # Required: only hour interval is supported
|
|
2224
|
-
|
|
2225
|
-
function:
|
|
2226
|
-
- key: perfTrigger
|
|
2227
|
-
handler: index.performanceTrigger
|
|
2228
|
-
```
|
|
2229
|
-
|
|
2230
|
-
**As Web Trigger (Development Only):**
|
|
2231
|
-
```yaml
|
|
2232
|
-
webtrigger:
|
|
2233
|
-
- key: print-slowest-queries
|
|
2234
|
-
function: perfTrigger
|
|
2235
|
-
|
|
2236
|
-
function:
|
|
2237
|
-
- key: perfTrigger
|
|
2238
|
-
handler: index.performanceTrigger
|
|
2239
|
-
```
|
|
2240
|
-
|
|
2241
|
-
> **⚠️ Important**: Web triggers are not recommended for production as they violate the "run-on-atlassian" principle. Use scheduler triggers for production monitoring.
|
|
2242
|
-
|
|
2243
|
-
### How It Works
|
|
2244
|
-
|
|
2245
|
-
The performance monitoring trigger works differently depending on how it's configured:
|
|
2246
|
-
|
|
2247
|
-
#### Scheduler Trigger Mode (Production)
|
|
2248
|
-
|
|
2249
|
-
When used as a **scheduler trigger**, the system:
|
|
2250
|
-
- **Runs automatically** on the configured interval (hour only)
|
|
2251
|
-
- **Logs to Forge Developer Console** only when thresholds are exceeded
|
|
2252
|
-
- **No HTTP response** - operates silently in the background
|
|
2253
|
-
- **Perfect for production** monitoring without violating "run-on-atlassian"
|
|
2254
|
-
|
|
2255
|
-
**Example Console Log Output:**
|
|
2256
|
-
```
|
|
2257
|
-
1. Select avg=3006.03ms max=3006.03ms mem≈0.08MB(max 0.08MB) exec=1
|
|
2258
|
-
digest=28344800f90f6c929484e83337404df7e55a660c5f4ce922c4b298ab5e90c425
|
|
2259
|
-
sql=select `demo_users` . `id` as `a_userid_id` , `demo_users` . `name` as `a_username_name` , `demo_orders` . `product` as `a_product_product` , `demo_orders` . `id` as `a_productid_id` , `sleep` ( ? ) from `demo_users` inner join `demo_orders` on `demo_orders` . `user_id` = `demo_users` . `id`
|
|
2260
|
-
|
|
2261
|
-
full plan:
|
|
2262
|
-
id task estRows operator info actRows execution info memory disk
|
|
2263
|
-
Projection_7 root 2.50 forge_38dd1c6156b94bb59c2c9a45582bbfc7.demo_users.id, forge_38dd1c6156b94bb59c2c9a45582bbfc7.demo_users.name, forge_38dd1c6156b94bb59c2c9a45582bbfc7.demo_orders.product, forge_38dd1c6156b94bb59c2c9a45582bbfc7.demo_orders.id, sleep(?)->Column#7 3 time:3s, loops:2, Concurrency:OFF 1.98 KB N/A
|
|
2264
|
-
└─IndexHashJoin_14 root 2.50 inner join, inner:IndexLookUp_11, outer key:forge_38dd1c6156b94bb59c2c9a45582bbfc7.demo_users.id, inner key:forge_38dd1c6156b94bb59c2c9a45582bbfc7.demo_orders.user_id, equal cond:eq(forge_38dd1c6156b94bb59c2c9a45582bbfc7.demo_users.id, forge_38dd1c6156b94bb59c2c9a45582bbfc7.demo_orders.user_id) 3 time:2.11ms, loops:2, inner:{total:1.31ms, concurrency:5, task:1, construct:6.62µs, fetch:1.3ms, build:4.68µs, join:6.14µs} 57.9 KB N/A
|
|
2265
|
-
```
|
|
2266
|
-
|
|
2267
|
-
#### Web Trigger Mode (Development)
|
|
2268
|
-
|
|
2269
|
-
When used as a **web trigger**, the system:
|
|
2270
|
-
- **Runs on-demand** when the web endpoint is called
|
|
2271
|
-
- **Returns JSON response** with detailed metrics
|
|
2272
|
-
- **Logs to console** AND provides structured data
|
|
2273
|
-
- **Useful for development** and debugging
|
|
2274
|
-
|
|
2275
|
-
**Example JSON Response:**
|
|
2276
|
-
```json
|
|
2277
|
-
{
|
|
2278
|
-
"success": true,
|
|
2279
|
-
"window": "last_1h",
|
|
2280
|
-
"top": 1,
|
|
2281
|
-
"warnThresholdMs": 300,
|
|
2282
|
-
"memoryThresholdBytes": 8388608,
|
|
2283
|
-
"rows": [
|
|
2284
|
-
{
|
|
2285
|
-
"rank": 1,
|
|
2286
|
-
"digest": "abc123...",
|
|
2287
|
-
"stmtType": "Select",
|
|
2288
|
-
"schemaName": "myapp",
|
|
2289
|
-
"execCount": 150,
|
|
2290
|
-
"avgLatencyMs": 450.25,
|
|
2291
|
-
"maxLatencyMs": 1200.50,
|
|
2292
|
-
"minLatencyMs": 200.10,
|
|
2293
|
-
"avgProcessTimeMs": 400.20,
|
|
2294
|
-
"avgWaitTimeMs": 50.05,
|
|
2295
|
-
"avgBackoffTimeMs": 0.00,
|
|
2296
|
-
"avgMemMB": 2.5,
|
|
2297
|
-
"maxMemMB": 8.2,
|
|
2298
|
-
"avgMemBytes": 2621440,
|
|
2299
|
-
"maxMemBytes": 8598323,
|
|
2300
|
-
"avgTotalKeys": 1000,
|
|
2301
|
-
"firstSeen": "2024-01-15 10:30:00",
|
|
2302
|
-
"lastSeen": "2024-01-15 11:30:00",
|
|
2303
|
-
"planInCache": true,
|
|
2304
|
-
"planCacheHits": 120,
|
|
2305
|
-
"digestText": "SELECT * FROM users WHERE active = ?",
|
|
2306
|
-
"plan": "IndexScan(users, idx_active)..."
|
|
2307
|
-
}
|
|
2308
|
-
],
|
|
2309
|
-
"generatedAt": "2024-01-15T11:30:00.000Z"
|
|
2310
|
-
}
|
|
2311
|
-
```
|
|
2312
|
-
|
|
2313
|
-
### Configuration Options
|
|
2314
|
-
|
|
2315
|
-
#### Threshold Parameters
|
|
2316
|
-
|
|
2317
|
-
| Parameter | Type | Default | Description |
|
|
2318
|
-
|-----------|------|---------|-------------|
|
|
2319
|
-
| `warnThresholdMs` | `number` | `300` | Latency threshold in milliseconds (secondary) |
|
|
2320
|
-
| `memoryThresholdBytes` | `number` | `8 * 1024 * 1024` | **Memory usage threshold in bytes (primary focus)** |
|
|
2321
|
-
| `showPlan` | `boolean` | `false` | Whether to include execution plan in logs |
|
|
2322
|
-
| `logCache` | `boolean` | `false` | Whether to log cache operations |
|
|
2323
|
-
|
|
2324
|
-
**⚠️ Important: OR Logic**
|
|
2325
|
-
The monitoring uses **OR logic** - if **either** threshold is exceeded, the query will be logged/returned:
|
|
2326
|
-
- Query exceeds `warnThresholdMs` **OR** `memoryThresholdBytes` → Included in results
|
|
2327
|
-
- This means you can set different thresholds for different monitoring priorities
|
|
2328
|
-
- No need to exceed both thresholds simultaneously
|
|
2329
|
-
|
|
2330
|
-
**💡 Pro Tips:**
|
|
2331
|
-
- **Memory-only performance monitoring**: Set `warnThresholdMs` to a very high value (e.g., 10000ms) to trigger only on memory usage
|
|
2332
|
-
- **Latency-only monitoring**: Set `memoryThresholdBytes` to 16MB (16 * 1024 * 1024) to trigger only on latency
|
|
2333
|
-
- **Combined monitoring**: Use both thresholds for comprehensive monitoring
|
|
2334
|
-
- **Execution plan analysis**: Set `showPlan: true` to include detailed execution plans in logs (useful for debugging)
|
|
2335
|
-
- **Cache debugging**: Set `logCache: true` to log cache operations and debug caching issues
|
|
2336
|
-
|
|
2337
|
-
**Memory Threshold Guidelines:**
|
|
2338
|
-
- **Conservative**: 4MB (25% of 16MB limit)
|
|
2339
|
-
- **Default**: 8MB (50% of 16MB limit)
|
|
2340
|
-
- **Aggressive**: 12MB (75% of 16MB limit)
|
|
2341
|
-
- **Critical**: 14MB (87.5% of 16MB limit)
|
|
2342
|
-
|
|
2343
|
-
#### Available Intervals
|
|
2344
|
-
|
|
2345
|
-
- `hour` - **Every hour (Required)** - Statistics are available for approximately 12 hours
|
|
2346
|
-
|
|
2347
|
-
> **⚠️ Important**: Due to Forge SQL's statistics retention policy (approximately 12 hours), **only `hour` interval is supported**. Using `day` or `week` intervals will result in incomplete or missing data.
|
|
2348
2429
|
|
|
2349
2430
|
## Migration Guide
|
|
2350
2431
|
|
|
@@ -2433,20 +2514,32 @@ const rawUsers = await forgeSQL.execute(
|
|
|
2433
2514
|
[true]
|
|
2434
2515
|
);
|
|
2435
2516
|
|
|
2517
|
+
// ⚠️ IMPORTANT: When using executeCacheable(), all table names must be wrapped with backticks (`)
|
|
2436
2518
|
const cachedRawUsers = await forgeSQL.executeCacheable(
|
|
2437
|
-
"SELECT * FROM users WHERE active = ?",
|
|
2519
|
+
"SELECT * FROM `users` WHERE active = ?",
|
|
2438
2520
|
[true],
|
|
2439
2521
|
300
|
|
2440
2522
|
);
|
|
2441
2523
|
|
|
2442
|
-
// ✅ Raw SQL execution with metadata capture
|
|
2524
|
+
// ✅ Raw SQL execution with metadata capture and performance monitoring
|
|
2443
2525
|
const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2526
|
+
async () => {
|
|
2527
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
2528
|
+
const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
2529
|
+
return { users, orders };
|
|
2530
|
+
},
|
|
2531
|
+
(totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2532
|
+
const threshold = 500; // ms baseline for this resolver
|
|
2533
|
+
|
|
2534
|
+
if (totalDbExecutionTime > threshold * 1.5) {
|
|
2535
|
+
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
2536
|
+
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
2537
|
+
} else if (totalDbExecutionTime > threshold) {
|
|
2538
|
+
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
2449
2539
|
}
|
|
2540
|
+
|
|
2541
|
+
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
2542
|
+
}
|
|
2450
2543
|
);
|
|
2451
2544
|
|
|
2452
2545
|
// ✅ DDL operations for schema modifications
|