Adeen commited on
Commit
940b278
·
1 Parent(s): db584ca

Clean up extra files and fix auth in edge functions

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .agents/skills/supabase-postgres-best-practices/SKILL.md +0 -64
  2. .agents/skills/supabase-postgres-best-practices/references/_contributing.md +0 -170
  3. .agents/skills/supabase-postgres-best-practices/references/_sections.md +0 -39
  4. .agents/skills/supabase-postgres-best-practices/references/_template.md +0 -34
  5. .agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +0 -55
  6. .agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +0 -49
  7. .agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +0 -46
  8. .agents/skills/supabase-postgres-best-practices/references/conn-limits.md +0 -44
  9. .agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +0 -41
  10. .agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +0 -46
  11. .agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +0 -54
  12. .agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +0 -53
  13. .agents/skills/supabase-postgres-best-practices/references/data-pagination.md +0 -50
  14. .agents/skills/supabase-postgres-best-practices/references/data-upsert.md +0 -50
  15. .agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +0 -56
  16. .agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +0 -68
  17. .agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +0 -50
  18. .agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +0 -54
  19. .agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +0 -45
  20. .agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +0 -55
  21. .agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +0 -55
  22. .agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +0 -44
  23. .agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +0 -40
  24. .agents/skills/supabase-postgres-best-practices/references/query-index-types.md +0 -48
  25. .agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +0 -43
  26. .agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +0 -45
  27. .agents/skills/supabase-postgres-best-practices/references/schema-constraints.md +0 -80
  28. .agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +0 -46
  29. .agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +0 -59
  30. .agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +0 -55
  31. .agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +0 -55
  32. .agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +0 -61
  33. .agents/skills/supabase-postgres-best-practices/references/security-privileges.md +0 -54
  34. .agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +0 -50
  35. .agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +0 -57
  36. .agents/skills/supabase/SKILL.md +0 -110
  37. .agents/skills/supabase/assets/feedback-issue-template.md +0 -17
  38. .agents/skills/supabase/references/skill-feedback.md +0 -17
  39. .claude/skills/supabase-postgres-best-practices/SKILL.md +0 -64
  40. .claude/skills/supabase-postgres-best-practices/references/_contributing.md +0 -170
  41. .claude/skills/supabase-postgres-best-practices/references/_sections.md +0 -39
  42. .claude/skills/supabase-postgres-best-practices/references/_template.md +0 -34
  43. .claude/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +0 -55
  44. .claude/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +0 -49
  45. .claude/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +0 -46
  46. .claude/skills/supabase-postgres-best-practices/references/conn-limits.md +0 -44
  47. .claude/skills/supabase-postgres-best-practices/references/conn-pooling.md +0 -41
  48. .claude/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +0 -46
  49. .claude/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +0 -54
  50. .claude/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +0 -53
