About backend-perf
A code-review skill that checks backend performance patterns and reports whether each check applies and passes. It is for developers reviewing backend changes, with PostgreSQL-specific checks kept distinct from general ones.
Key features
- 01Parallelize independent async calls
- 02Detect sequential N+1 queries
- 03Check pagination count queries
- 04Review PostgreSQL index coverage
- 05Check reuse of stable functions
- 06Inspect race-condition handling
Use cases
- 01Reviewing backend changes before merge
- 02Auditing PostgreSQL query plans and indexes
- 03Finding redundant fetches and oversized selects
Install
Copy the command and run it in a terminal.
$
npx skills add https:/ / github.com/ bhagyamudgal/ skills --skill backend-perfAfter installing, type/backend-perfin Claude Code to confirm it loaded.
Details
From the project READMEBackend performance checklist
I walk every check below against the code in hand and state a verdict on each one. Applied, clean, or not applicable and why. A check I did not name is a check I did not run.
Checks marked [pg] are PostgreSQL-specific. I skip them on other stores rather than translating them.
- Parallel async. Independent async calls run together in
Promise.all, never awaited one by one. - N+1 queries. No query inside a sequential
fororfor awaitloop. Batch withINclauses or a join instead.Promise.all(items.map(q => oneQuery(q)))over a few index-friendly queries runs in parallel and is not a sequential N+1. Cap the fan-out: an unbounded items list gets an explicit bound or concurrency limit, since N parallel queries still open N connections. Keep the parallel shape unlessEXPLAINshows the batched join runs faster, because batching often defeats the per-row index. - Pagination counts. Count and rows come from one query: a
count(*) OVER ()window or a single CTE, not a separateCOUNT(*)next to the data query. - Index coverage
[pg]. Every productionWHEREclause needs a matching index.similarity(col, ...),@@ to_tsvector(...), orLIKE '%pattern%'needs the matchinggin (col gin_trgm_ops)orgin (to_tsvector('lang', col))index in the same PR, or a one-line comment citing the ADR or issue that accepts the sequential scan. Prefer the indexable%operator over a rawsimilarity() > thresholdcomparison, but only with a matching threshold:%tests against the sessionpg_trgm.similarity_threshold(default 0.3), so set it to the query's threshold and confirm result-set equivalence, and EXPLAIN-verify that the planner picks the index. - STABLE function reuse
[pg].pg_trgm.similarityandts_rankare STABLE, not IMMUTABLE, so PostgreSQL does not reliably memoize duplicate calls in one SELECT. A value needed twice, for example once in the projection and once inGREATEST(...), is computed once in aWITH ... AS MATERIALIZEDCTE. A plain subquery alias does not guarantee this. The planner can flatten it and evaluate per row. - Select only needed columns. List endpoints name their columns instead of selecting the whole row.
- Reuse fetched rows. A row fetched for an ownership check is the row the handler returns. No second fetch by id.
- EXPLAIN before rewriting
[pg]. Any query restructure, batching, joining, indexing, or replacement of parallel-N with batched-1 shipsEXPLAIN (ANALYZE, BUFFERS)numbers for current versus proposed shape on representative data. Keep the faster shape. A rewrite that looks faster in the diff can run 10-100× slower on real data. - Race conditions. When 2+ writers can touch one row, use a transaction. Async completion order is not source order. Order explicitly with
awaitorPromise.allinstead of relying on accidental serialization. A check plus a write that must stay atomic usesSELECT ... FOR UPDATEon[pg]or a unique constraint, not application-level locking.
Comments
Markdown supported