Ai supabase-postgres
Postgres performance optimization and best practices from Supabase — covering indexing, connection management, RLS security, schema design, locking, data access patterns, and monitoring. Use when writing SQL, designing schemas, optimizing queries, or configuring Postgres.
git clone https://github.com/wpank/ai
T=$(mktemp -d) && git clone --depth=1 https://github.com/wpank/ai "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/backend/supabase-postgres" ~/.claude/skills/wpank-ai-supabase-postgres && rm -rf "$T"
skills/backend/supabase-postgres/SKILL.mdSupabase Postgres Best Practices
Comprehensive Postgres performance guide organized by impact priority. Each rule includes incorrect vs. correct SQL examples with explanations.
When to Use
- Writing SQL queries or designing schemas
- Implementing or reviewing indexes
- Debugging slow queries or connection issues
- Configuring connection pooling
- Implementing Row-Level Security (RLS)
- Reviewing database performance
Rule Categories by Priority
| Priority | Category | Impact | Reference Prefix |
|---|---|---|---|
| 1 | Query Performance | CRITICAL | |
| 2 | Connection Management | CRITICAL | |
| 3 | Security & RLS | CRITICAL | |
| 4 | Schema Design | HIGH | |
| 5 | Concurrency & Locking | MEDIUM-HIGH | |
| 6 | Data Access Patterns | MEDIUM | |
| 7 | Monitoring & Diagnostics | LOW-MEDIUM | |
| 8 | Advanced Features | LOW | |
See
references/ for detailed rule files with full SQL examples.
Installation
OpenClaw / Moltbot / Clawbot
npx clawhub@latest install supabase-postgres
Critical Rules Summary
Query Performance
Always index WHERE and JOIN columns. Unindexed columns cause full table scans that get exponentially slower as tables grow. Create indexes on every column used in WHERE, JOIN, or ORDER BY on large tables.
-- Create index on frequently filtered column create index orders_customer_id_idx on orders (customer_id);
Choose the right index type. B-tree (default) handles
=, <, >, BETWEEN. Use GIN for JSONB/arrays/full-text, BRIN for large time-series tables, Hash for equality-only lookups.
create index products_attrs_idx on products using gin (attributes); -- JSONB create index events_time_idx on events using brin (created_at); -- Time-series
Use partial indexes for queries that always filter on the same condition. They're smaller, faster, and cheaper to maintain.
create index orders_pending_idx on orders (created_at) where status = 'pending';
Connection Management
Always use connection pooling. Each Postgres connection uses 1-3MB RAM. Without pooling, 500 concurrent users = 500 connections = crashed database.
- Use PgBouncer or Supabase's built-in pooler
- Transaction mode for most apps (connection returned after each transaction)
- Session mode only when using prepared statements or temp tables
- Formula: pool_size =
(CPU cores * 2) + disk_count
Set appropriate connection limits. Monitor with:
select count(*), state from pg_stat_activity group by state;
Security & RLS
Enable RLS for multi-tenant data. Application-level filtering alone is one bug away from exposing all data.
alter table orders enable row level security; create policy orders_policy on orders for all to authenticated using ((select auth.uid()) = user_id); -- Wrap in SELECT for performance
Optimize RLS policies. Wrap function calls in
(select ...) so they execute once, not per-row. On a 1M-row table, this is 100x+ faster.
-- BAD: auth.uid() called per row using (auth.uid() = user_id); -- GOOD: auth.uid() called once, cached using ((select auth.uid()) = user_id);
High-Impact Rules Summary
Schema Design
Choose appropriate data types:
create table users ( id bigint generated always as identity primary key, -- Not serial email text, -- Not varchar(n) created_at timestamptz, -- Not timestamp is_active boolean default true, -- Not varchar price numeric(10,2) -- Not float );
Key guidelines:
bigint over int, text over varchar(n), timestamptz over timestamp, numeric over float for money.
Select optimal primary keys:
bigint identity for single-database, UUIDv7 for distributed systems. Avoid random UUIDv4 as PK on large tables (causes index fragmentation).
Always index foreign key columns. Postgres does NOT auto-index FKs. Missing FK indexes cause slow JOINs and CASCADE operations.
-- Find missing FK indexes select conrelid::regclass as table_name, a.attname as fk_column from pg_constraint c join pg_attribute a on a.attrelid = c.conrelid and a.attnum = any(c.conkey) where c.contype = 'f' and not exists ( select 1 from pg_index i where i.indrelid = c.conrelid and a.attnum = any(i.indkey) );
Concurrency & Locking
Prevent deadlocks with consistent lock ordering. Always acquire locks in a deterministic order (e.g., by ID).
-- Lock rows in ID order before updating begin; select * from accounts where id in (1, 2) order by id for update; update accounts set balance = balance - 100 where id = 1; update accounts set balance = balance + 100 where id = 2; commit;
Data Access Patterns
Eliminate N+1 queries. Batch with
ANY(array[...]) or use JOINs instead of per-row queries.
-- BAD: 101 round trips select id from users where active = true; -- 100 IDs select * from orders where user_id = 1; -- repeated 100 times -- GOOD: 1 round trip select * from orders where user_id = any($1::bigint[]);
Use cursor-based pagination. OFFSET scans all skipped rows. Keyset pagination is O(1) regardless of page depth.
-- BAD: page 1000 scans 20,000 rows select * from products order by id limit 20 offset 19980; -- GOOD: page 1000, same speed as page 1 select * from products where id > $last_id order by id limit 20;
Monitoring & Diagnostics
Use EXPLAIN ANALYZE to diagnose slow queries:
explain (analyze, buffers, format text) select * from orders where customer_id = 123 and status = 'pending';
What to look for:
on large tables → missing indexSeq Scan
→ poor selectivityRows Removed by Filter
→ data not cachedBuffers: read >> hit
→ increaseSort Method: external mergework_mem
Monitor with pg_stat_statements for aggregate query performance across the system.
Quick Reference
| Problem | Solution |
|---|---|
| Slow filtered queries | Add index on WHERE columns |
| Slow JOINs | Index foreign key columns |
| JSONB/array queries slow | Use GIN index |
| Time-series table huge | Use BRIN index |
| Too many connections | Use connection pooling |
| Data leaks | Enable RLS with policies |
| RLS is slow | Wrap functions in |
| N+1 queries | Batch with |
| Deep pagination slow | Cursor/keyset pagination |
| Deadlocks | Consistent lock ordering |
| Schema bloat | Use correct data types |
| Index fragmentation | Use or UUIDv7 PKs |
NEVER Do
- NEVER skip connection pooling — Direct connections don't scale
- NEVER use
for money — Usefloat
for exact arithmeticnumeric - NEVER rely on app-only filtering for security — Use RLS
- NEVER use
for deep pagination — Use keyset/cursor paginationOFFSET - NEVER use random UUIDv4 as PK on large tables — Causes index fragmentation
- NEVER create foreign keys without indexes — Postgres doesn't auto-index them
References
Detailed rule files in
references/:
Query Performance (Critical):
— Index WHERE and JOIN columnsquery-missing-indexes.md
— B-tree vs GIN vs BRIN vs Hashquery-index-types.md
— Partial indexes for filtered queriesquery-partial-indexes.md
— Multi-column index designquery-composite-indexes.md
— Index-only scansquery-covering-indexes.md
Connection Management (Critical):
— Connection pooling with PgBouncerconn-pooling.md
— Setting connection limitsconn-limits.md
— Idle connection managementconn-idle-timeout.md
— Prepared statement poolingconn-prepared-statements.md
Security (Critical):
— Row-Level Security fundamentalssecurity-rls-basics.md
— Optimizing RLS policiessecurity-rls-performance.md
— Role and privilege managementsecurity-privileges.md
Schema Design (High):
— Choosing data typesschema-data-types.md
— Primary key strategiesschema-primary-keys.md
— FK index requirementsschema-foreign-key-indexes.md
— Naming conventionsschema-lowercase-identifiers.md
— Table partitioning strategiesschema-partitioning.md
Concurrency & Locking (Medium-High):
— Avoiding deadlockslock-deadlock-prevention.md
— Keeping transactions shortlock-short-transactions.md
— Queue processing patternlock-skip-locked.md
— Advisory lockslock-advisory.md
Data Access (Medium):
— Eliminating N+1 queriesdata-n-plus-one.md
— Cursor vs offset paginationdata-pagination.md
— Bulk insert patternsdata-batch-inserts.md
— Upsert patternsdata-upsert.md
Monitoring (Low-Medium):
— Query plan analysismonitor-explain-analyze.md
— Aggregate query statsmonitor-pg-stat-statements.md
— Table maintenancemonitor-vacuum-analyze.md
Advanced Features (Low):
— JSONB query optimizationadvanced-jsonb-indexing.md
— Full-text search setupadvanced-full-text-search.md
External: