@oalacea/demon 1.0.0

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.
@@ -0,0 +1,430 @@
1
+ # Database Performance Guide
2
+
3
+ This prompt is included by EXECUTE.md. It provides detailed guidance for database query performance testing.
4
+
5
+ ---
6
+
7
+ ## Query Benchmarking (Vitest)
8
+
9
+ ```typescript
10
+ // tests/db/benchmarks.test.ts
11
+ import { bench, describe, beforeAll } from 'vitest';
12
+ import { prisma } from '@/lib/db';
13
+
14
+ describe('Database Query Performance', () => {
15
+ let userId: string;
16
+
17
+ beforeAll(async () => {
18
+ // Setup test data
19
+ const user = await prisma.user.create({
20
+ data: {
21
+ email: 'bench@example.com',
22
+ name: 'Benchmark User',
23
+ posts: {
24
+ create: Array.from({ length: 100 }, (_, i) => ({
25
+ title: `Post ${i}`,
26
+ content: `Content ${i}`.repeat(10),
27
+ })),
28
+ },
29
+ },
30
+ });
31
+ userId = user.id;
32
+ });
33
+
34
+ describe('Index Usage', () => {
35
+ bench('SELECT by indexed field (email)', async () => {
36
+ await prisma.user.findUnique({
37
+ where: { email: 'bench@example.com' },
38
+ });
39
+ });
40
+
41
+ bench('SELECT by indexed field (id)', async () => {
42
+ await prisma.user.findUnique({
43
+ where: { id: userId },
44
+ });
45
+ });
46
+
47
+ bench('SELECT by non-indexed field (name)', async () => {
48
+ await prisma.user.findFirst({
49
+ where: { name: 'Benchmark User' },
50
+ });
51
+ });
52
+ });
53
+
54
+ describe('Loading Strategies', () => {
55
+ bench('N+1 pattern (bad)', async () => {
56
+ const users = await prisma.user.findMany({ take: 10 });
57
+ for (const user of users) {
58
+ await prisma.post.findMany({
59
+ where: { authorId: user.id },
60
+ });
61
+ }
62
+ });
63
+
64
+ bench('Eager loading with include (good)', async () => {
65
+ await prisma.user.findMany({
66
+ take: 10,
67
+ include: { posts: true },
68
+ });
69
+ });
70
+
71
+ bench('Eager loading with select (optimal)', async () => {
72
+ await prisma.user.findMany({
73
+ take: 10,
74
+ select: {
75
+ id: true,
76
+ name: true,
77
+ posts: {
78
+ select: {
79
+ id: true,
80
+ title: true,
81
+ },
82
+ },
83
+ },
84
+ });
85
+ });
86
+ });
87
+
88
+ describe('Pagination', () => {
89
+ bench('Offset-based pagination', async () => {
90
+ await prisma.user.findMany({
91
+ skip: 50,
92
+ take: 10,
93
+ });
94
+ });
95
+
96
+ bench('Cursor-based pagination', async () => {
97
+ await prisma.user.findMany({
98
+ take: 10,
99
+ cursor: { id: userId },
100
+ skip: 1,
101
+ });
102
+ });
103
+ });
104
+
105
+ describe('Aggregations', () => {
106
+ bench('COUNT query', async () => {
107
+ await prisma.user.count();
108
+ });
109
+
110
+ bench('COUNT with index', async () => {
111
+ await prisma.post.count({
112
+ where: { authorId: userId },
113
+ });
114
+ });
115
+
116
+ bench('Complex aggregation', async () => {
117
+ await prisma.post.aggregate({
118
+ where: { authorId: userId },
119
+ _count: true,
120
+ _avg: { likes: true },
121
+ _sum: { views: true },
122
+ });
123
+ });
124
+ });
125
+
126
+ describe('Transaction Performance', () => {
127
+ bench('Single write', async () => {
128
+ await prisma.user.create({
129
+ data: {
130
+ email: `test-${Date.now()}@example.com`,
131
+ name: 'Test',
132
+ },
133
+ });
134
+ });
135
+
136
+ bench('Batch write (10 records)', async () => {
137
+ await prisma.user.createMany({
138
+ data: Array.from({ length: 10 }, (_, i) => ({
139
+ email: `batch-${Date.now()}-${i}@example.com`,
140
+ name: `User ${i}`,
141
+ })),
142
+ });
143
+ });
144
+
145
+ bench('Transaction with multiple writes', async () => {
146
+ await prisma.$transaction([
147
+ prisma.user.create({
148
+ data: {
149
+ email: `trans-${Date.now()}@example.com`,
150
+ name: 'Trans User',
151
+ },
152
+ }),
153
+ prisma.post.create({
154
+ data: {
155
+ title: 'Trans Post',
156
+ content: 'Content',
157
+ author: {
158
+ connect: { email: `trans-${Date.now()}@example.com` },
159
+ },
160
+ },
161
+ }),
162
+ ]);
163
+ });
164
+ });
165
+ });
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Query Analysis
171
+
172
+ ```typescript
173
+ // tests/db/analysis.test.ts
174
+ import { describe, it, expect, beforeAll } from 'vitest';
175
+ import { prisma } from '@/lib/db';
176
+
177
+ describe('Query Analysis', () => {
178
+ describe('N+1 Detection', () => {
179
+ it('should not have N+1 in user posts query', async () => {
180
+ const queries: string[] = [];
181
+
182
+ // Log queries (requires Prisma preview feature)
183
+ const originalExecute = prisma.$queryRawUnsafe.bind(prisma);
184
+ prisma.$queryRawUnsafe = (...args: any[]) => {
185
+ queries.push(args[0]);
186
+ return originalExecute(...args);
187
+ };
188
+
189
+ // Execute the query pattern
190
+ const users = await prisma.user.findMany({
191
+ take: 5,
192
+ include: { posts: true },
193
+ });
194
+
195
+ // Should only be 2 queries (users + posts via join)
196
+ // Not N+1 (1 for users + N for posts)
197
+ expect(queries.length).toBeLessThanOrEqual(2);
198
+ });
199
+ });
200
+
201
+ describe('Index Usage', () => {
202
+ it('should use index for email lookup', async () => {
203
+ // This is a conceptual test - actual implementation varies
204
+ // Use EXPLAIN ANALYZE for real verification
205
+
206
+ const start = Date.now();
207
+ await prisma.user.findUnique({
208
+ where: { email: 'test@example.com' },
209
+ });
210
+ const indexedTime = Date.now() - start;
211
+
212
+ // Compare with non-indexed lookup
213
+ const start2 = Date.now();
214
+ await prisma.user.findFirst({
215
+ where: { name: 'Test User' },
216
+ });
217
+ const nonIndexedTime = Date.now() - start2;
218
+
219
+ // Indexed should be faster (with significant data)
220
+ expect(indexedTime).toBeLessThan(nonIndexedTime);
221
+ });
222
+ });
223
+
224
+ describe('Connection Pool', () => {
225
+ it('should handle concurrent queries efficiently', async () => {
226
+ const start = Date.now();
227
+
228
+ await Promise.all([
229
+ prisma.user.findMany({ take: 10 }),
230
+ prisma.post.findMany({ take: 10 }),
231
+ prisma.comment.findMany({ take: 10 }),
232
+ ]);
233
+
234
+ const concurrentTime = Date.now() - start;
235
+
236
+ // Sequential time
237
+ const start2 = Date.now();
238
+ await prisma.user.findMany({ take: 10 });
239
+ await prisma.post.findMany({ take: 10 });
240
+ await prisma.comment.findMany({ take: 10 });
241
+ const sequentialTime = Date.now() - start2;
242
+
243
+ // Concurrent should be faster
244
+ expect(concurrentTime).toBeLessThan(sequentialTime);
245
+ });
246
+ });
247
+ });
248
+ ```
249
+
250
+ ---
251
+
252
+ ## Prisma-Specific Patterns
253
+
254
+ ### Include vs Select
255
+
256
+ ```typescript
257
+ describe('Field Selection Performance', () => {
258
+ it('should prefer select over include for large data', async () => {
259
+ // Include loads all fields
260
+ const start1 = Date.now();
261
+ await prisma.user.findMany({
262
+ take: 100,
263
+ include: { posts: true },
264
+ });
265
+ const includeTime = Date.now() - start1;
266
+
267
+ // Select loads only needed fields
268
+ const start2 = Date.now();
269
+ await prisma.user.findMany({
270
+ take: 100,
271
+ select: {
272
+ id: true,
273
+ name: true,
274
+ posts: {
275
+ select: { id: true, title: true },
276
+ },
277
+ },
278
+ });
279
+ const selectTime = Date.now() - start2;
280
+
281
+ // Select should be faster for large datasets
282
+ expect(selectTime).toBeLessThan(includeTime);
283
+ });
284
+ });
285
+ ```
286
+
287
+ ### Lazy vs Eager Loading
288
+
289
+ ```typescript
290
+ describe('Loading Strategy', () => {
291
+ bench('Lazy loading (deferred)', async () => {
292
+ const user = await prisma.user.findUnique({
293
+ where: { id: userId },
294
+ });
295
+
296
+ if (user) {
297
+ await prisma.post.findMany({
298
+ where: { authorId: user.id },
299
+ });
300
+ }
301
+ });
302
+
303
+ bench('Eager loading (include)', async () => {
304
+ await prisma.user.findUnique({
305
+ where: { id: userId },
306
+ include: { posts: true },
307
+ });
308
+ });
309
+ });
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Performance Assertions
315
+
316
+ ```typescript
317
+ // tests/db/performance-assertions.test.ts
318
+ import { describe, it, expect } from 'vitest';
319
+ import { prisma } from '@/lib/db';
320
+
321
+ describe('Performance Assertions', () => {
322
+ it('should return user list within 100ms', async () => {
323
+ const start = Date.now();
324
+ await prisma.user.findMany({ take: 50 });
325
+ const duration = Date.now() - start;
326
+
327
+ expect(duration).toBeLessThan(100);
328
+ });
329
+
330
+ it('should create user within 50ms', async () => {
331
+ const start = Date.now();
332
+ await prisma.user.create({
333
+ data: {
334
+ email: `perf-${Date.now()}@example.com`,
335
+ name: 'Perf Test',
336
+ },
337
+ });
338
+ const duration = Date.now() - start;
339
+
340
+ expect(duration).toBeLessThan(50);
341
+ });
342
+
343
+ it('should handle 100 concurrent queries without timeouts', async () => {
344
+ const results = await Promise.allSettled(
345
+ Array.from({ length: 100 }, () =>
346
+ prisma.user.findFirst({ where: { id: userId } })
347
+ )
348
+ );
349
+
350
+ const failures = results.filter((r) => r.status === 'rejected');
351
+ expect(failures).toHaveLength(0);
352
+ });
353
+ });
354
+ ```
355
+
356
+ ---
357
+
358
+ ## Migration Performance
359
+
360
+ ```typescript
361
+ // tests/db/migration-performance.test.ts
362
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
363
+ import { prisma } from '@/lib/db';
364
+
365
+ describe('Migration Performance', () => {
366
+ beforeAll(async () => {
367
+ // Ensure clean state
368
+ await prisma.user.deleteMany({});
369
+ });
370
+
371
+ afterAll(async () => {
372
+ await prisma.user.deleteMany({});
373
+ });
374
+
375
+ it('should migrate 1000 records efficiently', async () => {
376
+ const start = Date.now();
377
+
378
+ // Batch insert
379
+ await prisma.user.createMany({
380
+ data: Array.from({ length: 1000 }, (_, i) => ({
381
+ email: `migrate-${i}@example.com`,
382
+ name: `User ${i}`,
383
+ })),
384
+ skipDuplicates: true,
385
+ });
386
+
387
+ const duration = Date.now() - start;
388
+
389
+ // Should complete in reasonable time
390
+ expect(duration).toBeLessThan(5000);
391
+ });
392
+
393
+ it('should update 1000 records efficiently', async () => {
394
+ // First create
395
+ await prisma.user.createMany({
396
+ data: Array.from({ length: 1000 }, (_, i) => ({
397
+ email: `update-${i}@example.com`,
398
+ name: `User ${i}`,
399
+ })),
400
+ });
401
+
402
+ const start = Date.now();
403
+
404
+ // Batch update
405
+ await prisma.user.updateMany({
406
+ where: { email: { startsWith: 'update-' } },
407
+ data: { name: 'Updated' },
408
+ });
409
+
410
+ const duration = Date.now() - start;
411
+
412
+ expect(duration).toBeLessThan(2000);
413
+ });
414
+ });
415
+ ```
416
+
417
+ ---
418
+
419
+ ## Running DB Performance Tests
420
+
421
+ ```bash
422
+ # Run all performance tests
423
+ docker exec demon-tools npm test -- tests/db/benchmarks.test.ts --reporter=verbose
424
+
425
+ # Run specific benchmark
426
+ docker exec demon-tools npm test -- tests/db/benchmarks.test.ts --bench
427
+
428
+ # Run with memory profiling
429
+ docker exec demon-tools node --expose-gc -r ts-node/register tests/db/benchmarks.test.ts
430
+ ```