.agents/skills/supabase-postgres-best-practices/SKILL.md DELETED
@@ -1,64 +0,0 @@
1
- ---
2
- name: supabase-postgres-best-practices
3
- description: Postgres performance optimization and best practices from Supabase. Use this skill when writing, reviewing, or optimizing Postgres queries, schema designs, or database configurations.
4
- license: MIT
5
- metadata:
6
- author: supabase
7
- version: "1.1.1"
8
- organization: Supabase
9
- date: January 2026
10
- abstract: Comprehensive Postgres performance optimization guide for developers using Supabase and Postgres. Contains performance rules across 8 categories, prioritized by impact from critical (query performance, connection management) to incremental (advanced features). Each rule includes detailed explanations, incorrect vs. correct SQL examples, query plan analysis, and specific performance metrics to guide automated optimization and code generation.
11
- ---
12
-
13
- # Supabase Postgres Best Practices
14
-
15
- Comprehensive performance optimization guide for Postgres, maintained by Supabase. Contains rules across 8 categories, prioritized by impact to guide automated query optimization and schema design.
16
-
17
- ## When to Apply
18
-
19
- Reference these guidelines when:
20
- - Writing SQL queries or designing schemas
21
- - Implementing indexes or query optimization
22
- - Reviewing database performance issues
23
- - Configuring connection pooling or scaling
24
- - Optimizing for Postgres-specific features
25
- - Working with Row-Level Security (RLS)
26
-
27
- ## Rule Categories by Priority
28
-
29
- | Priority | Category | Impact | Prefix |
30
- |----------|----------|--------|--------|
31
- | 1 | Query Performance | CRITICAL | `query-` |
32
- | 2 | Connection Management | CRITICAL | `conn-` |
33
- | 3 | Security & RLS | CRITICAL | `security-` |
34
- | 4 | Schema Design | HIGH | `schema-` |
35
- | 5 | Concurrency & Locking | MEDIUM-HIGH | `lock-` |
36
- | 6 | Data Access Patterns | MEDIUM | `data-` |
37
- | 7 | Monitoring & Diagnostics | LOW-MEDIUM | `monitor-` |
38
- | 8 | Advanced Features | LOW | `advanced-` |
39
-
40
- ## How to Use
41
-
42
- Read individual rule files for detailed explanations and SQL examples:
43
-
44
- ```
45
- references/query-missing-indexes.md
46
- references/query-partial-indexes.md
47
- references/_sections.md
48
- ```
49
-
50
- Each rule file contains:
51
- - Brief explanation of why it matters
52
- - Incorrect SQL example with explanation
53
- - Correct SQL example with explanation
54
- - Optional EXPLAIN output or metrics
55
- - Additional context and references
56
- - Supabase-specific notes (when applicable)
57
-
58
- ## References
59
-
60
- - https://www.postgresql.org/docs/current/
61
- - https://supabase.com/docs
62
- - https://wiki.postgresql.org/wiki/Performance_Optimization
63
- - https://supabase.com/docs/guides/database/overview
64
- - https://supabase.com/docs/guides/auth/row-level-security
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/_contributing.md DELETED
@@ -1,170 +0,0 @@
1
- # Writing Guidelines for Postgres References
2
-
3
- This document provides guidelines for creating effective Postgres best
4
- practice references that work well with AI agents and LLMs.
5
-
6
- ## Key Principles
7
-
8
- ### 1. Concrete Transformation Patterns
9
-
10
- Show exact SQL rewrites. Avoid philosophical advice.
11
-
12
- **Good:** "Use `WHERE id = ANY(ARRAY[...])` instead of
13
- `WHERE id IN (SELECT ...)`" **Bad:** "Design good schemas"
14
-
15
- ### 2. Error-First Structure
16
-
17
- Always show the problematic pattern first, then the solution. This trains agents
18
- to recognize anti-patterns.
19
-
20
- ```markdown
21
- **Incorrect (sequential queries):** [bad example]
22
-
23
- **Correct (batched query):** [good example]
24
- ```
25
-
26
- ### 3. Quantified Impact
27
-
28
- Include specific metrics. Helps agents prioritize fixes.
29
-
30
- **Good:** "10x faster queries", "50% smaller index", "Eliminates N+1"
31
- **Bad:** "Faster", "Better", "More efficient"
32
-
33
- ### 4. Self-Contained Examples
34
-
35
- Examples should be complete and runnable (or close to it). Include `CREATE TABLE`
36
- if context is needed.
37
-
38
- ```sql
39
- -- Include table definition when needed for clarity
40
- CREATE TABLE users (
41
- id bigint PRIMARY KEY,
42
- email text NOT NULL,
43
- deleted_at timestamptz
44
- );
45
-
46
- -- Now show the index
47
- CREATE INDEX users_active_email_idx ON users(email) WHERE deleted_at IS NULL;
48
- ```
49
-
50
- ### 5. Semantic Naming
51
-
52
- Use meaningful table/column names. Names carry intent for LLMs.
53
-
54
- **Good:** `users`, `email`, `created_at`, `is_active`
55
- **Bad:** `table1`, `col1`, `field`, `flag`
56
-
57
- ---
58
-
59
- ## Code Example Standards
60
-
61
- ### SQL Formatting
62
-
63
- ```sql
64
- -- Use lowercase keywords, clear formatting
65
- CREATE INDEX CONCURRENTLY users_email_idx
66
- ON users(email)
67
- WHERE deleted_at IS NULL;
68
-
69
- -- Not cramped or ALL CAPS
70
- CREATE INDEX CONCURRENTLY USERS_EMAIL_IDX ON USERS(EMAIL) WHERE DELETED_AT IS NULL;
71
- ```
72
-
73
- ### Comments
74
-
75
- - Explain _why_, not _what_
76
- - Highlight performance implications
77
- - Point out common pitfalls
78
-
79
- ### Language Tags
80
-
81
- - `sql` - Standard SQL queries
82
- - `plpgsql` - Stored procedures/functions
83
- - `typescript` - Application code (when needed)
84
- - `python` - Application code (when needed)
85
-
86
- ---
87
-
88
- ## When to Include Application Code
89
-
90
- **Default: SQL Only**
91
-
92
- Most references should focus on pure SQL patterns. This keeps examples portable.
93
-
94
- **Include Application Code When:**
95
-
96
- - Connection pooling configuration
97
- - Transaction management in application context
98
- - ORM anti-patterns (N+1 in Prisma/TypeORM)
99
- - Prepared statement usage
100
-
101
- **Format for Mixed Examples:**
102
-
103
- ````markdown
104
- **Incorrect (N+1 in application):**
105
-
106
- ```typescript
107
- for (const user of users) {
108
- const posts = await db.query("SELECT * FROM posts WHERE user_id = $1", [
109
- user.id,
110
- ]);
111
- }
112
- ```
113
- ````
114
-
115
- **Correct (batch query):**
116
-
117
- ```typescript
118
- const posts = await db.query("SELECT * FROM posts WHERE user_id = ANY($1)", [
119
- userIds,
120
- ]);
121
- ```
122
-
123
- ---
124
-
125
- ## Impact Level Guidelines
126
-
127
- | Level | Improvement | Use When |
128
- |-------|-------------|----------|
129
- | **CRITICAL** | 10-100x | Missing indexes, connection exhaustion, sequential scans on large tables |
130
- | **HIGH** | 5-20x | Wrong index types, poor partitioning, missing covering indexes |
131
- | **MEDIUM-HIGH** | 2-5x | N+1 queries, inefficient pagination, RLS optimization |
132
- | **MEDIUM** | 1.5-3x | Redundant indexes, query plan instability |
133
- | **LOW-MEDIUM** | 1.2-2x | VACUUM tuning, configuration tweaks |
134
- | **LOW** | Incremental | Advanced patterns, edge cases |
135
-
136
- ---
137
-
138
- ## Reference Standards
139
-
140
- **Primary Sources:**
141
-
142
- - Official Postgres documentation
143
- - Supabase documentation
144
- - Postgres wiki
145
- - Established blogs (2ndQuadrant, Crunchy Data)
146
-
147
- **Format:**
148
-
149
- ```markdown
150
- Reference:
151
- [Postgres Indexes](https://www.postgresql.org/docs/current/indexes.html)
152
- ```
153
-
154
- ---
155
-
156
- ## Review Checklist
157
-
158
- Before submitting a reference:
159
-
160
- - [ ] Title is clear and action-oriented
161
- - [ ] Impact level matches the performance gain
162
- - [ ] impactDescription includes quantification
163
- - [ ] Explanation is concise (1-2 sentences)
164
- - [ ] Has at least 1 **Incorrect** SQL example
165
- - [ ] Has at least 1 **Correct** SQL example
166
- - [ ] SQL uses semantic naming
167
- - [ ] Comments explain _why_, not _what_
168
- - [ ] Trade-offs mentioned if applicable
169
- - [ ] Reference links included
170
- - [ ] `pnpm test` passes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/_sections.md DELETED
@@ -1,39 +0,0 @@
1
- # Section Definitions
2
-
3
- This file defines the rule categories for Postgres best practices. Rules are automatically assigned to sections based on their filename prefix.
4
-
5
- Take the examples below as pure demonstrative. Replace each section with the actual rule categories for Postgres best practices.
6
-
7
- ---
8
-
9
- ## 1. Query Performance (query)
10
- **Impact:** CRITICAL
11
- **Description:** Slow queries, missing indexes, inefficient query plans. The most common source of Postgres performance issues.
12
-
13
- ## 2. Connection Management (conn)
14
- **Impact:** CRITICAL
15
- **Description:** Connection pooling, limits, and serverless strategies. Critical for applications with high concurrency or serverless deployments.
16
-
17
- ## 3. Security & RLS (security)
18
- **Impact:** CRITICAL
19
- **Description:** Row-Level Security policies, privilege management, and authentication patterns.
20
-
21
- ## 4. Schema Design (schema)
22
- **Impact:** HIGH
23
- **Description:** Table design, index strategies, partitioning, and data type selection. Foundation for long-term performance.
24
-
25
- ## 5. Concurrency & Locking (lock)
26
- **Impact:** MEDIUM-HIGH
27
- **Description:** Transaction management, isolation levels, deadlock prevention, and lock contention patterns.
28
-
29
- ## 6. Data Access Patterns (data)
30
- **Impact:** MEDIUM
31
- **Description:** N+1 query elimination, batch operations, cursor-based pagination, and efficient data fetching.
32
-
33
- ## 7. Monitoring & Diagnostics (monitor)
34
- **Impact:** LOW-MEDIUM
35
- **Description:** Using pg_stat_statements, EXPLAIN ANALYZE, metrics collection, and performance diagnostics.
36
-
37
- ## 8. Advanced Features (advanced)
38
- **Impact:** LOW
39
- **Description:** Full-text search, JSONB optimization, PostGIS, extensions, and advanced Postgres features.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/_template.md DELETED
@@ -1,34 +0,0 @@
1
- ---
2
- title: Clear, Action-Oriented Title (e.g., "Use Partial Indexes for Filtered Queries")
3
- impact: MEDIUM
4
- impactDescription: 5-20x query speedup for filtered queries
5
- tags: indexes, query-optimization, performance
6
- ---
7
-
8
- ## [Rule Title]
9
-
10
- [1-2 sentence explanation of the problem and why it matters. Focus on performance impact.]
11
-
12
- **Incorrect (describe the problem):**
13
-
14
- ```sql
15
- -- Comment explaining what makes this slow/problematic
16
- CREATE INDEX users_email_idx ON users(email);
17
-
18
- SELECT * FROM users WHERE email = 'user@example.com' AND deleted_at IS NULL;
19
- -- This scans deleted records unnecessarily
20
- ```
21
-
22
- **Correct (describe the solution):**
23
-
24
- ```sql
25
- -- Comment explaining why this is better
26
- CREATE INDEX users_active_email_idx ON users(email) WHERE deleted_at IS NULL;
27
-
28
- SELECT * FROM users WHERE email = 'user@example.com' AND deleted_at IS NULL;
29
- -- Only indexes active users, 10x smaller index, faster queries
30
- ```
31
-
32
- [Optional: Additional context, edge cases, or trade-offs]
33
-
34
- Reference: [Postgres Docs](https://www.postgresql.org/docs/current/)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md DELETED
@@ -1,55 +0,0 @@
1
- ---
2
- title: Use tsvector for Full-Text Search
3
- impact: MEDIUM
4
- impactDescription: 100x faster than LIKE, with ranking support
5
- tags: full-text-search, tsvector, gin, search
6
- ---
7
-
8
- ## Use tsvector for Full-Text Search
9
-
10
- LIKE with wildcards can't use indexes. Full-text search with tsvector is orders of magnitude faster.
11
-
12
- **Incorrect (LIKE pattern matching):**
13
-
14
- ```sql
15
- -- Cannot use index, scans all rows
16
- select * from articles where content like '%postgresql%';
17
-
18
- -- Case-insensitive makes it worse
19
- select * from articles where lower(content) like '%postgresql%';
20
- ```
21
-
22
- **Correct (full-text search with tsvector):**
23
-
24
- ```sql
25
- -- Add tsvector column and index
26
- alter table articles add column search_vector tsvector
27
- generated always as (to_tsvector('english', coalesce(title,'') || ' ' || coalesce(content,''))) stored;
28
-
29
- create index articles_search_idx on articles using gin (search_vector);
30
-
31
- -- Fast full-text search
32
- select * from articles
33
- where search_vector @@ to_tsquery('english', 'postgresql & performance');
34
-
35
- -- With ranking
36
- select *, ts_rank(search_vector, query) as rank
37
- from articles, to_tsquery('english', 'postgresql') query
38
- where search_vector @@ query
39
- order by rank desc;
40
- ```
41
-
42
- Search multiple terms:
43
-
44
- ```sql
45
- -- AND: both terms required
46
- to_tsquery('postgresql & performance')
47
-
48
- -- OR: either term
49
- to_tsquery('postgresql | mysql')
50
-
51
- -- Prefix matching
52
- to_tsquery('post:*')
53
- ```
54
-
55
- Reference: [Full Text Search](https://supabase.com/docs/guides/database/full-text-search)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md DELETED
@@ -1,49 +0,0 @@
1
- ---
2
- title: Index JSONB Columns for Efficient Querying
3
- impact: MEDIUM
4
- impactDescription: 10-100x faster JSONB queries with proper indexing
5
- tags: jsonb, gin, indexes, json
6
- ---
7
-
8
- ## Index JSONB Columns for Efficient Querying
9
-
10
- JSONB queries without indexes scan the entire table. Use GIN indexes for containment queries.
11
-
12
- **Incorrect (no index on JSONB):**
13
-
14
- ```sql
15
- create table products (
16
- id bigint primary key,
17
- attributes jsonb
18
- );
19
-
20
- -- Full table scan for every query
21
- select * from products where attributes @> '{"color": "red"}';
22
- select * from products where attributes->>'brand' = 'Nike';
23
- ```
24
-
25
- **Correct (GIN index for JSONB):**
26
-
27
- ```sql
28
- -- GIN index for containment operators (@>, ?, ?&, ?|)
29
- create index products_attrs_gin on products using gin (attributes);
30
-
31
- -- Now containment queries use the index
32
- select * from products where attributes @> '{"color": "red"}';
33
-
34
- -- For specific key lookups, use expression index
35
- create index products_brand_idx on products ((attributes->>'brand'));
36
- select * from products where attributes->>'brand' = 'Nike';
37
- ```
38
-
39
- Choose the right operator class:
40
-
41
- ```sql
42
- -- jsonb_ops (default): supports all operators, larger index
43
- create index idx1 on products using gin (attributes);
44
-
45
- -- jsonb_path_ops: only @> operator, but 2-3x smaller index
46
- create index idx2 on products using gin (attributes jsonb_path_ops);
47
- ```
48
-
49
- Reference: [JSONB Indexes](https://www.postgresql.org/docs/current/datatype-json.html#JSON-INDEXING)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md DELETED
@@ -1,46 +0,0 @@
1
- ---
2
- title: Configure Idle Connection Timeouts
3
- impact: HIGH
4
- impactDescription: Reclaim 30-50% of connection slots from idle clients
5
- tags: connections, timeout, idle, resource-management
6
- ---
7
-
8
- ## Configure Idle Connection Timeouts
9
-
10
- Idle connections waste resources. Configure timeouts to automatically reclaim them.
11
-
12
- **Incorrect (connections held indefinitely):**
13
-
14
- ```sql
15
- -- No timeout configured
16
- show idle_in_transaction_session_timeout; -- 0 (disabled)
17
-
18
- -- Connections stay open forever, even when idle
19
- select pid, state, state_change, query
20
- from pg_stat_activity
21
- where state = 'idle in transaction';
22
- -- Shows transactions idle for hours, holding locks
23
- ```
24
-
25
- **Correct (automatic cleanup of idle connections):**
26
-
27
- ```sql
28
- -- Terminate connections idle in transaction after 30 seconds
29
- alter system set idle_in_transaction_session_timeout = '30s';
30
-
31
- -- Terminate completely idle connections after 10 minutes
32
- alter system set idle_session_timeout = '10min';
33
-
34
- -- Reload configuration
35
- select pg_reload_conf();
36
- ```
37
-
38
- For pooled connections, configure at the pooler level:
39
-
40
- ```ini
41
- # pgbouncer.ini
42
- server_idle_timeout = 60
43
- client_idle_timeout = 300
44
- ```
45
-
46
- Reference: [Connection Timeouts](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-IDLE-IN-TRANSACTION-SESSION-TIMEOUT)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/conn-limits.md DELETED
@@ -1,44 +0,0 @@
1
- ---
2
- title: Set Appropriate Connection Limits
3
- impact: CRITICAL
4
- impactDescription: Prevent database crashes and memory exhaustion
5
- tags: connections, max-connections, limits, stability
6
- ---
7
-
8
- ## Set Appropriate Connection Limits
9
-
10
- Too many connections exhaust memory and degrade performance. Set limits based on available resources.
11
-
12
- **Incorrect (unlimited or excessive connections):**
13
-
14
- ```sql
15
- -- Default max_connections = 100, but often increased blindly
16
- show max_connections; -- 500 (way too high for 4GB RAM)
17
-
18
- -- Each connection uses 1-3MB RAM
19
- -- 500 connections * 2MB = 1GB just for connections!
20
- -- Out of memory errors under load
21
- ```
22
-
23
- **Correct (calculate based on resources):**
24
-
25
- ```sql
26
- -- Formula: max_connections = (RAM in MB / 5MB per connection) - reserved
27
- -- For 4GB RAM: (4096 / 5) - 10 = ~800 theoretical max
28
- -- But practically, 100-200 is better for query performance
29
-
30
- -- Recommended settings for 4GB RAM
31
- alter system set max_connections = 100;
32
-
33
- -- Also set work_mem appropriately
34
- -- work_mem * max_connections should not exceed 25% of RAM
35
- alter system set work_mem = '8MB'; -- 8MB * 100 = 800MB max
36
- ```
37
-
38
- Monitor connection usage:
39
-
40
- ```sql
41
- select count(*), state from pg_stat_activity group by state;
42
- ```
43
-
44
- Reference: [Database Connections](https://supabase.com/docs/guides/platform/performance#connection-management)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md DELETED
@@ -1,41 +0,0 @@
1
- ---
2
- title: Use Connection Pooling for All Applications
3
- impact: CRITICAL
4
- impactDescription: Handle 10-100x more concurrent users
5
- tags: connection-pooling, pgbouncer, performance, scalability
6
- ---
7
-
8
- ## Use Connection Pooling for All Applications
9
-
10
- Postgres connections are expensive (1-3MB RAM each). Without pooling, applications exhaust connections under load.
11
-
12
- **Incorrect (new connection per request):**
13
-
14
- ```sql
15
- -- Each request creates a new connection
16
- -- Application code: db.connect() per request
17
- -- Result: 500 concurrent users = 500 connections = crashed database
18
-
19
- -- Check current connections
20
- select count(*) from pg_stat_activity; -- 487 connections!
21
- ```
22
-
23
- **Correct (connection pooling):**
24
-
25
- ```sql
26
- -- Use a pooler like PgBouncer between app and database
27
- -- Application connects to pooler, pooler reuses a small pool to Postgres
28
-
29
- -- Configure pool_size based on: (CPU cores * 2) + spindle_count
30
- -- Example for 4 cores: pool_size = 10
31
-
32
- -- Result: 500 concurrent users share 10 actual connections
33
- select count(*) from pg_stat_activity; -- 10 connections
34
- ```
35
-
36
- Pool modes:
37
-
38
- - **Transaction mode**: connection returned after each transaction (best for most apps)
39
- - **Session mode**: connection held for entire session (needed for prepared statements, temp tables)
40
-
41
- Reference: [Connection Pooling](https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pooler)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md DELETED
@@ -1,46 +0,0 @@
1
- ---
2
- title: Use Prepared Statements Correctly with Pooling
3
- impact: HIGH
4
- impactDescription: Avoid prepared statement conflicts in pooled environments
5
- tags: prepared-statements, connection-pooling, transaction-mode
6
- ---
7
-
8
- ## Use Prepared Statements Correctly with Pooling
9
-
10
- Prepared statements are tied to individual database connections. In transaction-mode pooling, connections are shared, causing conflicts.
11
-
12
- **Incorrect (named prepared statements with transaction pooling):**
13
-
14
- ```sql
15
- -- Named prepared statement
16
- prepare get_user as select * from users where id = $1;
17
-
18
- -- In transaction mode pooling, next request may get different connection
19
- execute get_user(123);
20
- -- ERROR: prepared statement "get_user" does not exist
21
- ```
22
-
23
- **Correct (use unnamed statements or session mode):**
24
-
25
- ```sql
26
- -- Option 1: Use unnamed prepared statements (most ORMs do this automatically)
27
- -- The query is prepared and executed in a single protocol message
28
-
29
- -- Option 2: Deallocate after use in transaction mode
30
- prepare get_user as select * from users where id = $1;
31
- execute get_user(123);
32
- deallocate get_user;
33
-
34
- -- Option 3: Use session mode pooling (port 5432 vs 6543)
35
- -- Connection is held for entire session, prepared statements persist
36
- ```
37
-
38
- Check your driver settings:
39
-
40
- ```sql
41
- -- Many drivers use prepared statements by default
42
- -- Node.js pg: { prepare: false } to disable
43
- -- JDBC: prepareThreshold=0 to disable
44
- ```
45
-
46
- Reference: [Prepared Statements with Pooling](https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pool-modes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md DELETED
@@ -1,54 +0,0 @@
1
- ---
2
- title: Batch INSERT Statements for Bulk Data
3
- impact: MEDIUM
4
- impactDescription: 10-50x faster bulk inserts
5
- tags: batch, insert, bulk, performance, copy
6
- ---
7
-
8
- ## Batch INSERT Statements for Bulk Data
9
-
10
- Individual INSERT statements have high overhead. Batch multiple rows in single statements or use COPY.
11
-
12
- **Incorrect (individual inserts):**
13
-
14
- ```sql
15
- -- Each insert is a separate transaction and round trip
16
- insert into events (user_id, action) values (1, 'click');
17
- insert into events (user_id, action) values (1, 'view');
18
- insert into events (user_id, action) values (2, 'click');
19
- -- ... 1000 more individual inserts
20
-
21
- -- 1000 inserts = 1000 round trips = slow
22
- ```
23
-
24
- **Correct (batch insert):**
25
-
26
- ```sql
27
- -- Multiple rows in single statement
28
- insert into events (user_id, action) values
29
- (1, 'click'),
30
- (1, 'view'),
31
- (2, 'click'),
32
- -- ... up to ~1000 rows per batch
33
- (999, 'view');
34
-
35
- -- One round trip for 1000 rows
36
- ```
37
-
38
- For large imports, use COPY:
39
-
40
- ```sql
41
- -- COPY is fastest for bulk loading
42
- copy events (user_id, action, created_at)
43
- from '/path/to/data.csv'
44
- with (format csv, header true);
45
-
46
- -- Or from stdin in application
47
- copy events (user_id, action) from stdin with (format csv);
48
- 1,click
49
- 1,view
50
- 2,click
51
- \.
52
- ```
53
-
54
- Reference: [COPY](https://www.postgresql.org/docs/current/sql-copy.html)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md DELETED
@@ -1,53 +0,0 @@
1
- ---
2
- title: Eliminate N+1 Queries with Batch Loading
3
- impact: MEDIUM-HIGH
4
- impactDescription: 10-100x fewer database round trips
5
- tags: n-plus-one, batch, performance, queries
6
- ---
7
-
8
- ## Eliminate N+1 Queries with Batch Loading
9
-
10
- N+1 queries execute one query per item in a loop. Batch them into a single query using arrays or JOINs.
11
-
12
- **Incorrect (N+1 queries):**
13
-
14
- ```sql
15
- -- First query: get all users
16
- select id from users where active = true; -- Returns 100 IDs
17
-
18
- -- Then N queries, one per user
19
- select * from orders where user_id = 1;
20
- select * from orders where user_id = 2;
21
- select * from orders where user_id = 3;
22
- -- ... 97 more queries!
23
-
24
- -- Total: 101 round trips to database
25
- ```
26
-
27
- **Correct (single batch query):**
28
-
29
- ```sql
30
- -- Collect IDs and query once with ANY
31
- select * from orders where user_id = any(array[1, 2, 3, ...]);
32
-
33
- -- Or use JOIN instead of loop
34
- select u.id, u.name, o.*
35
- from users u
36
- left join orders o on o.user_id = u.id
37
- where u.active = true;
38
-
39
- -- Total: 1 round trip
40
- ```
41
-
42
- Application pattern:
43
-
44
- ```sql
45
- -- Instead of looping in application code:
46
- -- for user in users: db.query("SELECT * FROM orders WHERE user_id = $1", user.id)
47
-
48
- -- Pass array parameter:
49
- select * from orders where user_id = any($1::bigint[]);
50
- -- Application passes: [1, 2, 3, 4, 5, ...]
51
- ```
52
-
53
- Reference: [N+1 Query Problem](https://supabase.com/docs/guides/database/query-optimization)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/data-pagination.md DELETED
@@ -1,50 +0,0 @@
1
- ---
2
- title: Use Cursor-Based Pagination Instead of OFFSET
3
- impact: MEDIUM-HIGH
4
- impactDescription: Consistent O(1) performance regardless of page depth
5
- tags: pagination, cursor, keyset, offset, performance
6
- ---
7
-
8
- ## Use Cursor-Based Pagination Instead of OFFSET
9
-
10
- OFFSET-based pagination scans all skipped rows, getting slower on deeper pages. Cursor pagination is O(1).
11
-
12
- **Incorrect (OFFSET pagination):**
13
-
14
- ```sql
15
- -- Page 1: scans 20 rows
16
- select * from products order by id limit 20 offset 0;
17
-
18
- -- Page 100: scans 2000 rows to skip 1980
19
- select * from products order by id limit 20 offset 1980;
20
-
21
- -- Page 10000: scans 200,000 rows!
22
- select * from products order by id limit 20 offset 199980;
23
- ```
24
-
25
- **Correct (cursor/keyset pagination):**
26
-
27
- ```sql
28
- -- Page 1: get first 20
29
- select * from products order by id limit 20;
30
- -- Application stores last_id = 20
31
-
32
- -- Page 2: start after last ID
33
- select * from products where id > 20 order by id limit 20;
34
- -- Uses index, always fast regardless of page depth
35
-
36
- -- Page 10000: same speed as page 1
37
- select * from products where id > 199980 order by id limit 20;
38
- ```
39
-
40
- For multi-column sorting:
41
-
42
- ```sql
43
- -- Cursor must include all sort columns
44
- select * from products
45
- where (created_at, id) > ('2024-01-15 10:00:00', 12345)
46
- order by created_at, id
47
- limit 20;
48
- ```
49
-
50
- Reference: [Pagination](https://supabase.com/docs/guides/database/pagination)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/data-upsert.md DELETED
@@ -1,50 +0,0 @@
1
- ---
2
- title: Use UPSERT for Insert-or-Update Operations
3
- impact: MEDIUM
4
- impactDescription: Atomic operation, eliminates race conditions
5
- tags: upsert, on-conflict, insert, update
6
- ---
7
-
8
- ## Use UPSERT for Insert-or-Update Operations
9
-
10
- Using separate SELECT-then-INSERT/UPDATE creates race conditions. Use INSERT ... ON CONFLICT for atomic upserts.
11
-
12
- **Incorrect (check-then-insert race condition):**
13
-
14
- ```sql
15
- -- Race condition: two requests check simultaneously
16
- select * from settings where user_id = 123 and key = 'theme';
17
- -- Both find nothing
18
-
19
- -- Both try to insert
20
- insert into settings (user_id, key, value) values (123, 'theme', 'dark');
21
- -- One succeeds, one fails with duplicate key error!
22
- ```
23
-
24
- **Correct (atomic UPSERT):**
25
-
26
- ```sql
27
- -- Single atomic operation
28
- insert into settings (user_id, key, value)
29
- values (123, 'theme', 'dark')
30
- on conflict (user_id, key)
31
- do update set value = excluded.value, updated_at = now();
32
-
33
- -- Returns the inserted/updated row
34
- insert into settings (user_id, key, value)
35
- values (123, 'theme', 'dark')
36
- on conflict (user_id, key)
37
- do update set value = excluded.value
38
- returning *;
39
- ```
40
-
41
- Insert-or-ignore pattern:
42
-
43
- ```sql
44
- -- Insert only if not exists (no update)
45
- insert into page_views (page_id, user_id)
46
- values (1, 123)
47
- on conflict (page_id, user_id) do nothing;
48
- ```
49
-
50
- Reference: [INSERT ON CONFLICT](https://www.postgresql.org/docs/current/sql-insert.html#SQL-ON-CONFLICT)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md DELETED
@@ -1,56 +0,0 @@
1
- ---
2
- title: Use Advisory Locks for Application-Level Locking
3
- impact: MEDIUM
4
- impactDescription: Efficient coordination without row-level lock overhead
5
- tags: advisory-locks, coordination, application-locks
6
- ---
7
-
8
- ## Use Advisory Locks for Application-Level Locking
9
-
10
- Advisory locks provide application-level coordination without requiring database rows to lock.
11
-
12
- **Incorrect (creating rows just for locking):**
13
-
14
- ```sql
15
- -- Creating dummy rows to lock on
16
- create table resource_locks (
17
- resource_name text primary key
18
- );
19
-
20
- insert into resource_locks values ('report_generator');
21
-
22
- -- Lock by selecting the row
23
- select * from resource_locks where resource_name = 'report_generator' for update;
24
- ```
25
-
26
- **Correct (advisory locks):**
27
-
28
- ```sql
29
- -- Session-level advisory lock (released on disconnect or unlock)
30
- select pg_advisory_lock(hashtext('report_generator'));
31
- -- ... do exclusive work ...
32
- select pg_advisory_unlock(hashtext('report_generator'));
33
-
34
- -- Transaction-level lock (released on commit/rollback)
35
- begin;
36
- select pg_advisory_xact_lock(hashtext('daily_report'));
37
- -- ... do work ...
38
- commit; -- Lock automatically released
39
- ```
40
-
41
- Try-lock for non-blocking operations:
42
-
43
- ```sql
44
- -- Returns immediately with true/false instead of waiting
45
- select pg_try_advisory_lock(hashtext('resource_name'));
46
-
47
- -- Use in application
48
- if (acquired) {
49
- -- Do work
50
- select pg_advisory_unlock(hashtext('resource_name'));
51
- } else {
52
- -- Skip or retry later
53
- }
54
- ```
55
-
56
- Reference: [Advisory Locks](https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md DELETED
@@ -1,68 +0,0 @@
1
- ---
2
- title: Prevent Deadlocks with Consistent Lock Ordering
3
- impact: MEDIUM-HIGH
4
- impactDescription: Eliminate deadlock errors, improve reliability
5
- tags: deadlocks, locking, transactions, ordering
6
- ---
7
-
8
- ## Prevent Deadlocks with Consistent Lock Ordering
9
-
10
- Deadlocks occur when transactions lock resources in different orders. Always
11
- acquire locks in a consistent order.
12
-
13
- **Incorrect (inconsistent lock ordering):**
14
-
15
- ```sql
16
- -- Transaction A -- Transaction B
17
- begin; begin;
18
- update accounts update accounts
19
- set balance = balance - 100 set balance = balance - 50
20
- where id = 1; where id = 2; -- B locks row 2
21
-
22
- update accounts update accounts
23
- set balance = balance + 100 set balance = balance + 50
24
- where id = 2; -- A waits for B where id = 1; -- B waits for A
25
-
26
- -- DEADLOCK! Both waiting for each other
27
- ```
28
-
29
- **Correct (lock rows in consistent order first):**
30
-
31
- ```sql
32
- -- Explicitly acquire locks in ID order before updating
33
- begin;
34
- select * from accounts where id in (1, 2) order by id for update;
35
-
36
- -- Now perform updates in any order - locks already held
37
- update accounts set balance = balance - 100 where id = 1;
38
- update accounts set balance = balance + 100 where id = 2;
39
- commit;
40
- ```
41
-
42
- Alternative: use a single statement to update atomically:
43
-
44
- ```sql
45
- -- Single statement acquires all locks atomically
46
- begin;
47
- update accounts
48
- set balance = balance + case id
49
- when 1 then -100
50
- when 2 then 100
51
- end
52
- where id in (1, 2);
53
- commit;
54
- ```
55
-
56
- Detect deadlocks in logs:
57
-
58
- ```sql
59
- -- Check for recent deadlocks
60
- select * from pg_stat_database where deadlocks > 0;
61
-
62
- -- Enable deadlock logging
63
- set log_lock_waits = on;
64
- set deadlock_timeout = '1s';
65
- ```
66
-
67
- Reference:
68
- [Deadlocks](https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-DEADLOCKS)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md DELETED
@@ -1,50 +0,0 @@
1
- ---
2
- title: Keep Transactions Short to Reduce Lock Contention
3
- impact: MEDIUM-HIGH
4
- impactDescription: 3-5x throughput improvement, fewer deadlocks
5
- tags: transactions, locking, contention, performance
6
- ---
7
-
8
- ## Keep Transactions Short to Reduce Lock Contention
9
-
10
- Long-running transactions hold locks that block other queries. Keep transactions as short as possible.
11
-
12
- **Incorrect (long transaction with external calls):**
13
-
14
- ```sql
15
- begin;
16
- select * from orders where id = 1 for update; -- Lock acquired
17
-
18
- -- Application makes HTTP call to payment API (2-5 seconds)
19
- -- Other queries on this row are blocked!
20
-
21
- update orders set status = 'paid' where id = 1;
22
- commit; -- Lock held for entire duration
23
- ```
24
-
25
- **Correct (minimal transaction scope):**
26
-
27
- ```sql
28
- -- Validate data and call APIs outside transaction
29
- -- Application: response = await paymentAPI.charge(...)
30
-
31
- -- Only hold lock for the actual update
32
- begin;
33
- update orders
34
- set status = 'paid', payment_id = $1
35
- where id = $2 and status = 'pending'
36
- returning *;
37
- commit; -- Lock held for milliseconds
38
- ```
39
-
40
- Use `statement_timeout` to prevent runaway transactions:
41
-
42
- ```sql
43
- -- Abort queries running longer than 30 seconds
44
- set statement_timeout = '30s';
45
-
46
- -- Or per-session
47
- set local statement_timeout = '5s';
48
- ```
49
-
50
- Reference: [Transaction Management](https://www.postgresql.org/docs/current/tutorial-transactions.html)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md DELETED
@@ -1,54 +0,0 @@
1
- ---
2
- title: Use SKIP LOCKED for Non-Blocking Queue Processing
3
- impact: MEDIUM-HIGH
4
- impactDescription: 10x throughput for worker queues
5
- tags: skip-locked, queue, workers, concurrency
6
- ---
7
-
8
- ## Use SKIP LOCKED for Non-Blocking Queue Processing
9
-
10
- When multiple workers process a queue, SKIP LOCKED allows workers to process different rows without waiting.
11
-
12
- **Incorrect (workers block each other):**
13
-
14
- ```sql
15
- -- Worker 1 and Worker 2 both try to get next job
16
- begin;
17
- select * from jobs where status = 'pending' order by created_at limit 1 for update;
18
- -- Worker 2 waits for Worker 1's lock to release!
19
- ```
20
-
21
- **Correct (SKIP LOCKED for parallel processing):**
22
-
23
- ```sql
24
- -- Each worker skips locked rows and gets the next available
25
- begin;
26
- select * from jobs
27
- where status = 'pending'
28
- order by created_at
29
- limit 1
30
- for update skip locked;
31
-
32
- -- Worker 1 gets job 1, Worker 2 gets job 2 (no waiting)
33
-
34
- update jobs set status = 'processing' where id = $1;
35
- commit;
36
- ```
37
-
38
- Complete queue pattern:
39
-
40
- ```sql
41
- -- Atomic claim-and-update in one statement
42
- update jobs
43
- set status = 'processing', worker_id = $1, started_at = now()
44
- where id = (
45
- select id from jobs
46
- where status = 'pending'
47
- order by created_at
48
- limit 1
49
- for update skip locked
50
- )
51
- returning *;
52
- ```
53
-
54
- Reference: [SELECT FOR UPDATE SKIP LOCKED](https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md DELETED
@@ -1,45 +0,0 @@
1
- ---
2
- title: Use EXPLAIN ANALYZE to Diagnose Slow Queries
3
- impact: LOW-MEDIUM
4
- impactDescription: Identify exact bottlenecks in query execution
5
- tags: explain, analyze, diagnostics, query-plan
6
- ---
7
-
8
- ## Use EXPLAIN ANALYZE to Diagnose Slow Queries
9
-
10
- EXPLAIN ANALYZE executes the query and shows actual timings, revealing the true performance bottlenecks.
11
-
12
- **Incorrect (guessing at performance issues):**
13
-
14
- ```sql
15
- -- Query is slow, but why?
16
- select * from orders where customer_id = 123 and status = 'pending';
17
- -- "It must be missing an index" - but which one?
18
- ```
19
-
20
- **Correct (use EXPLAIN ANALYZE):**
21
-
22
- ```sql
23
- explain (analyze, buffers, format text)
24
- select * from orders where customer_id = 123 and status = 'pending';
25
-
26
- -- Output reveals the issue:
27
- -- Seq Scan on orders (cost=0.00..25000.00 rows=50 width=100) (actual time=0.015..450.123 rows=50 loops=1)
28
- -- Filter: ((customer_id = 123) AND (status = 'pending'::text))
29
- -- Rows Removed by Filter: 999950
30
- -- Buffers: shared hit=5000 read=15000
31
- -- Planning Time: 0.150 ms
32
- -- Execution Time: 450.500 ms
33
- ```
34
-
35
- Key things to look for:
36
-
37
- ```sql
38
- -- Seq Scan on large tables = missing index
39
- -- Rows Removed by Filter = poor selectivity or missing index
40
- -- Buffers: read >> hit = data not cached, needs more memory
41
- -- Nested Loop with high loops = consider different join strategy
42
- -- Sort Method: external merge = work_mem too low
43
- ```
44
-
45
- Reference: [EXPLAIN](https://supabase.com/docs/guides/database/inspect)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md DELETED
@@ -1,55 +0,0 @@
1
- ---
2
- title: Enable pg_stat_statements for Query Analysis
3
- impact: LOW-MEDIUM
4
- impactDescription: Identify top resource-consuming queries
5
- tags: pg-stat-statements, monitoring, statistics, performance
6
- ---
7
-
8
- ## Enable pg_stat_statements for Query Analysis
9
-
10
- pg_stat_statements tracks execution statistics for all queries, helping identify slow and frequent queries.
11
-
12
- **Incorrect (no visibility into query patterns):**
13
-
14
- ```sql
15
- -- Database is slow, but which queries are the problem?
16
- -- No way to know without pg_stat_statements
17
- ```
18
-
19
- **Correct (enable and query pg_stat_statements):**
20
-
21
- ```sql
22
- -- Enable the extension
23
- create extension if not exists pg_stat_statements;
24
-
25
- -- Find slowest queries by total time
26
- select
27
- calls,
28
- round(total_exec_time::numeric, 2) as total_time_ms,
29
- round(mean_exec_time::numeric, 2) as mean_time_ms,
30
- query
31
- from pg_stat_statements
32
- order by total_exec_time desc
33
- limit 10;
34
-
35
- -- Find most frequent queries
36
- select calls, query
37
- from pg_stat_statements
38
- order by calls desc
39
- limit 10;
40
-
41
- -- Reset statistics after optimization
42
- select pg_stat_statements_reset();
43
- ```
44
-
45
- Key metrics to monitor:
46
-
47
- ```sql
48
- -- Queries with high mean time (candidates for optimization)
49
- select query, mean_exec_time, calls
50
- from pg_stat_statements
51
- where mean_exec_time > 100 -- > 100ms average
52
- order by mean_exec_time desc;
53
- ```
54
-
55
- Reference: [pg_stat_statements](https://supabase.com/docs/guides/database/extensions/pg_stat_statements)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md DELETED
@@ -1,55 +0,0 @@
1
- ---
2
- title: Maintain Table Statistics with VACUUM and ANALYZE
3
- impact: MEDIUM
4
- impactDescription: 2-10x better query plans with accurate statistics
5
- tags: vacuum, analyze, statistics, maintenance, autovacuum
6
- ---
7
-
8
- ## Maintain Table Statistics with VACUUM and ANALYZE
9
-
10
- Outdated statistics cause the query planner to make poor decisions. VACUUM reclaims space, ANALYZE updates statistics.
11
-
12
- **Incorrect (stale statistics):**
13
-
14
- ```sql
15
- -- Table has 1M rows but stats say 1000
16
- -- Query planner chooses wrong strategy
17
- explain select * from orders where status = 'pending';
18
- -- Shows: Seq Scan (because stats show small table)
19
- -- Actually: Index Scan would be much faster
20
- ```
21
-
22
- **Correct (maintain fresh statistics):**
23
-
24
- ```sql
25
- -- Manually analyze after large data changes
26
- analyze orders;
27
-
28
- -- Analyze specific columns used in WHERE clauses
29
- analyze orders (status, created_at);
30
-
31
- -- Check when tables were last analyzed
32
- select
33
- relname,
34
- last_vacuum,
35
- last_autovacuum,
36
- last_analyze,
37
- last_autoanalyze
38
- from pg_stat_user_tables
39
- order by last_analyze nulls first;
40
- ```
41
-
42
- Autovacuum tuning for busy tables:
43
-
44
- ```sql
45
- -- Increase frequency for high-churn tables
46
- alter table orders set (
47
- autovacuum_vacuum_scale_factor = 0.05, -- Vacuum at 5% dead tuples (default 20%)
48
- autovacuum_analyze_scale_factor = 0.02 -- Analyze at 2% changes (default 10%)
49
- );
50
-
51
- -- Check autovacuum status
52
- select * from pg_stat_progress_vacuum;
53
- ```
54
-
55
- Reference: [VACUUM](https://supabase.com/docs/guides/database/database-size#vacuum-operations)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md DELETED
@@ -1,44 +0,0 @@
1
- ---
2
- title: Create Composite Indexes for Multi-Column Queries
3
- impact: HIGH
4
- impactDescription: 5-10x faster multi-column queries
5
- tags: indexes, composite-index, multi-column, query-optimization
6
- ---
7
-
8
- ## Create Composite Indexes for Multi-Column Queries
9
-
10
- When queries filter on multiple columns, a composite index is more efficient than separate single-column indexes.
11
-
12
- **Incorrect (separate indexes require bitmap scan):**
13
-
14
- ```sql
15
- -- Two separate indexes
16
- create index orders_status_idx on orders (status);
17
- create index orders_created_idx on orders (created_at);
18
-
19
- -- Query must combine both indexes (slower)
20
- select * from orders where status = 'pending' and created_at > '2024-01-01';
21
- ```
22
-
23
- **Correct (composite index):**
24
-
25
- ```sql
26
- -- Single composite index (leftmost column first for equality checks)
27
- create index orders_status_created_idx on orders (status, created_at);
28
-
29
- -- Query uses one efficient index scan
30
- select * from orders where status = 'pending' and created_at > '2024-01-01';
31
- ```
32
-
33
- **Column order matters** - place equality columns first, range columns last:
34
-
35
- ```sql
36
- -- Good: status (=) before created_at (>)
37
- create index idx on orders (status, created_at);
38
-
39
- -- Works for: WHERE status = 'pending'
40
- -- Works for: WHERE status = 'pending' AND created_at > '2024-01-01'
41
- -- Does NOT work for: WHERE created_at > '2024-01-01' (leftmost prefix rule)
42
- ```
43
-
44
- Reference: [Multicolumn Indexes](https://www.postgresql.org/docs/current/indexes-multicolumn.html)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md DELETED
@@ -1,40 +0,0 @@
1
- ---
2
- title: Use Covering Indexes to Avoid Table Lookups
3
- impact: MEDIUM-HIGH
4
- impactDescription: 2-5x faster queries by eliminating heap fetches
5
- tags: indexes, covering-index, include, index-only-scan
6
- ---
7
-
8
- ## Use Covering Indexes to Avoid Table Lookups
9
-
10
- Covering indexes include all columns needed by a query, enabling index-only scans that skip the table entirely.
11
-
12
- **Incorrect (index scan + heap fetch):**
13
-
14
- ```sql
15
- create index users_email_idx on users (email);
16
-
17
- -- Must fetch name and created_at from table heap
18
- select email, name, created_at from users where email = 'user@example.com';
19
- ```
20
-
21
- **Correct (index-only scan with INCLUDE):**
22
-
23
- ```sql
24
- -- Include non-searchable columns in the index
25
- create index users_email_idx on users (email) include (name, created_at);
26
-
27
- -- All columns served from index, no table access needed
28
- select email, name, created_at from users where email = 'user@example.com';
29
- ```
30
-
31
- Use INCLUDE for columns you SELECT but don't filter on:
32
-
33
- ```sql
34
- -- Searching by status, but also need customer_id and total
35
- create index orders_status_idx on orders (status) include (customer_id, total);
36
-
37
- select status, customer_id, total from orders where status = 'shipped';
38
- ```
39
-
40
- Reference: [Index-Only Scans](https://www.postgresql.org/docs/current/indexes-index-only-scans.html)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/query-index-types.md DELETED
@@ -1,48 +0,0 @@
1
- ---
2
- title: Choose the Right Index Type for Your Data
3
- impact: HIGH
4
- impactDescription: 10-100x improvement with correct index type
5
- tags: indexes, btree, gin, gist, brin, hash, index-types
6
- ---
7
-
8
- ## Choose the Right Index Type for Your Data
9
-
10
- Different index types excel at different query patterns. The default B-tree isn't always optimal.
11
-
12
- **Incorrect (B-tree for JSONB containment):**
13
-
14
- ```sql
15
- -- B-tree cannot optimize containment operators
16
- create index products_attrs_idx on products (attributes);
17
- select * from products where attributes @> '{"color": "red"}';
18
- -- Full table scan - B-tree doesn't support @> operator
19
- ```
20
-
21
- **Correct (GIN for JSONB):**
22
-
23
- ```sql
24
- -- GIN supports @>, ?, ?&, ?| operators
25
- create index products_attrs_idx on products using gin (attributes);
26
- select * from products where attributes @> '{"color": "red"}';
27
- ```
28
-
29
- Index type guide:
30
-
31
- ```sql
32
- -- B-tree (default): =, <, >, BETWEEN, IN, IS NULL
33
- create index users_created_idx on users (created_at);
34
-
35
- -- GIN: arrays, JSONB, full-text search
36
- create index posts_tags_idx on posts using gin (tags);
37
-
38
- -- GiST: geometric data, range types, nearest-neighbor (KNN) queries
39
- create index locations_idx on places using gist (location);
40
-
41
- -- BRIN: large time-series tables (10-100x smaller)
42
- create index events_time_idx on events using brin (created_at);
43
-
44
- -- Hash: equality-only (slightly faster than B-tree for =)
45
- create index sessions_token_idx on sessions using hash (token);
46
- ```
47
-
48
- Reference: [Index Types](https://www.postgresql.org/docs/current/indexes-types.html)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md DELETED
@@ -1,43 +0,0 @@
1
- ---
2
- title: Add Indexes on WHERE and JOIN Columns
3
- impact: CRITICAL
4
- impactDescription: 100-1000x faster queries on large tables
5
- tags: indexes, performance, sequential-scan, query-optimization
6
- ---
7
-
8
- ## Add Indexes on WHERE and JOIN Columns
9
-
10
- Queries filtering or joining on unindexed columns cause full table scans, which become exponentially slower as tables grow.
11
-
12
- **Incorrect (sequential scan on large table):**
13
-
14
- ```sql
15
- -- No index on customer_id causes full table scan
16
- select * from orders where customer_id = 123;
17
-
18
- -- EXPLAIN shows: Seq Scan on orders (cost=0.00..25000.00 rows=100 width=85)
19
- ```
20
-
21
- **Correct (index scan):**
22
-
23
- ```sql
24
- -- Create index on frequently filtered column
25
- create index orders_customer_id_idx on orders (customer_id);
26
-
27
- select * from orders where customer_id = 123;
28
-
29
- -- EXPLAIN shows: Index Scan using orders_customer_id_idx (cost=0.42..8.44 rows=100 width=85)
30
- ```
31
-
32
- For JOIN columns, always index the foreign key side:
33
-
34
- ```sql
35
- -- Index the referencing column
36
- create index orders_customer_id_idx on orders (customer_id);
37
-
38
- select c.name, o.total
39
- from customers c
40
- join orders o on o.customer_id = c.id;
41
- ```
42
-
43
- Reference: [Query Optimization](https://supabase.com/docs/guides/database/query-optimization)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md DELETED
@@ -1,45 +0,0 @@
1
- ---
2
- title: Use Partial Indexes for Filtered Queries
3
- impact: HIGH
4
- impactDescription: 5-20x smaller indexes, faster writes and queries
5
- tags: indexes, partial-index, query-optimization, storage
6
- ---
7
-
8
- ## Use Partial Indexes for Filtered Queries
9
-
10
- Partial indexes only include rows matching a WHERE condition, making them smaller and faster when queries consistently filter on the same condition.
11
-
12
- **Incorrect (full index includes irrelevant rows):**
13
-
14
- ```sql
15
- -- Index includes all rows, even soft-deleted ones
16
- create index users_email_idx on users (email);
17
-
18
- -- Query always filters active users
19
- select * from users where email = 'user@example.com' and deleted_at is null;
20
- ```
21
-
22
- **Correct (partial index matches query filter):**
23
-
24
- ```sql
25
- -- Index only includes active users
26
- create index users_active_email_idx on users (email)
27
- where deleted_at is null;
28
-
29
- -- Query uses the smaller, faster index
30
- select * from users where email = 'user@example.com' and deleted_at is null;
31
- ```
32
-
33
- Common use cases for partial indexes:
34
-
35
- ```sql
36
- -- Only pending orders (status rarely changes once completed)
37
- create index orders_pending_idx on orders (created_at)
38
- where status = 'pending';
39
-
40
- -- Only non-null values
41
- create index products_sku_idx on products (sku)
42
- where sku is not null;
43
- ```
44
-
45
- Reference: [Partial Indexes](https://www.postgresql.org/docs/current/indexes-partial.html)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/schema-constraints.md DELETED
@@ -1,80 +0,0 @@
1
- ---
2
- title: Add Constraints Safely in Migrations
3
- impact: HIGH
4
- impactDescription: Prevents migration failures and enables idempotent schema changes
5
- tags: constraints, migrations, schema, alter-table
6
- ---
7
-
8
- ## Add Constraints Safely in Migrations
9
-
10
- PostgreSQL does not support `ADD CONSTRAINT IF NOT EXISTS`. Migrations using this syntax will fail.
11
-
12
- **Incorrect (causes syntax error):**
13
-
14
- ```sql
15
- -- ERROR: syntax error at or near "not" (SQLSTATE 42601)
16
- alter table public.profiles
17
- add constraint if not exists profiles_birthchart_id_unique unique (birthchart_id);
18
- ```
19
-
20
- **Correct (idempotent constraint creation):**
21
-
22
- ```sql
23
- -- Use DO block to check before adding
24
- do $$
25
- begin
26
- if not exists (
27
- select 1 from pg_constraint
28
- where conname = 'profiles_birthchart_id_unique'
29
- and conrelid = 'public.profiles'::regclass
30
- ) then
31
- alter table public.profiles
32
- add constraint profiles_birthchart_id_unique unique (birthchart_id);
33
- end if;
34
- end $$;
35
- ```
36
-
37
- For all constraint types:
38
-
39
- ```sql
40
- -- Check constraints
41
- do $$
42
- begin
43
- if not exists (
44
- select 1 from pg_constraint
45
- where conname = 'check_age_positive'
46
- ) then
47
- alter table users add constraint check_age_positive check (age > 0);
48
- end if;
49
- end $$;
50
-
51
- -- Foreign keys
52
- do $$
53
- begin
54
- if not exists (
55
- select 1 from pg_constraint
56
- where conname = 'profiles_birthchart_id_fkey'
57
- ) then
58
- alter table profiles
59
- add constraint profiles_birthchart_id_fkey
60
- foreign key (birthchart_id) references birthcharts(id);
61
- end if;
62
- end $$;
63
- ```
64
-
65
- Check if constraint exists:
66
-
67
- ```sql
68
- -- Query to check constraint existence
69
- select conname, contype, pg_get_constraintdef(oid)
70
- from pg_constraint
71
- where conrelid = 'public.profiles'::regclass;
72
-
73
- -- contype values:
74
- -- 'p' = PRIMARY KEY
75
- -- 'f' = FOREIGN KEY
76
- -- 'u' = UNIQUE
77
- -- 'c' = CHECK
78
- ```
79
-
80
- Reference: [Constraints](https://www.postgresql.org/docs/current/ddl-constraints.html)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md DELETED
@@ -1,46 +0,0 @@
1
- ---
2
- title: Choose Appropriate Data Types
3
- impact: HIGH
4
- impactDescription: 50% storage reduction, faster comparisons
5
- tags: data-types, schema, storage, performance
6
- ---
7
-
8
- ## Choose Appropriate Data Types
9
-
10
- Using the right data types reduces storage, improves query performance, and prevents bugs.
11
-
12
- **Incorrect (wrong data types):**
13
-
14
- ```sql
15
- create table users (
16
- id int, -- Will overflow at 2.1 billion
17
- email varchar(255), -- Unnecessary length limit
18
- created_at timestamp, -- Missing timezone info
19
- is_active varchar(5), -- String for boolean
20
- price varchar(20) -- String for numeric
21
- );
22
- ```
23
-
24
- **Correct (appropriate data types):**
25
-
26
- ```sql
27
- create table users (
28
- id bigint generated always as identity primary key, -- 9 quintillion max
29
- email text, -- No artificial limit, same performance as varchar
30
- created_at timestamptz, -- Always store timezone-aware timestamps
31
- is_active boolean default true, -- 1 byte vs variable string length
32
- price numeric(10,2) -- Exact decimal arithmetic
33
- );
34
- ```
35
-
36
- Key guidelines:
37
-
38
- ```sql
39
- -- IDs: use bigint, not int (future-proofing)
40
- -- Strings: use text, not varchar(n) unless constraint needed
41
- -- Time: use timestamptz, not timestamp
42
- -- Money: use numeric, not float (precision matters)
43
- -- Enums: use text with check constraint or create enum type
44
- ```
45
-
46
- Reference: [Data Types](https://www.postgresql.org/docs/current/datatype.html)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md DELETED
@@ -1,59 +0,0 @@
1
- ---
2
- title: Index Foreign Key Columns
3
- impact: HIGH
4
- impactDescription: 10-100x faster JOINs and CASCADE operations
5
- tags: foreign-key, indexes, joins, schema
6
- ---
7
-
8
- ## Index Foreign Key Columns
9
-
10
- Postgres does not automatically index foreign key columns. Missing indexes cause slow JOINs and CASCADE operations.
11
-
12
- **Incorrect (unindexed foreign key):**
13
-
14
- ```sql
15
- create table orders (
16
- id bigint generated always as identity primary key,
17
- customer_id bigint references customers(id) on delete cascade,
18
- total numeric(10,2)
19
- );
20
-
21
- -- No index on customer_id!
22
- -- JOINs and ON DELETE CASCADE both require full table scan
23
- select * from orders where customer_id = 123; -- Seq Scan
24
- delete from customers where id = 123; -- Locks table, scans all orders
25
- ```
26
-
27
- **Correct (indexed foreign key):**
28
-
29
- ```sql
30
- create table orders (
31
- id bigint generated always as identity primary key,
32
- customer_id bigint references customers(id) on delete cascade,
33
- total numeric(10,2)
34
- );
35
-
36
- -- Always index the FK column
37
- create index orders_customer_id_idx on orders (customer_id);
38
-
39
- -- Now JOINs and cascades are fast
40
- select * from orders where customer_id = 123; -- Index Scan
41
- delete from customers where id = 123; -- Uses index, fast cascade
42
- ```
43
-
44
- Find missing FK indexes:
45
-
46
- ```sql
47
- select
48
- conrelid::regclass as table_name,
49
- a.attname as fk_column
50
- from pg_constraint c
51
- join pg_attribute a on a.attrelid = c.conrelid and a.attnum = any(c.conkey)
52
- where c.contype = 'f'
53
- and not exists (
54
- select 1 from pg_index i
55
- where i.indrelid = c.conrelid and a.attnum = any(i.indkey)
56
- );
57
- ```
58
-
59
- Reference: [Foreign Keys](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-FK)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md DELETED
@@ -1,55 +0,0 @@
1
- ---
2
- title: Use Lowercase Identifiers for Compatibility
3
- impact: MEDIUM
4
- impactDescription: Avoid case-sensitivity bugs with tools, ORMs, and AI assistants
5
- tags: naming, identifiers, case-sensitivity, schema, conventions
6
- ---
7
-
8
- ## Use Lowercase Identifiers for Compatibility
9
-
10
- PostgreSQL folds unquoted identifiers to lowercase. Quoted mixed-case identifiers require quotes forever and cause issues with tools, ORMs, and AI assistants that may not recognize them.
11
-
12
- **Incorrect (mixed-case identifiers):**
13
-
14
- ```sql
15
- -- Quoted identifiers preserve case but require quotes everywhere
16
- CREATE TABLE "Users" (
17
- "userId" bigint PRIMARY KEY,
18
- "firstName" text,
19
- "lastName" text
20
- );
21
-
22
- -- Must always quote or queries fail
23
- SELECT "firstName" FROM "Users" WHERE "userId" = 1;
24
-
25
- -- This fails - Users becomes users without quotes
26
- SELECT firstName FROM Users;
27
- -- ERROR: relation "users" does not exist
28
- ```
29
-
30
- **Correct (lowercase snake_case):**
31
-
32
- ```sql
33
- -- Unquoted lowercase identifiers are portable and tool-friendly
34
- CREATE TABLE users (
35
- user_id bigint PRIMARY KEY,
36
- first_name text,
37
- last_name text
38
- );
39
-
40
- -- Works without quotes, recognized by all tools
41
- SELECT first_name FROM users WHERE user_id = 1;
42
- ```
43
-
44
- Common sources of mixed-case identifiers:
45
-
46
- ```sql
47
- -- ORMs often generate quoted camelCase - configure them to use snake_case
48
- -- Migrations from other databases may preserve original casing
49
- -- Some GUI tools quote identifiers by default - disable this
50
-
51
- -- If stuck with mixed-case, create views as a compatibility layer
52
- CREATE VIEW users AS SELECT "userId" AS user_id, "firstName" AS first_name FROM "Users";
53
- ```
54
-
55
- Reference: [Identifiers and Key Words](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md DELETED
@@ -1,55 +0,0 @@
1
- ---
2
- title: Partition Large Tables for Better Performance
3
- impact: MEDIUM-HIGH
4
- impactDescription: 5-20x faster queries and maintenance on large tables
5
- tags: partitioning, large-tables, time-series, performance
6
- ---
7
-
8
- ## Partition Large Tables for Better Performance
9
-
10
- Partitioning splits a large table into smaller pieces, improving query performance and maintenance operations.
11
-
12
- **Incorrect (single large table):**
13
-
14
- ```sql
15
- create table events (
16
- id bigint generated always as identity,
17
- created_at timestamptz,
18
- data jsonb
19
- );
20
-
21
- -- 500M rows, queries scan everything
22
- select * from events where created_at > '2024-01-01'; -- Slow
23
- vacuum events; -- Takes hours, locks table
24
- ```
25
-
26
- **Correct (partitioned by time range):**
27
-
28
- ```sql
29
- create table events (
30
- id bigint generated always as identity,
31
- created_at timestamptz not null,
32
- data jsonb
33
- ) partition by range (created_at);
34
-
35
- -- Create partitions for each month
36
- create table events_2024_01 partition of events
37
- for values from ('2024-01-01') to ('2024-02-01');
38
-
39
- create table events_2024_02 partition of events
40
- for values from ('2024-02-01') to ('2024-03-01');
41
-
42
- -- Queries only scan relevant partitions
43
- select * from events where created_at > '2024-01-15'; -- Only scans events_2024_01+
44
-
45
- -- Drop old data instantly
46
- drop table events_2023_01; -- Instant vs DELETE taking hours
47
- ```
48
-
49
- When to partition:
50
-
51
- - Tables > 100M rows
52
- - Time-series data with date-based queries
53
- - Need to efficiently drop old data
54
-
55
- Reference: [Table Partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md DELETED
@@ -1,61 +0,0 @@
1
- ---
2
- title: Select Optimal Primary Key Strategy
3
- impact: HIGH
4
- impactDescription: Better index locality, reduced fragmentation
5
- tags: primary-key, identity, uuid, serial, schema
6
- ---
7
-
8
- ## Select Optimal Primary Key Strategy
9
-
10
- Primary key choice affects insert performance, index size, and replication
11
- efficiency.
12
-
13
- **Incorrect (problematic PK choices):**
14
-
15
- ```sql
16
- -- identity is the SQL-standard approach
17
- create table users (
18
- id serial primary key -- Works, but IDENTITY is recommended
19
- );
20
-
21
- -- Random UUIDs (v4) cause index fragmentation
22
- create table orders (
23
- id uuid default gen_random_uuid() primary key -- UUIDv4 = random = scattered inserts
24
- );
25
- ```
26
-
27
- **Correct (optimal PK strategies):**
28
-
29
- ```sql
30
- -- Use IDENTITY for sequential IDs (SQL-standard, best for most cases)
31
- create table users (
32
- id bigint generated always as identity primary key
33
- );
34
-
35
- -- For distributed systems needing UUIDs, use UUIDv7 (time-ordered)
36
- -- Requires pg_uuidv7 extension: create extension pg_uuidv7;
37
- create table orders (
38
- id uuid default uuid_generate_v7() primary key -- Time-ordered, no fragmentation
39
- );
40
-
41
- -- Alternative: time-prefixed IDs for sortable, distributed IDs (no extension needed)
42
- create table events (
43
- id text default concat(
44
- to_char(now() at time zone 'utc', 'YYYYMMDDHH24MISSMS'),
45
- gen_random_uuid()::text
46
- ) primary key
47
- );
48
- ```
49
-
50
- Guidelines:
51
-
52
- - Single database: `bigint identity` (sequential, 8 bytes, SQL-standard)
53
- - Distributed/exposed IDs: UUIDv7 (requires pg_uuidv7) or ULID (time-ordered, no
54
- fragmentation)
55
- - `serial` works but `identity` is SQL-standard and preferred for new
56
- applications
57
- - Avoid random UUIDs (v4) as primary keys on large tables (causes index
58
- fragmentation)
59
-
60
- Reference:
61
- [Identity Columns](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-PARMS-GENERATED-IDENTITY)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/security-privileges.md DELETED
@@ -1,54 +0,0 @@
1
- ---
2
- title: Apply Principle of Least Privilege
3
- impact: MEDIUM
4
- impactDescription: Reduced attack surface, better audit trail
5
- tags: privileges, security, roles, permissions
6
- ---
7
-
8
- ## Apply Principle of Least Privilege
9
-
10
- Grant only the minimum permissions required. Never use superuser for application queries.
11
-
12
- **Incorrect (overly broad permissions):**
13
-
14
- ```sql
15
- -- Application uses superuser connection
16
- -- Or grants ALL to application role
17
- grant all privileges on all tables in schema public to app_user;
18
- grant all privileges on all sequences in schema public to app_user;
19
-
20
- -- Any SQL injection becomes catastrophic
21
- -- drop table users; cascades to everything
22
- ```
23
-
24
- **Correct (minimal, specific grants):**
25
-
26
- ```sql
27
- -- Create role with no default privileges
28
- create role app_readonly nologin;
29
-
30
- -- Grant only SELECT on specific tables
31
- grant usage on schema public to app_readonly;
32
- grant select on public.products, public.categories to app_readonly;
33
-
34
- -- Create role for writes with limited scope
35
- create role app_writer nologin;
36
- grant usage on schema public to app_writer;
37
- grant select, insert, update on public.orders to app_writer;
38
- grant usage on sequence orders_id_seq to app_writer;
39
- -- No DELETE permission
40
-
41
- -- Login role inherits from these
42
- create role app_user login password 'xxx';
43
- grant app_writer to app_user;
44
- ```
45
-
46
- Revoke public defaults:
47
-
48
- ```sql
49
- -- Revoke default public access
50
- revoke all on schema public from public;
51
- revoke all on all tables in schema public from public;
52
- ```
53
-
54
- Reference: [Roles and Privileges](https://supabase.com/blog/postgres-roles-and-privileges)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md DELETED
@@ -1,50 +0,0 @@
1
- ---
2
- title: Enable Row Level Security for Multi-Tenant Data
3
- impact: CRITICAL
4
- impactDescription: Database-enforced tenant isolation, prevent data leaks
5
- tags: rls, row-level-security, multi-tenant, security
6
- ---
7
-
8
- ## Enable Row Level Security for Multi-Tenant Data
9
-
10
- Row Level Security (RLS) enforces data access at the database level, ensuring users only see their own data.
11
-
12
- **Incorrect (application-level filtering only):**
13
-
14
- ```sql
15
- -- Relying only on application to filter
16
- select * from orders where user_id = $current_user_id;
17
-
18
- -- Bug or bypass means all data is exposed!
19
- select * from orders; -- Returns ALL orders
20
- ```
21
-
22
- **Correct (database-enforced RLS):**
23
-
24
- ```sql
25
- -- Enable RLS on the table
26
- alter table orders enable row level security;
27
-
28
- -- Create policy for users to see only their orders
29
- create policy orders_user_policy on orders
30
- for all
31
- using (user_id = current_setting('app.current_user_id')::bigint);
32
-
33
- -- Force RLS even for table owners
34
- alter table orders force row level security;
35
-
36
- -- Set user context and query
37
- set app.current_user_id = '123';
38
- select * from orders; -- Only returns orders for user 123
39
- ```
40
-
41
- Policy for authenticated role:
42
-
43
- ```sql
44
- create policy orders_user_policy on orders
45
- for all
46
- to authenticated
47
- using (user_id = auth.uid());
48
- ```
49
-
50
- Reference: [Row Level Security](https://supabase.com/docs/guides/database/postgres/row-level-security)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md DELETED
@@ -1,57 +0,0 @@
1
- ---
2
- title: Optimize RLS Policies for Performance
3
- impact: HIGH
4
- impactDescription: 5-10x faster RLS queries with proper patterns
5
- tags: rls, performance, security, optimization
6
- ---
7
-
8
- ## Optimize RLS Policies for Performance
9
-
10
- Poorly written RLS policies can cause severe performance issues. Use subqueries and indexes strategically.
11
-
12
- **Incorrect (function called for every row):**
13
-
14
- ```sql
15
- create policy orders_policy on orders
16
- using (auth.uid() = user_id); -- auth.uid() called per row!
17
-
18
- -- With 1M rows, auth.uid() is called 1M times
19
- ```
20
-
21
- **Correct (wrap functions in SELECT):**
22
-
23
- ```sql
24
- create policy orders_policy on orders
25
- using ((select auth.uid()) = user_id); -- Called once, cached
26
-
27
- -- 100x+ faster on large tables
28
- ```
29
-
30
- Use security definer functions for complex checks:
31
-
32
- ```sql
33
- -- Create helper function (runs as definer, bypasses RLS)
34
- create or replace function is_team_member(team_id bigint)
35
- returns boolean
36
- language sql
37
- security definer
38
- set search_path = ''
39
- as $$
40
- select exists (
41
- select 1 from public.team_members
42
- where team_id = $1 and user_id = (select auth.uid())
43
- );
44
- $$;
45
-
46
- -- Use in policy (indexed lookup, not per-row check)
47
- create policy team_orders_policy on orders
48
- using ((select is_team_member(team_id)));
49
- ```
50
-
51
- Always add indexes on columns used in RLS policies:
52
-
53
- ```sql
54
- create index orders_user_id_idx on orders (user_id);
55
- ```
56
-
57
- Reference: [RLS Performance](https://supabase.com/docs/guides/database/postgres/row-level-security#rls-performance-recommendations)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase/SKILL.md DELETED
@@ -1,110 +0,0 @@
1
- ---
2
- name: supabase
3
- description: "Use when doing ANY task involving Supabase. Triggers: Supabase products (Database, Auth, Edge Functions, Realtime, Storage, Vectors, Cron, Queues); client libraries and SSR integrations (supabase-js, @supabase/ssr) in Next.js, React, SvelteKit, Astro, Remix; auth issues (login, logout, sessions, JWT, cookies, getSession, getUser, getClaims, RLS); Supabase CLI or MCP server; schema changes, migrations, security audits, Postgres extensions (pg_graphql, pg_cron, pg_vector)."
4
- metadata:
5
- author: supabase
6
- version: "0.1.0"
7
- ---
8
-
9
- # Supabase
10
-
11
- ## Core Principles
12
-
13
- **1. Supabase changes frequently — verify against current docs before implementing.**
14
- Do not rely on training data for Supabase features. Function signatures, config.toml settings, and API conventions change between versions. Before implementing, look up the relevant topic using the documentation access methods below.
15
-
16
- **2. Verify your work.**
17
- After implementing any fix, run a test query to confirm the change works. A fix without verification is incomplete.
18
-
19
- **3. Recover from errors, don't loop.**
20
- If an approach fails after 2-3 attempts, stop and reconsider. Try a different method, check documentation, inspect the error more carefully, and review relevant logs when available. Supabase issues are not always solved by retrying the same command, and the answer is not always in the logs, but logs are often worth checking before proceeding.
21
-
22
- **4. Exposing tables to the Data API:** Depending on the user's [Data API settings](https://supabase.com/dashboard/project/<ref>/integrations/data_api/settings), newly created tables may not be automatically exposed via the Data (REST) API. If this is the case, `anon` and `authenticated` roles will need to be explicitly granted access.
23
-
24
- > Note that this is separate from RLS, which controls which _rows_ are visible once a table is accessible, not whether the table is accessible at all.
25
-
26
- When a user reports a SQL-created table is unexpectedly inaccessible, check their Data API settings and whether the roles have been granted access via explicit `GRANT` SQL. When granting public (`anon`/`authenticated`) access, always enable RLS too. See [Exposing a Table to the Data API](https://supabase.com/docs/guides/database/data-api.md) for the full setup workflow.
27
-
28
- **5. RLS in exposed schemas.**
29
- Enable RLS on every table in any exposed schema, which includes `public` by default. This is critical in Supabase because tables in exposed schemas can be reachable through the Data API when the `anon`/`authenticated` roles have access (see [Exposing a Table to the Data API](https://supabase.com/docs/guides/database/data-api.md)). For private schemas, prefer RLS as defense in depth. After enabling RLS, create policies that match the actual access model rather than defaulting every table to the same `auth.uid()` pattern.
30
-
31
- **6. Security checklist.**
32
- When working on any Supabase task that touches auth, RLS, views, storage, or user data, run through this checklist. These are Supabase-specific security traps that silently create vulnerabilities:
33
-
34
- - **Auth and session security**
35
- - **Never use `user_metadata` claims in JWT-based authorization decisions.** In Supabase, `raw_user_meta_data` is user-editable and can appear in `auth.jwt()`, so it is unsafe for RLS policies or any other authorization logic. Store authorization data in `raw_app_meta_data` / `app_metadata` instead.
36
- - **Deleting a user does not invalidate existing access tokens.** Sign out or revoke sessions first, keep JWT expiry short for sensitive apps, and for strict guarantees validate `session_id` against `auth.sessions` on sensitive operations.
37
- - **If you use `app_metadata` or `auth.jwt()` for authorization, remember JWT claims are not always fresh until the user's token is refreshed.**
38
-
39
- - **API key and client exposure**
40
- - **Never expose the `service_role` or secret key in public clients.** Prefer publishable keys for frontend code. Legacy `anon` keys are only for compatibility. In Next.js, any `NEXT_PUBLIC_` env var is sent to the browser.
41
-
42
- - **RLS, views, and privileged database code**
43
- - **Views bypass RLS by default.** In Postgres 15 and above, use `CREATE VIEW ... WITH (security_invoker = true)`. In older versions of Postgres, protect your views by revoking access from the `anon` and `authenticated` roles, or by putting them in an unexposed schema.
44
- - **UPDATE requires a SELECT policy.** In Postgres RLS, an UPDATE needs to first SELECT the row. Without a SELECT policy, updates silently return 0 rows — no error, just no change.
45
- - **Do not put `security definer` functions in an exposed schema.** Keep them in a private or otherwise unexposed schema.
46
-
47
- - **Storage access control**
48
- - **Storage upsert requires INSERT + SELECT + UPDATE.** Granting only INSERT allows new uploads but file replacement (upsert) silently fails. You need all three.
49
-
50
- For any security concern not covered above, fetch the Supabase product security index: `https://supabase.com/docs/guides/security/product-security.md`
51
-
52
- ## Supabase CLI
53
-
54
- Always discover commands via `--help` — never guess. The CLI structure changes between versions.
55
-
56
- ```bash
57
- supabase --help # All top-level commands
58
- supabase <group> --help # Subcommands (e.g., supabase db --help)
59
- supabase <group> <command> --help # Flags for a specific command
60
- ```
61
-
62
- **Supabase CLI Known gotchas:**
63
-
64
- - `supabase db query` requires **CLI v2.79.0+** → use MCP `execute_sql` or `psql` as fallback
65
- - `supabase db advisors` requires **CLI v2.81.3+** → use MCP `get_advisors` as fallback
66
- - When you need a new migration SQL file, **always** create it with `supabase migration new <name>` first. Never invent a migration filename or rely on memory for the expected format.
67
-
68
- **Version check and upgrade:** Run `supabase --version` to check. For CLI changelogs and version-specific features, consult the [CLI documentation](https://supabase.com/docs/reference/cli/introduction) or [GitHub releases](https://github.com/supabase/cli/releases).
69
-
70
- ## Supabase MCP Server
71
-
72
- For setup instructions, server URL, and configuration, see the [MCP setup guide](https://supabase.com/docs/guides/getting-started/mcp).
73
-
74
- **Troubleshooting connection issues** — follow these steps in order:
75
-
76
- 1. **Check if the server is reachable:**
77
- `curl -so /dev/null -w "%{http_code}" https://mcp.supabase.com/mcp`
78
- A `401` is expected (no token) and means the server is up. Timeout or "connection refused" means it may be down.
79
-
80
- 2. **Check `.mcp.json` configuration:**
81
- Verify the project root has a valid `.mcp.json` with the correct server URL. If missing, create one pointing to `https://mcp.supabase.com/mcp`.
82
-
83
- 3. **Authenticate the MCP server:**
84
- If the server is reachable and `.mcp.json` is correct but tools aren't visible, the user needs to authenticate. The Supabase MCP server uses OAuth 2.1 — tell the user to trigger the auth flow in their agent, complete it in the browser, and reload the session.
85
-
86
- ## Supabase Documentation
87
-
88
- Before implementing any Supabase feature, find the relevant documentation. Use these methods in priority order:
89
-
90
- 1. **MCP `search_docs` tool** (preferred — returns relevant snippets directly)
91
- 2. **Fetch docs pages as markdown** — any docs page can be fetched by appending `.md` to the URL path.
92
- 3. **Web search** for Supabase-specific topics when you don't know which page to look at.
93
-
94
- ## Making and Committing Schema Changes
95
-
96
- **To make schema changes, use `execute_sql` (MCP) or `supabase db query` (CLI).** These run SQL directly on the database without creating migration history entries, so you can iterate freely and generate a clean migration when ready.
97
-
98
- Do NOT use `apply_migration` to change a local database schema — it writes a migration history entry on every call, which means you can't iterate, and `supabase db diff` / `supabase db pull` will produce empty or conflicting diffs. If you use it, you'll be stuck with whatever SQL you passed on the first try.
99
-
100
- **When ready to commit** your changes to a migration file:
101
-
102
- 1. **Run advisors** → `supabase db advisors` (CLI v2.81.3+) or MCP `get_advisors`. Fix any issues.
103
- 2. **Review the Security Checklist above** if your changes involve views, functions, triggers, or storage.
104
- 3. **Generate the migration** → `supabase db pull <descriptive-name> --local --yes`
105
- 4. **Verify** → `supabase migration list --local`
106
-
107
- ## Reference Guides
108
-
109
- - **Skill Feedback** → [references/skill-feedback.md](references/skill-feedback.md)
110
- **MUST read when** the user reports that this skill gave incorrect guidance or is missing information.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase/assets/feedback-issue-template.md DELETED
@@ -1,17 +0,0 @@
1
- ## What happened
2
-
3
- **Task:** <!-- e.g., "Set up MFA on patient records" -->
4
-
5
- **Skill said:** <!-- e.g., "Use auth.jwt()->'app_metadata' in the RLS policy" -->
6
-
7
- **Expected:** <!-- e.g., "The function also needs SECURITY DEFINER + grant to supabase_auth_admin" -->
8
-
9
- ## Source
10
-
11
- **File:** <!-- e.g., references/security-model.md -->
12
-
13
- **Section:** <!-- e.g., "Trust Boundaries > user_metadata vs app_metadata" -->
14
-
15
- ## Fix suggestion
16
-
17
- <!-- Leave blank if unsure -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.agents/skills/supabase/references/skill-feedback.md DELETED
@@ -1,17 +0,0 @@
1
- # Skill Feedback
2
-
3
- Use this when the user reports that the skill gave incorrect guidance, is missing information, or could be improved. This is about the skill (agent instructions), not about Supabase the product.
4
-
5
- ## Steps
6
-
7
- 1. **Ask permission** — Ask the user if they'd like to submit feedback to the skill maintainers. If they decline, move on.
8
-
9
- 2. **Draft the issue** — Use the template at [assets/feedback-issue-template.md](../assets/feedback-issue-template.md) to structure the feedback. Fill in the fields based on the conversation. Always identify which specific reference file and section caused the problem.
10
-
11
- 3. **Submit** — Create a GitHub Issue on the `supabase/agent-skills` repository using the draft as the issue body. The title must follow this format: `user-feedback: <summary of the problem>`.
12
-
13
- 4. **Share the result** — Share the issue URL with the user after submission. If submission fails, give the user this link to create the issue manually:
14
-
15
- ```
16
- https://github.com/supabase/agent-skills/issues/new
17
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.claude/skills/supabase-postgres-best-practices/SKILL.md DELETED
@@ -1,64 +0,0 @@
1
- ---
2
- name: supabase-postgres-best-practices
3
- description: Postgres performance optimization and best practices from Supabase. Use this skill when writing, reviewing, or optimizing Postgres queries, schema designs, or database configurations.
4
- license: MIT
5
- metadata:
6
- author: supabase
7
- version: "1.1.1"
8
- organization: Supabase
9
- date: January 2026
10
- abstract: Comprehensive Postgres performance optimization guide for developers using Supabase and Postgres. Contains performance rules across 8 categories, prioritized by impact from critical (query performance, connection management) to incremental (advanced features). Each rule includes detailed explanations, incorrect vs. correct SQL examples, query plan analysis, and specific performance metrics to guide automated optimization and code generation.
11
- ---
12
-
13
- # Supabase Postgres Best Practices
14
-
15
- Comprehensive performance optimization guide for Postgres, maintained by Supabase. Contains rules across 8 categories, prioritized by impact to guide automated query optimization and schema design.
16
-
17
- ## When to Apply
18
-
19
- Reference these guidelines when:
20
- - Writing SQL queries or designing schemas
21
- - Implementing indexes or query optimization
22
- - Reviewing database performance issues
23
- - Configuring connection pooling or scaling
24
- - Optimizing for Postgres-specific features
25
- - Working with Row-Level Security (RLS)
26
-
27
- ## Rule Categories by Priority
28
-
29
- | Priority | Category | Impact | Prefix |
30
- |----------|----------|--------|--------|
31
- | 1 | Query Performance | CRITICAL | `query-` |
32
- | 2 | Connection Management | CRITICAL | `conn-` |
33
- | 3 | Security & RLS | CRITICAL | `security-` |
34
- | 4 | Schema Design | HIGH | `schema-` |
35
- | 5 | Concurrency & Locking | MEDIUM-HIGH | `lock-` |
36
- | 6 | Data Access Patterns | MEDIUM | `data-` |
37
- | 7 | Monitoring & Diagnostics | LOW-MEDIUM | `monitor-` |
38
- | 8 | Advanced Features | LOW | `advanced-` |
39
-
40
- ## How to Use
41
-
42
- Read individual rule files for detailed explanations and SQL examples:
43
-
44
- ```
45
- references/query-missing-indexes.md
46
- references/query-partial-indexes.md
47
- references/_sections.md
48
- ```
49
-
50
- Each rule file contains:
51
- - Brief explanation of why it matters
52
- - Incorrect SQL example with explanation
53
- - Correct SQL example with explanation
54
- - Optional EXPLAIN output or metrics
55
- - Additional context and references
56
- - Supabase-specific notes (when applicable)
57
-
58
- ## References
59
-
60
- - https://www.postgresql.org/docs/current/
61
- - https://supabase.com/docs
62
- - https://wiki.postgresql.org/wiki/Performance_Optimization
63
- - https://supabase.com/docs/guides/database/overview
64
- - https://supabase.com/docs/guides/auth/row-level-security
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.claude/skills/supabase-postgres-best-practices/references/_contributing.md DELETED
@@ -1,170 +0,0 @@
1
- # Writing Guidelines for Postgres References
2
-
3
- This document provides guidelines for creating effective Postgres best
4
- practice references that work well with AI agents and LLMs.
5
-
6
- ## Key Principles
7
-
8
- ### 1. Concrete Transformation Patterns
9
-
10
- Show exact SQL rewrites. Avoid philosophical advice.
11
-
12
- **Good:** "Use `WHERE id = ANY(ARRAY[...])` instead of
13
- `WHERE id IN (SELECT ...)`" **Bad:** "Design good schemas"
14
-
15
- ### 2. Error-First Structure
16
-
17
- Always show the problematic pattern first, then the solution. This trains agents
18
- to recognize anti-patterns.
19
-
20
- ```markdown
21
- **Incorrect (sequential queries):** [bad example]
22
-
23
- **Correct (batched query):** [good example]
24
- ```
25
-
26
- ### 3. Quantified Impact
27
-
28
- Include specific metrics. Helps agents prioritize fixes.
29
-
30
- **Good:** "10x faster queries", "50% smaller index", "Eliminates N+1"
31
- **Bad:** "Faster", "Better", "More efficient"
32
-
33
- ### 4. Self-Contained Examples
34
-
35
- Examples should be complete and runnable (or close to it). Include `CREATE TABLE`
36
- if context is needed.
37
-
38
- ```sql
39
- -- Include table definition when needed for clarity
40
- CREATE TABLE users (
41
- id bigint PRIMARY KEY,
42
- email text NOT NULL,
43
- deleted_at timestamptz
44
- );
45
-
46
- -- Now show the index
47
- CREATE INDEX users_active_email_idx ON users(email) WHERE deleted_at IS NULL;
48
- ```
49
-
50
- ### 5. Semantic Naming
51
-
52
- Use meaningful table/column names. Names carry intent for LLMs.
53
-
54
- **Good:** `users`, `email`, `created_at`, `is_active`
55
- **Bad:** `table1`, `col1`, `field`, `flag`
56
-
57
- ---
58
-
59
- ## Code Example Standards
60
-
61
- ### SQL Formatting
62
-
63
- ```sql
64
- -- Use lowercase keywords, clear formatting
65
- CREATE INDEX CONCURRENTLY users_email_idx
66
- ON users(email)
67
- WHERE deleted_at IS NULL;
68
-
69
- -- Not cramped or ALL CAPS
70
- CREATE INDEX CONCURRENTLY USERS_EMAIL_IDX ON USERS(EMAIL) WHERE DELETED_AT IS NULL;
71
- ```
72
-
73
- ### Comments
74
-
75
- - Explain _why_, not _what_
76
- - Highlight performance implications
77
- - Point out common pitfalls
78
-
79
- ### Language Tags
80
-
81
- - `sql` - Standard SQL queries
82
- - `plpgsql` - Stored procedures/functions
83
- - `typescript` - Application code (when needed)
84
- - `python` - Application code (when needed)
85
-
86
- ---
87
-
88
- ## When to Include Application Code
89
-
90
- **Default: SQL Only**
91
-
92
- Most references should focus on pure SQL patterns. This keeps examples portable.
93
-
94
- **Include Application Code When:**
95
-
96
- - Connection pooling configuration
97
- - Transaction management in application context
98
- - ORM anti-patterns (N+1 in Prisma/TypeORM)
99
- - Prepared statement usage
100
-
101
- **Format for Mixed Examples:**
102
-
103
- ````markdown
104
- **Incorrect (N+1 in application):**
105
-
106
- ```typescript
107
- for (const user of users) {
108
- const posts = await db.query("SELECT * FROM posts WHERE user_id = $1", [
109
- user.id,
110
- ]);
111
- }
112
- ```
113
- ````
114
-
115
- **Correct (batch query):**
116
-
117
- ```typescript
118
- const posts = await db.query("SELECT * FROM posts WHERE user_id = ANY($1)", [
119
- userIds,
120
- ]);
121
- ```
122
-
123
- ---
124
-
125
- ## Impact Level Guidelines
126
-
127
- | Level | Improvement | Use When |
128
- |-------|-------------|----------|
129
- | **CRITICAL** | 10-100x | Missing indexes, connection exhaustion, sequential scans on large tables |
130
- | **HIGH** | 5-20x | Wrong index types, poor partitioning, missing covering indexes |
131
- | **MEDIUM-HIGH** | 2-5x | N+1 queries, inefficient pagination, RLS optimization |
132
- | **MEDIUM** | 1.5-3x | Redundant indexes, query plan instability |
133
- | **LOW-MEDIUM** | 1.2-2x | VACUUM tuning, configuration tweaks |
134
- | **LOW** | Incremental | Advanced patterns, edge cases |
135
-
136
- ---
137
-
138
- ## Reference Standards
139
-
140
- **Primary Sources:**
141
-
142
- - Official Postgres documentation
143
- - Supabase documentation
144
- - Postgres wiki
145
- - Established blogs (2ndQuadrant, Crunchy Data)
146
-
147
- **Format:**
148
-
149
- ```markdown
150
- Reference:
151
- [Postgres Indexes](https://www.postgresql.org/docs/current/indexes.html)
152
- ```
153
-
154
- ---
155
-
156
- ## Review Checklist
157
-
158
- Before submitting a reference:
159
-
160
- - [ ] Title is clear and action-oriented
161
- - [ ] Impact level matches the performance gain
162
- - [ ] impactDescription includes quantification
163
- - [ ] Explanation is concise (1-2 sentences)
164
- - [ ] Has at least 1 **Incorrect** SQL example
165
- - [ ] Has at least 1 **Correct** SQL example
166
- - [ ] SQL uses semantic naming
167
- - [ ] Comments explain _why_, not _what_
168
- - [ ] Trade-offs mentioned if applicable
169
- - [ ] Reference links included
170
- - [ ] `pnpm test` passes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.claude/skills/supabase-postgres-best-practices/references/_sections.md DELETED
@@ -1,39 +0,0 @@
1
- # Section Definitions
2
-
3
- This file defines the rule categories for Postgres best practices. Rules are automatically assigned to sections based on their filename prefix.
4
-
5
- Take the examples below as pure demonstrative. Replace each section with the actual rule categories for Postgres best practices.
6
-
7
- ---
8
-
9
- ## 1. Query Performance (query)
10
- **Impact:** CRITICAL
11
- **Description:** Slow queries, missing indexes, inefficient query plans. The most common source of Postgres performance issues.
12
-
13
- ## 2. Connection Management (conn)
14
- **Impact:** CRITICAL
15
- **Description:** Connection pooling, limits, and serverless strategies. Critical for applications with high concurrency or serverless deployments.
16
-
17
- ## 3. Security & RLS (security)
18
- **Impact:** CRITICAL
19
- **Description:** Row-Level Security policies, privilege management, and authentication patterns.
20
-
21
- ## 4. Schema Design (schema)
22
- **Impact:** HIGH
23
- **Description:** Table design, index strategies, partitioning, and data type selection. Foundation for long-term performance.
24
-
25
- ## 5. Concurrency & Locking (lock)
26
- **Impact:** MEDIUM-HIGH
27
- **Description:** Transaction management, isolation levels, deadlock prevention, and lock contention patterns.
28
-
29
- ## 6. Data Access Patterns (data)
30
- **Impact:** MEDIUM
31
- **Description:** N+1 query elimination, batch operations, cursor-based pagination, and efficient data fetching.
32
-
33
- ## 7. Monitoring & Diagnostics (monitor)
34
- **Impact:** LOW-MEDIUM
35
- **Description:** Using pg_stat_statements, EXPLAIN ANALYZE, metrics collection, and performance diagnostics.
36
-
37
- ## 8. Advanced Features (advanced)
38
- **Impact:** LOW
39
- **Description:** Full-text search, JSONB optimization, PostGIS, extensions, and advanced Postgres features.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.claude/skills/supabase-postgres-best-practices/references/_template.md DELETED
@@ -1,34 +0,0 @@
1
- ---
2
- title: Clear, Action-Oriented Title (e.g., "Use Partial Indexes for Filtered Queries")
3
- impact: MEDIUM
4
- impactDescription: 5-20x query speedup for filtered queries
5
- tags: indexes, query-optimization, performance
6
- ---
7
-
8
- ## [Rule Title]
9
-
10
- [1-2 sentence explanation of the problem and why it matters. Focus on performance impact.]
11
-
12
- **Incorrect (describe the problem):**
13
-
14
- ```sql
15
- -- Comment explaining what makes this slow/problematic
16
- CREATE INDEX users_email_idx ON users(email);
17
-
18
- SELECT * FROM users WHERE email = 'user@example.com' AND deleted_at IS NULL;
19
- -- This scans deleted records unnecessarily
20
- ```
21
-
22
- **Correct (describe the solution):**
23
-
24
- ```sql
25
- -- Comment explaining why this is better
26
- CREATE INDEX users_active_email_idx ON users(email) WHERE deleted_at IS NULL;
27
-
28
- SELECT * FROM users WHERE email = 'user@example.com' AND deleted_at IS NULL;
29
- -- Only indexes active users, 10x smaller index, faster queries
30
- ```
31
-
32
- [Optional: Additional context, edge cases, or trade-offs]
33
-
34
- Reference: [Postgres Docs](https://www.postgresql.org/docs/current/)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.claude/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md DELETED
@@ -1,55 +0,0 @@
1
- ---
2
- title: Use tsvector for Full-Text Search
3
- impact: MEDIUM
4
- impactDescription: 100x faster than LIKE, with ranking support
5
- tags: full-text-search, tsvector, gin, search
6
- ---
7
-
8
- ## Use tsvector for Full-Text Search
9
-
10
- LIKE with wildcards can't use indexes. Full-text search with tsvector is orders of magnitude faster.
11
-
12
- **Incorrect (LIKE pattern matching):**
13
-
14
- ```sql
15
- -- Cannot use index, scans all rows
16
- select * from articles where content like '%postgresql%';
17
-
18
- -- Case-insensitive makes it worse
19
- select * from articles where lower(content) like '%postgresql%';
20
- ```
21
-
22
- **Correct (full-text search with tsvector):**
23
-
24
- ```sql
25
- -- Add tsvector column and index
26
- alter table articles add column search_vector tsvector
27
- generated always as (to_tsvector('english', coalesce(title,'') || ' ' || coalesce(content,''))) stored;
28
-
29
- create index articles_search_idx on articles using gin (search_vector);
30
-
31
- -- Fast full-text search
32
- select * from articles
33
- where search_vector @@ to_tsquery('english', 'postgresql & performance');
34
-
35
- -- With ranking
36
- select *, ts_rank(search_vector, query) as rank
37
- from articles, to_tsquery('english', 'postgresql') query
38
- where search_vector @@ query
39
- order by rank desc;
40
- ```
41
-
42
- Search multiple terms:
43
-
44
- ```sql
45
- -- AND: both terms required
46
- to_tsquery('postgresql & performance')
47
-
48
- -- OR: either term
49
- to_tsquery('postgresql | mysql')
50
-
51
- -- Prefix matching
52
- to_tsquery('post:*')
53
- ```
54
-
55
- Reference: [Full Text Search](https://supabase.com/docs/guides/database/full-text-search)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.claude/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md DELETED
@@ -1,49 +0,0 @@
1
- ---
2
- title: Index JSONB Columns for Efficient Querying
3
- impact: MEDIUM
4
- impactDescription: 10-100x faster JSONB queries with proper indexing
5
- tags: jsonb, gin, indexes, json
6
- ---
7
-
8
- ## Index JSONB Columns for Efficient Querying
9
-
10
- JSONB queries without indexes scan the entire table. Use GIN indexes for containment queries.
11
-
12
- **Incorrect (no index on JSONB):**
13
-
14
- ```sql
15
- create table products (
16
- id bigint primary key,
17
- attributes jsonb
18
- );
19
-
20
- -- Full table scan for every query
21
- select * from products where attributes @> '{"color": "red"}';
22
- select * from products where attributes->>'brand' = 'Nike';
23
- ```
24
-
25
- **Correct (GIN index for JSONB):**
26
-
27
- ```sql
28
- -- GIN index for containment operators (@>, ?, ?&, ?|)
29
- create index products_attrs_gin on products using gin (attributes);
30
-
31
- -- Now containment queries use the index
32
- select * from products where attributes @> '{"color": "red"}';
33
-
34
- -- For specific key lookups, use expression index
35
- create index products_brand_idx on products ((attributes->>'brand'));
36
- select * from products where attributes->>'brand' = 'Nike';
37
- ```
38
-
39
- Choose the right operator class:
40
-
41
- ```sql
42
- -- jsonb_ops (default): supports all operators, larger index
43
- create index idx1 on products using gin (attributes);
44
-
45
- -- jsonb_path_ops: only @> operator, but 2-3x smaller index
46
- create index idx2 on products using gin (attributes jsonb_path_ops);
47
- ```
48
-
49
- Reference: [JSONB Indexes](https://www.postgresql.org/docs/current/datatype-json.html#JSON-INDEXING)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.claude/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md DELETED
@@ -1,46 +0,0 @@
1
- ---
2
- title: Configure Idle Connection Timeouts
3
- impact: HIGH
4
- impactDescription: Reclaim 30-50% of connection slots from idle clients
5
- tags: connections, timeout, idle, resource-management
6
- ---
7
-
8
- ## Configure Idle Connection Timeouts
9
-
10
- Idle connections waste resources. Configure timeouts to automatically reclaim them.
11
-
12
- **Incorrect (connections held indefinitely):**
13
-
14
- ```sql
15
- -- No timeout configured
16
- show idle_in_transaction_session_timeout; -- 0 (disabled)
17
-
18
- -- Connections stay open forever, even when idle
19
- select pid, state, state_change, query
20
- from pg_stat_activity
21
- where state = 'idle in transaction';
22
- -- Shows transactions idle for hours, holding locks
23
- ```
24
-
25
- **Correct (automatic cleanup of idle connections):**
26
-
27
- ```sql
28
- -- Terminate connections idle in transaction after 30 seconds
29
- alter system set idle_in_transaction_session_timeout = '30s';
30
-
31
- -- Terminate completely idle connections after 10 minutes
32
- alter system set idle_session_timeout = '10min';
33
-
34
- -- Reload configuration
35
- select pg_reload_conf();
36
- ```
37
-
38
- For pooled connections, configure at the pooler level:
39
-
40
- ```ini
41
- # pgbouncer.ini
42
- server_idle_timeout = 60
43
- client_idle_timeout = 300
44
- ```
45
-
46
- Reference: [Connection Timeouts](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-IDLE-IN-TRANSACTION-SESSION-TIMEOUT)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.claude/skills/supabase-postgres-best-practices/references/conn-limits.md DELETED
@@ -1,44 +0,0 @@
1
- ---
2
- title: Set Appropriate Connection Limits
3
- impact: CRITICAL
4
- impactDescription: Prevent database crashes and memory exhaustion
5
- tags: connections, max-connections, limits, stability
6
- ---
7
-
8
- ## Set Appropriate Connection Limits
9
-
10
- Too many connections exhaust memory and degrade performance. Set limits based on available resources.
11
-
12
- **Incorrect (unlimited or excessive connections):**
13
-
14
- ```sql
15
- -- Default max_connections = 100, but often increased blindly
16
- show max_connections; -- 500 (way too high for 4GB RAM)
17
-
18
- -- Each connection uses 1-3MB RAM
19
- -- 500 connections * 2MB = 1GB just for connections!
20
- -- Out of memory errors under load
21
- ```
22
-
23
- **Correct (calculate based on resources):**
24
-
25
- ```sql
26
- -- Formula: max_connections = (RAM in MB / 5MB per connection) - reserved
27
- -- For 4GB RAM: (4096 / 5) - 10 = ~800 theoretical max
28
- -- But practically, 100-200 is better for query performance
29
-
30
- -- Recommended settings for 4GB RAM
31
- alter system set max_connections = 100;
32
-
33
- -- Also set work_mem appropriately
34
- -- work_mem * max_connections should not exceed 25% of RAM
35
- alter system set work_mem = '8MB'; -- 8MB * 100 = 800MB max
36
- ```
37
-
38
- Monitor connection usage:
39
-
40
- ```sql
41
- select count(*), state from pg_stat_activity group by state;
42
- ```
43
-
44
- Reference: [Database Connections](https://supabase.com/docs/guides/platform/performance#connection-management)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.claude/skills/supabase-postgres-best-practices/references/conn-pooling.md DELETED
@@ -1,41 +0,0 @@
1
- ---
2
- title: Use Connection Pooling for All Applications
3
- impact: CRITICAL
4
- impactDescription: Handle 10-100x more concurrent users
5
- tags: connection-pooling, pgbouncer, performance, scalability
6
- ---
7
-
8
- ## Use Connection Pooling for All Applications
9
-
10
- Postgres connections are expensive (1-3MB RAM each). Without pooling, applications exhaust connections under load.
11
-
12
- **Incorrect (new connection per request):**
13
-
14
- ```sql
15
- -- Each request creates a new connection
16
- -- Application code: db.connect() per request
17
- -- Result: 500 concurrent users = 500 connections = crashed database
18
-
19
- -- Check current connections
20
- select count(*) from pg_stat_activity; -- 487 connections!
21
- ```
22
-
23
- **Correct (connection pooling):**
24
-
25
- ```sql
26
- -- Use a pooler like PgBouncer between app and database
27
- -- Application connects to pooler, pooler reuses a small pool to Postgres
28
-
29
- -- Configure pool_size based on: (CPU cores * 2) + spindle_count
30
- -- Example for 4 cores: pool_size = 10
31
-
32
- -- Result: 500 concurrent users share 10 actual connections
33
- select count(*) from pg_stat_activity; -- 10 connections
34
- ```
35
-
36
- Pool modes:
37
-
38
- - **Transaction mode**: connection returned after each transaction (best for most apps)
39
- - **Session mode**: connection held for entire session (needed for prepared statements, temp tables)
40
-
41
- Reference: [Connection Pooling](https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pooler)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.claude/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md DELETED
@@ -1,46 +0,0 @@
1
- ---
2
- title: Use Prepared Statements Correctly with Pooling
3
- impact: HIGH
4
- impactDescription: Avoid prepared statement conflicts in pooled environments
5
- tags: prepared-statements, connection-pooling, transaction-mode
6
- ---
7
-
8
- ## Use Prepared Statements Correctly with Pooling
9
-
10
- Prepared statements are tied to individual database connections. In transaction-mode pooling, connections are shared, causing conflicts.
11
-
12
- **Incorrect (named prepared statements with transaction pooling):**
13
-
14
- ```sql
15
- -- Named prepared statement
16
- prepare get_user as select * from users where id = $1;
17
-
18
- -- In transaction mode pooling, next request may get different connection
19
- execute get_user(123);
20
- -- ERROR: prepared statement "get_user" does not exist
21
- ```
22
-
23
- **Correct (use unnamed statements or session mode):**
24
-
25
- ```sql
26
- -- Option 1: Use unnamed prepared statements (most ORMs do this automatically)
27
- -- The query is prepared and executed in a single protocol message
28
-
29
- -- Option 2: Deallocate after use in transaction mode
30
- prepare get_user as select * from users where id = $1;
31
- execute get_user(123);
32
- deallocate get_user;
33
-
34
- -- Option 3: Use session mode pooling (port 5432 vs 6543)
35
- -- Connection is held for entire session, prepared statements persist
36
- ```
37
-
38
- Check your driver settings:
39
-
40
- ```sql
41
- -- Many drivers use prepared statements by default
42
- -- Node.js pg: { prepare: false } to disable
43
- -- JDBC: prepareThreshold=0 to disable
44
- ```
45
-
46
- Reference: [Prepared Statements with Pooling](https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pool-modes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.claude/skills/supabase-postgres-best-practices/references/data-batch-inserts.md DELETED
@@ -1,54 +0,0 @@
1
- ---
2
- title: Batch INSERT Statements for Bulk Data
3
- impact: MEDIUM
4
- impactDescription: 10-50x faster bulk inserts
5
- tags: batch, insert, bulk, performance, copy
6
- ---
7
-
8
- ## Batch INSERT Statements for Bulk Data
9
-
10
- Individual INSERT statements have high overhead. Batch multiple rows in single statements or use COPY.
11
-
12
- **Incorrect (individual inserts):**
13
-
14
- ```sql
15
- -- Each insert is a separate transaction and round trip
16
- insert into events (user_id, action) values (1, 'click');
17
- insert into events (user_id, action) values (1, 'view');
18
- insert into events (user_id, action) values (2, 'click');
19
- -- ... 1000 more individual inserts
20
-
21
- -- 1000 inserts = 1000 round trips = slow
22
- ```
23
-
24
- **Correct (batch insert):**
25
-
26
- ```sql
27
- -- Multiple rows in single statement
28
- insert into events (user_id, action) values
29
- (1, 'click'),
30
- (1, 'view'),
31
- (2, 'click'),
32
- -- ... up to ~1000 rows per batch
33
- (999, 'view');
34
-
35
- -- One round trip for 1000 rows
36
- ```
37
-
38
- For large imports, use COPY:
39
-
40
- ```sql
41
- -- COPY is fastest for bulk loading
42
- copy events (user_id, action, created_at)
43
- from '/path/to/data.csv'
44
- with (format csv, header true);
45
-
46
- -- Or from stdin in application
47
- copy events (user_id, action) from stdin with (format csv);
48
- 1,click
49
- 1,view
50
- 2,click
51
- \.
52
- ```
53
-
54
- Reference: [COPY](https://www.postgresql.org/docs/current/sql-copy.html)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.claude/skills/supabase-postgres-best-practices/references/data-n-plus-one.md DELETED
@@ -1,53 +0,0 @@
1
- ---
2
- title: Eliminate N+1 Queries with Batch Loading
3
- impact: MEDIUM-HIGH
4
- impactDescription: 10-100x fewer database round trips
5
- tags: n-plus-one, batch, performance, queries
6
- ---
7
-
8
- ## Eliminate N+1 Queries with Batch Loading
9
-
10
- N+1 queries execute one query per item in a loop. Batch them into a single query using arrays or JOINs.
11
-
12
- **Incorrect (N+1 queries):**
13
-
14
- ```sql
15
- -- First query: get all users
16
- select id from users where active = true; -- Returns 100 IDs
17
-
18
- -- Then N queries, one per user
19
- select * from orders where user_id = 1;
20
- select * from orders where user_id = 2;
21
- select * from orders where user_id = 3;
22
- -- ... 97 more queries!
23
-
24
- -- Total: 101 round trips to database
25
- ```
26
-
27
- **Correct (single batch query):**
28
-
29
- ```sql
30
- -- Collect IDs and query once with ANY
31
- select * from orders where user_id = any(array[1, 2, 3, ...]);
32
-
33
- -- Or use JOIN instead of loop
34
- select u.id, u.name, o.*
35
- from users u
36
- left join orders o on o.user_id = u.id
37
- where u.active = true;
38
-
39
- -- Total: 1 round trip
40
- ```
41
-
42
- Application pattern:
43
-
44
- ```sql
45
- -- Instead of looping in application code:
46
- -- for user in users: db.query("SELECT * FROM orders WHERE user_id = $1", user.id)
47
-
48
- -- Pass array parameter:
49
- select * from orders where user_id = any($1::bigint[]);
50
- -- Application passes: [1, 2, 3, 4, 5, ...]
51
- ```
52
-
53
- Reference: [N+1 Query Problem](https://supabase.com/docs/guides/database/query-optimization)