Best for
- Slow page loads (database bottleneck)
- Query timeout errors
- N+1 queries
kensaurus/cursor-kenji/skills/backend-db-performance/SKILL.md
Optimize slow queries, indexes, and N+1s. Use when "slow query", "database performance", "add an index", or "N+1". Schema consistency → audit-db-schema. RLS access control → plan-rls-audit.
Decision brief
Degree of freedom: MIXED. Which query/index/N+1 to fix [HIGH freedom]; existing-index probes and EXPLAIN ANALYZE [LOW freedom — run exactly].
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/kensaurus/cursor-kenji --skill "skills/backend-db-performance"Inspect the Agent Skill "backend-db-performance" from https://github.com/kensaurus/cursor-kenji/blob/28a0bd8403c950f58ed063d47a858ee3493b0038/skills/backend-db-performance/SKILL.md at commit 28a0bd8403c950f58ed063d47a858ee3493b0038. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
1. Observe — EXPLAIN ANALYZE / pgstatstatements / existing pgindexes 2. Interpret — seq scan vs N+1 vs over-fetch vs missing pagination 3. Classify — add-index / eager-load / narrow-select / paginate / leave-alone 4. Severity — write-path timeout outranks a 200ms list page
Observe: /feed p95 2.4s; Prisma logs 81 queries; pgindexes has no idxpostsusercreated. Interpret: findMany posts then per-row user.findUnique — N+1; ORDER BY createdat is a seq scan. Classify: eager-load include: { author } + composite index (userid, createdat DESC). Verify: EXP…
Systematic approach to identifying and fixing database performance issues.
Slow page loads (database bottleneck)
Before ANY optimization, verify current state:
Permission review
No configured static risk pattern was detected
This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.
Evidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 92/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 9 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Degree of freedom: MIXED. Which query/index/N+1 to fix [HIGH freedom];
existing-index probes and EXPLAIN ANALYZE [LOW freedom — run exactly].
pg_stat_statements / existing pg_indexesObserve:
/feedp95 2.4s; Prisma logs 81 queries;pg_indexeshas noidx_posts_user_created. Interpret:findManyposts then per-rowuser.findUnique— N+1;ORDER BY created_atis a seq scan. Classify: eager-loadinclude: { author }+ composite index(user_id, created_at DESC). Verify: EXPLAIN ANALYZE → Index Scan; query count 2; p95 < 200ms. Did not add a duplicate index.
pg_indexes / migrations before CREATE INDEXaudit-db-schema; RLS access → plan-rls-auditSystematic approach to identifying and fixing database performance issues.
Before ANY optimization, verify current state:
SELECT indexname, indexdef FROM pg_indexes
WHERE schemaname = 'public' AND tablename = 'your_table';
ls -la supabase/migrations/ | grep -i "index\|optim\|perf"
SELECT 1 FROM pg_indexes WHERE indexname = 'your_proposed_index';
get_advisors MCP tool for performance/securityWhy: Duplicate indexes waste storage and slow writes. Always verify before adding.
Prisma - Enable query logging:
// lib/db.ts
import { PrismaClient } from '@prisma/client'
export const db = new PrismaClient({
log: [
{ emit: 'event', level: 'query' },
],
})
db.$on('query', (e) => {
if (e.duration > 100) { // Log queries > 100ms
console.log(`Slow query (${e.duration}ms):`, e.query)
}
})
Supabase - Query analysis:
-- Enable query stats
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Find slow queries
SELECT
query,
calls,
total_time / calls as avg_time_ms,
rows / calls as avg_rows
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 20;
| Issue | Symptom | Solution |
|---|---|---|
| N+1 Queries | Many small queries | Use include / eager load |
| Missing Index | Slow WHERE/JOIN | Add index on filtered columns |
| Full Table Scan | Slow on large tables | Add index, limit results |
| Over-fetching | Slow response | Select only needed fields |
| No Pagination | Memory issues | Add cursor/offset pagination |
Problem: Fetching related data in loop
// Bad - N+1 queries
const posts = await db.post.findMany()
for (const post of posts) {
const author = await db.user.findUnique({ where: { id: post.authorId } })
// 1 query for posts + N queries for authors
}
Solution: Eager loading
// Good - 2 queries total
const posts = await db.post.findMany({
include: {
author: true,
},
})
// Or with select for specific fields
const posts = await db.post.findMany({
include: {
author: {
select: { id: true, name: true, avatar: true }
},
},
})
Supabase equivalent:
// Single query with join
const { data: posts } = await supabase
.from('posts')
.select(`
*,
author:users(id, name, avatar)
`)
Add index when column is used in:
WHERE clauses (filtering)JOIN conditionsORDER BY clausesDon't add index when:
-- Single column index
CREATE INDEX idx_posts_user_id ON posts(user_id);
-- Composite index (order matters!)
CREATE INDEX idx_posts_user_created ON posts(user_id, created_at DESC);
-- Unique index
CREATE UNIQUE INDEX idx_users_email ON users(email);
-- Partial index (index subset of rows)
CREATE INDEX idx_posts_published ON posts(created_at)
WHERE published = true;
-- GIN index for JSONB/array
CREATE INDEX idx_posts_tags ON posts USING GIN(tags);
-- Full-text search
CREATE INDEX idx_posts_search ON posts
USING GIN(to_tsvector('english', title || ' ' || content));
model Post {
id String @id @default(cuid())
userId String
title String
status Status
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
// Single column index
@@index([userId])
// Composite index
@@index([userId, createdAt(sort: Desc)])
// Unique constraint (creates unique index)
@@unique([userId, title])
}
// Bad - fetches all columns
const users = await db.user.findMany()
// Good - fetches only needed
const users = await db.user.findMany({
select: {
id: true,
name: true,
email: true,
},
})
Offset pagination (simple, but slow at high offsets):
const posts = await db.post.findMany({
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' },
})
Cursor pagination (better for large datasets):
const posts = await db.post.findMany({
take: limit,
skip: cursor ? 1 : 0, // Skip cursor itself
cursor: cursor ? { id: cursor } : undefined,
orderBy: { createdAt: 'desc' },
})
// Return next cursor
const nextCursor = posts.length === limit ? posts[posts.length - 1].id : null
// Bad - individual inserts
for (const item of items) {
await db.item.create({ data: item })
}
// Good - batch insert
await db.item.createMany({
data: items,
skipDuplicates: true,
})
// Good - transaction for related data
await db.$transaction([
db.order.create({ data: order }),
db.orderItem.createMany({ data: orderItems }),
db.inventory.updateMany({ where: {...}, data: {...} }),
])
// Get count without fetching data
const count = await db.post.count({
where: { published: true },
})
// Combined with pagination
const [posts, count] = await db.$transaction([
db.post.findMany({ where, take: limit, skip: offset }),
db.post.count({ where }),
])
Normalize when:
Denormalize when:
-- Normalized (separate table)
CREATE TABLE post_stats (
post_id UUID PRIMARY KEY REFERENCES posts(id),
view_count INT DEFAULT 0,
like_count INT DEFAULT 0
);
-- Denormalized (same table)
ALTER TABLE posts
ADD COLUMN view_count INT DEFAULT 0,
ADD COLUMN like_count INT DEFAULT 0;
-- Use appropriate types
id UUID DEFAULT gen_random_uuid() -- vs TEXT for IDs
status VARCHAR(20) -- vs unlimited TEXT
price DECIMAL(10,2) -- vs FLOAT for money
created_at TIMESTAMPTZ -- vs TIMESTAMP (include timezone)
-- Use enums for fixed values
CREATE TYPE status AS ENUM ('draft', 'published', 'archived');
model Post {
id String @id
deletedAt DateTime?
@@index([deletedAt]) // Index for filtering
}
// Query pattern
const posts = await db.post.findMany({
where: { deletedAt: null },
})
-- Bad: Function call in RLS (slow)
CREATE POLICY "slow_policy" ON posts
FOR SELECT USING (
user_id IN (SELECT user_id FROM team_members WHERE team_id = get_user_team())
);
-- Good: Direct comparison (fast)
CREATE POLICY "fast_policy" ON posts
FOR SELECT USING (user_id = auth.uid());
-- Good: Join-based (when needed)
CREATE POLICY "team_policy" ON posts
FOR SELECT USING (
EXISTS (
SELECT 1 FROM team_members
WHERE team_members.team_id = posts.team_id
AND team_members.user_id = auth.uid()
)
);
// Move complex aggregations to Edge Functions
// instead of multiple round trips
// supabase/functions/dashboard-stats/index.ts
Deno.serve(async (req) => {
const stats = await supabase.rpc('get_dashboard_stats', {
user_id: userId
})
return new Response(JSON.stringify(stats))
})
EXPLAIN ANALYZE
SELECT * FROM posts
WHERE user_id = 'abc123'
ORDER BY created_at DESC
LIMIT 20;
-- Look for:
-- - Seq Scan (bad on large tables)
-- - Index Scan (good)
-- - Nested Loop (check if N+1)
-- - High actual time
| Metric | Target | Action if Exceeded |
|---|---|---|
| Query time | < 100ms | Add index, optimize |
| Rows scanned | < 10x returned | Add index |
| Memory usage | < 256MB | Add LIMIT, pagination |
| Connection count | < pool size | Use connection pooling |
Frequently asked questions
Degree of freedom: MIXED. Which query/index/N+1 to fix [HIGH freedom]; existing-index probes and EXPLAIN ANALYZE [LOW freedom — run exactly].
The source record exposes this install command: npx skills add https://github.com/kensaurus/cursor-kenji --skill "skills/backend-db-performance". Inspect the command and pinned source before running it.
Alternatives
brucesongs/kali-claw
Insecure Design (OWASP A06:2025) focuses on security flaws in system architecture and design phases, rather than code implementation-level bugs.
NintendaDev/unikit-ai
Generate and maintain the project's TECHNICAL documentation from its codebase — scans the project structure, tech stack, and module boundaries, then writes a lean README landing page plus detailed topic pages (architecture, modules, setup, build, APIs), only the docs that are relevant. Use whenever the user wants to create, update, or validate documentation of the CODE or the project itself, e.g. "generate documentation", "create docs", "write the README", "update the project docs", "document th
Jamie-BitFlight/claude_skills
Create high-quality Claude Code agents from scratch or by adapting existing agents as templates. Use when the user wants to create a new agent, modify agent configurations, build specialized subagents, or design agent architectures. Guides through requirements gathering, template selection, and agent file generation following Anthropic best practices (v2.1.63+).
magnus919/agent-skills
Use this skill to reverse-engineer an existing software system, map its architecture, data flow, privacy posture, coupling, quality characteristics, and feature surface, then produce an evidence-grounded clean-room design document, PRD, or migration plan under new constraints. Use for codebase archaeology, implicit contract extraction, architecture health assessment, or decomposition-readiness analysis. Do not use for greenfield architecture design, direct code review, bug hunting, security audi