Files
clawmates/skills/backend/postgres-index-selection.md
T
Omar SobhandClaude Opus 4.7 7b23f61632
ci / gates (push) Successful in 4s
ci / frontend (push) Successful in 25s
ci / rust (push) Failing after 3m41s
ci / e2e (push) Skipped
ci / publish (push) Skipped
slice 3.5c: seed 15 built-in skills across the 6 stacks
Hand-authored skill catalog anchored to real 2026-07 versions:
  - Rust 1.97.1 (stable), edition 2024
  - React 19.2.7, Server Components + Actions
  - TailwindCSS 4.3.3 (CSS-first config, Oxide engine)
  - three.js r185 (WebGPURenderer stable, BatchedMesh matured)
  - React Native 0.86 / Expo SDK 54+ (New Architecture default)
  - cargo-nextest 0.9.140, gitleaks 8.20+, cargo-audit 0.21+
  - Postgres 17 (18 in beta, don't rely on)
  - CUDA Blackwell, Metal Apple7+, ROCm CDNA3

Ships 15 skills across the categories:
  foundation/  workspace-repo-commit-protocol
               small-focused-commits
               tdd-red-green-refactor
               code-review-checklist
               int-xx-marker-protocol
               decompose-int-items
  rust/        write-rust-current-edition
               rust-error-handling
               cargo-test-driven-development
               rust-async-tokio-idioms
  backend/     postgres-migrations-forward-only
               postgres-index-selection
               api-pagination-day-1
  frontend/    react-19-server-components
               tailwind-v4-idioms
               component-4-state-model
  mobile/      expo-managed-vs-bare
               rn-flashlist-perf
  gpu/         gpu-coalescing-and-occupancy
               roofline-model
  threejs/     threejs-perf-and-teardown
  security/    cargo-audit-workflow
               secret-scanning-gitleaks

skills_loader.rs walks skills/**/*.md, parses YAML frontmatter
(name, description, when_to_use, tags), upserts via
skills_catalog::upsert_builtin. Idempotent per boot — bumps version
+ appends skill_versions row ONLY when body changes. Deterministic
sha256-derived ids so builtins are stable across boots.

Dockerfile copies skills/ to /etc/clawmates/skills. Server boot
task spawns loader alongside team_template_loader.

Follow-ups (Slice 3.5c continuation, future PRs):
  - 20-30 more skills (duckdb, shadcn composition, a11y, WebGPU
    migration, metal frame capture, rocprof, deep gitea forge
    integration, semgrep rulepacks)
  - Bind skills to team template roles (add [role.skills] refs to
    templates/teams/*.toml + wire template_role_skills population
    in team_template_loader)

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-19 13:55:44 -07:00

77 lines
3.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: postgres-index-selection
description: Which index type to reach for — btree/gin/gist/brin/hash — and when partial or expression indexes beat a plain one.
when_to_use: You're adding an index or diagnosing a slow query in Postgres.
tags: [backend, postgres, performance]
---
# Postgres index selection (17)
Default to **btree** unless the data shape says otherwise.
## btree
- Equality + range on scalar columns.
- Multi-column: leading column must be in the WHERE clause for the index to help. `(a, b, c)` helps `WHERE a=? AND b=?`, not `WHERE b=?`.
- Descending: rare — btree can scan in either direction, but `ORDER BY x DESC` with a mixed-direction multi-col query benefits from `(a ASC, b DESC)`.
## Partial indexes — huge wins
Any time a WHERE clause repeats across queries, a partial index is a smaller, faster index:
```sql
CREATE INDEX missions_running_idx
ON missions (updated_at DESC)
WHERE status IN ('running', 'pending');
```
The clawmates codebase leans on this heavily — see `topology_runs_status_idx WHERE status = 'queued'`.
## Expression indexes
For computed lookups:
```sql
CREATE UNIQUE INDEX users_lower_email_idx ON users (lower(email));
-- Query MUST use the exact expression:
SELECT * FROM users WHERE lower(email) = lower($1);
```
## GIN
- **JSONB**: `CREATE INDEX foo_meta_gin ON foo USING gin (metadata jsonb_path_ops);``jsonb_path_ops` is smaller + faster than default for `@>` queries. Use default when you also need `?` / `?|` / `?&`.
- **Arrays**: `CREATE INDEX skills_tags_gin ON skills USING gin (tags);``WHERE tags && ARRAY['rust']` becomes index-served.
- **Full-text search**: `to_tsvector('english', body)` with a GIN index.
## GiST
- Geo (PostGIS): `USING gist` on `geometry` / `geography` columns.
- Range types: `USING gist` on `tstzrange` for "does this range overlap" queries.
## BRIN
- Very large tables where the indexed column correlates with physical row order (append-only timestamps, sequential ids). 1/1000th the size of btree, works only for range scans.
## Hash (rare)
- Equality-only, very high-cardinality, no range. Postgres 10+ hash indexes are WAL-logged + replicated so they're finally safe — but btree wins in most benchmarks. Reach for it only when profiling justifies.
## Diagnosis flow
```sql
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT ...;
```
Read from bottom up. Watch for:
- **Seq Scan on a hot table** with a WHERE that should be indexed → missing index.
- **Index Scan** with `Rows Removed by Filter` > returned rows → wrong index; add a compound or partial.
- **Bitmap Heap Scan** with `Recheck` on lossy → index is being used but not tightly; check if partial would help.
- **Sort** with `Method: external merge Disk` → sort spills to disk; add index that provides the order.
## Anti-patterns
- **Indexing every column**. Each index has a write cost (INSERT/UPDATE amplification). Prefer 12 well-chosen composites over 6 single-column indexes.
- **Indexing a boolean without WHERE**. `CREATE INDEX foo_active_idx ON foo(active)` is worthless; `... WHERE active = true` is what you want.
- **Trusting `pg_stat_user_indexes.idx_scan = 0`** without waiting a full billing cycle. Some indexes exist for the monthly report.