Query Optimization
by IvanTorresEdge
Database query optimization strategies. Use when improving query performance.
Skill Details
Repository Files
1 file in this skill directory
name: query-optimization description: Database query optimization strategies. Use when improving query performance.
Query Optimization Skill
This skill covers database query optimization for Node.js applications.
When to Use
Use this skill when:
- Queries are slow
- Database CPU is high
- Optimizing N+1 queries
- Adding indexes strategically
Core Principle
MEASURE FIRST, OPTIMIZE SECOND - Profile before optimizing. The bottleneck is often not where you expect.
N+1 Query Prevention
The Problem
// BAD: N+1 queries
const posts = await prisma.post.findMany();
for (const post of posts) {
// This runs a query for each post!
const author = await prisma.user.findUnique({
where: { id: post.authorId },
});
console.log(`${post.title} by ${author?.name}`);
}
The Solution
// GOOD: Single query with include
const posts = await prisma.post.findMany({
include: {
author: {
select: { id: true, name: true },
},
},
});
for (const post of posts) {
console.log(`${post.title} by ${post.author.name}`);
}
DataLoader Pattern
// src/lib/dataloader.ts
import DataLoader from 'dataloader';
import { PrismaClient } from '@prisma/client';
export function createUserLoader(prisma: PrismaClient) {
return new DataLoader<string, User | null>(async (ids) => {
const users = await prisma.user.findMany({
where: { id: { in: [...ids] } },
});
const userMap = new Map(users.map((u) => [u.id, u]));
return ids.map((id) => userMap.get(id) ?? null);
});
}
// Usage
const userLoader = createUserLoader(prisma);
const posts = await prisma.post.findMany();
// Batches all user lookups into single query
const postsWithAuthors = await Promise.all(
posts.map(async (post) => ({
...post,
author: await userLoader.load(post.authorId),
}))
);
Index Strategies
When to Add Indexes
-- Columns in WHERE clauses
CREATE INDEX posts_author_id_idx ON posts(author_id);
-- Columns in JOIN conditions
CREATE INDEX comments_post_id_idx ON comments(post_id);
-- Columns in ORDER BY
CREATE INDEX posts_created_at_idx ON posts(created_at DESC);
-- Compound indexes for combined queries
CREATE INDEX posts_published_created_idx ON posts(published, created_at DESC);
Prisma Index Definition
model Post {
id String @id
authorId String
published Boolean
createdAt DateTime
@@index([authorId])
@@index([published, createdAt(sort: Desc)])
}
Drizzle Index Definition
export const posts = pgTable('posts', {
id: text('id').primaryKey(),
authorId: text('author_id'),
published: boolean('published'),
createdAt: timestamp('created_at'),
}, (table) => ({
authorIdx: index('posts_author_idx').on(table.authorId),
publishedCreatedIdx: index('posts_published_created_idx')
.on(table.published, desc(table.createdAt)),
}));
Query Analysis
Explain Analyze
// Prisma
const result = await prisma.$queryRaw`
EXPLAIN ANALYZE
SELECT * FROM posts WHERE author_id = ${userId}
`;
console.log(result);
// Drizzle
const result = await db.execute(sql`
EXPLAIN ANALYZE
SELECT * FROM posts WHERE author_id = ${userId}
`);
Query Logging
// Prisma with query logging
const prisma = new PrismaClient({
log: [
{
emit: 'event',
level: 'query',
},
],
});
prisma.$on('query', (e) => {
if (e.duration > 100) { // Log slow queries (>100ms)
console.warn('Slow query:', {
query: e.query,
duration: `${e.duration}ms`,
});
}
});
Pagination Optimization
Offset Pagination (Simple but Slow)
// Gets slower as offset increases
const posts = await prisma.post.findMany({
skip: (page - 1) * perPage,
take: perPage,
orderBy: { createdAt: 'desc' },
});
Cursor Pagination (Fast)
// Constant performance regardless of page
async function getPosts(cursor?: string, limit = 20) {
const posts = await prisma.post.findMany({
take: limit + 1, // Fetch one extra to check if more exist
cursor: cursor ? { id: cursor } : undefined,
orderBy: { createdAt: 'desc' },
});
const hasMore = posts.length > limit;
const items = hasMore ? posts.slice(0, -1) : posts;
const nextCursor = hasMore ? items[items.length - 1]?.id : null;
return { items, nextCursor, hasMore };
}
Select Only What You Need
// BAD: Fetching all columns
const users = await prisma.user.findMany();
// GOOD: Select specific columns
const users = await prisma.user.findMany({
select: {
id: true,
name: true,
email: true,
},
});
// Drizzle equivalent
const users = await db
.select({
id: users.id,
name: users.name,
email: users.email,
})
.from(users);
Batch Operations
// BAD: Individual inserts
for (const item of items) {
await prisma.item.create({ data: item });
}
// GOOD: Batch insert
await prisma.item.createMany({
data: items,
});
// Drizzle batch insert
await db.insert(items).values(itemsData);
Connection Pooling
// Prisma connection pool
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
});
// For serverless, use external pooler (PgBouncer, Supabase Pooler)
// DATABASE_URL=postgres://...?pgbouncer=true
Caching Strategies
// In-memory cache for frequently accessed data
import { LRUCache } from 'lru-cache';
const userCache = new LRUCache<string, User>({
max: 500,
ttl: 1000 * 60 * 5, // 5 minutes
});
async function getUserById(id: string): Promise<User | null> {
const cached = userCache.get(id);
if (cached) return cached;
const user = await prisma.user.findUnique({ where: { id } });
if (user) userCache.set(id, user);
return user;
}
// Invalidate on update
async function updateUser(id: string, data: UserUpdate): Promise<User> {
const user = await prisma.user.update({ where: { id }, data });
userCache.set(id, user);
return user;
}
Query Optimization Checklist
- Check indexes - Ensure WHERE/JOIN columns are indexed
- **Avoid SELECT *** - Select only needed columns
- Use includes - Prevent N+1 queries
- Cursor pagination - For large datasets
- Batch operations - Group inserts/updates
- Connection pooling - Especially for serverless
- Query caching - For read-heavy data
Monitoring Queries
// Prisma metrics
import { Prisma } from '@prisma/client';
const prisma = new PrismaClient().$extends({
query: {
$allOperations({ operation, model, args, query }) {
const start = performance.now();
return query(args).finally(() => {
const duration = performance.now() - start;
if (duration > 100) {
console.warn(`Slow ${model}.${operation}: ${duration.toFixed(2)}ms`);
}
});
},
},
});
Best Practices
- Profile first - Measure before optimizing
- Index strategically - Not every column needs an index
- Monitor slow queries - Set up alerts
- Use EXPLAIN ANALYZE - Understand query plans
- Test with production data - Performance varies with data size
- Review regularly - Queries that were fast may become slow
Notes
- Indexes speed up reads but slow down writes
- Composite indexes order matters
- Cursor pagination is preferred for APIs
- Cache invalidation is harder than caching
Related Skills
Xlsx
Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work with spreadsheets (.xlsx, .xlsm, .csv, .tsv, etc) for: (1) Creating new spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modify existing spreadsheets while preserving formulas, (4) Data analysis and visualization in spreadsheets, or (5) Recalculating formulas
Clickhouse Io
ClickHouse database patterns, query optimization, analytics, and data engineering best practices for high-performance analytical workloads.
Clickhouse Io
ClickHouse database patterns, query optimization, analytics, and data engineering best practices for high-performance analytical workloads.
Analyzing Financial Statements
This skill calculates key financial ratios and metrics from financial statement data for investment analysis
Data Storytelling
Transform data into compelling narratives using visualization, context, and persuasive structure. Use when presenting analytics to stakeholders, creating data reports, or building executive presentations.
Kpi Dashboard Design
Design effective KPI dashboards with metrics selection, visualization best practices, and real-time monitoring patterns. Use when building business dashboards, selecting metrics, or designing data visualization layouts.
Dbt Transformation Patterns
Master dbt (data build tool) for analytics engineering with model organization, testing, documentation, and incremental strategies. Use when building data transformations, creating data models, or implementing analytics engineering best practices.
Sql Optimization Patterns
Master SQL query optimization, indexing strategies, and EXPLAIN analysis to dramatically improve database performance and eliminate slow queries. Use when debugging slow queries, designing database schemas, or optimizing application performance.
Anndata
This skill should be used when working with annotated data matrices in Python, particularly for single-cell genomics analysis, managing experimental measurements with metadata, or handling large-scale biological datasets. Use when tasks involve AnnData objects, h5ad files, single-cell RNA-seq data, or integration with scanpy/scverse tools.
Xlsx
Spreadsheet toolkit (.xlsx/.csv). Create/edit with formulas/formatting, analyze data, visualization, recalculate formulas, for spreadsheet processing and analysis.
