@intentsolutionsio/supabase-pack 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +73 -47
- package/package.json +4 -4
- package/skills/supabase-advanced-troubleshooting/SKILL.md +404 -200
- package/skills/supabase-advanced-troubleshooting/references/errors.md +11 -0
- package/skills/supabase-advanced-troubleshooting/references/evidence-collection-framework.md +34 -0
- package/skills/supabase-advanced-troubleshooting/references/examples.md +11 -0
- package/skills/supabase-advanced-troubleshooting/references/rls-edge-functions-realtime.md +363 -0
- package/skills/supabase-advanced-troubleshooting/references/systematic-isolation.md +56 -0
- package/skills/supabase-advanced-troubleshooting/references/timing-analysis.md +35 -0
- package/skills/supabase-architecture-variants/SKILL.md +395 -216
- package/skills/supabase-architecture-variants/references/errors.md +11 -0
- package/skills/supabase-architecture-variants/references/examples.md +12 -0
- package/skills/supabase-architecture-variants/references/serverless-and-multi-tenant.md +251 -0
- package/skills/supabase-architecture-variants/references/variant-a-monolith-(simple).md +44 -0
- package/skills/supabase-architecture-variants/references/variant-b-service-layer-(moderate).md +72 -0
- package/skills/supabase-architecture-variants/references/variant-c-microservice-(complex).md +81 -0
- package/skills/supabase-auth-storage-realtime-core/SKILL.md +471 -37
- package/skills/supabase-ci-integration/SKILL.md +315 -67
- package/skills/supabase-ci-integration/references/errors.md +10 -0
- package/skills/supabase-ci-integration/references/examples.md +36 -0
- package/skills/supabase-ci-integration/references/implementation.md +54 -0
- package/skills/supabase-common-errors/SKILL.md +320 -62
- package/skills/supabase-common-errors/references/errors.md +53 -0
- package/skills/supabase-common-errors/references/examples.md +23 -0
- package/skills/supabase-cost-tuning/SKILL.md +365 -131
- package/skills/supabase-cost-tuning/references/cost-estimation.md +34 -0
- package/skills/supabase-cost-tuning/references/cost-reduction-strategies.md +40 -0
- package/skills/supabase-cost-tuning/references/errors.md +11 -0
- package/skills/supabase-cost-tuning/references/examples.md +15 -0
- package/skills/supabase-data-handling/SKILL.md +378 -145
- package/skills/supabase-data-handling/references/errors.md +11 -0
- package/skills/supabase-data-handling/references/examples.md +27 -0
- package/skills/supabase-data-handling/references/implementation.md +223 -0
- package/skills/supabase-data-handling/references/retention-and-backup.md +221 -0
- package/skills/supabase-debug-bundle/SKILL.md +267 -73
- package/skills/supabase-debug-bundle/references/errors.md +12 -0
- package/skills/supabase-debug-bundle/references/examples.md +24 -0
- package/skills/supabase-debug-bundle/references/implementation.md +54 -0
- package/skills/supabase-deploy-integration/SKILL.md +258 -147
- package/skills/supabase-deploy-integration/references/errors.md +11 -0
- package/skills/supabase-deploy-integration/references/examples.md +21 -0
- package/skills/supabase-deploy-integration/references/google-cloud-run.md +36 -0
- package/skills/supabase-deploy-integration/references/vercel-deployment.md +35 -0
- package/skills/supabase-enterprise-rbac/SKILL.md +327 -160
- package/skills/supabase-enterprise-rbac/references/api-scoping-and-enforcement.md +255 -0
- package/skills/supabase-enterprise-rbac/references/errors.md +11 -0
- package/skills/supabase-enterprise-rbac/references/examples.md +12 -0
- package/skills/supabase-enterprise-rbac/references/role-implementation.md +33 -0
- package/skills/supabase-enterprise-rbac/references/sso-integration.md +35 -0
- package/skills/supabase-hello-world/SKILL.md +160 -54
- package/skills/supabase-incident-runbook/SKILL.md +453 -131
- package/skills/supabase-incident-runbook/references/errors.md +11 -0
- package/skills/supabase-incident-runbook/references/examples.md +10 -0
- package/skills/supabase-incident-runbook/references/immediate-actions-by-error-type.md +41 -0
- package/skills/supabase-install-auth/SKILL.md +186 -50
- package/skills/supabase-install-auth/references/examples.md +102 -0
- package/skills/supabase-known-pitfalls/SKILL.md +411 -241
- package/skills/supabase-known-pitfalls/references/errors.md +11 -0
- package/skills/supabase-known-pitfalls/references/examples.md +12 -0
- package/skills/supabase-load-scale/SKILL.md +346 -217
- package/skills/supabase-load-scale/references/capacity-planning.md +47 -0
- package/skills/supabase-load-scale/references/errors.md +11 -0
- package/skills/supabase-load-scale/references/examples.md +26 -0
- package/skills/supabase-load-scale/references/load-testing-with-k6.md +59 -0
- package/skills/supabase-load-scale/references/scaling-patterns.md +65 -0
- package/skills/supabase-load-scale/references/table-partitioning.md +263 -0
- package/skills/supabase-local-dev-loop/SKILL.md +272 -73
- package/skills/supabase-local-dev-loop/references/errors.md +11 -0
- package/skills/supabase-local-dev-loop/references/examples.md +21 -0
- package/skills/supabase-local-dev-loop/references/implementation.md +60 -0
- package/skills/supabase-migration-deep-dive/SKILL.md +338 -177
- package/skills/supabase-migration-deep-dive/references/backfill-versioning-rollback.md +258 -0
- package/skills/supabase-migration-deep-dive/references/errors.md +11 -0
- package/skills/supabase-migration-deep-dive/references/examples.md +12 -0
- package/skills/supabase-migration-deep-dive/references/implementation-plan.md +80 -0
- package/skills/supabase-migration-deep-dive/references/pre-migration-assessment.md +39 -0
- package/skills/supabase-multi-env-setup/SKILL.md +393 -152
- package/skills/supabase-multi-env-setup/references/configuration-structure.md +59 -0
- package/skills/supabase-multi-env-setup/references/errors.md +11 -0
- package/skills/supabase-multi-env-setup/references/examples.md +11 -0
- package/skills/supabase-observability/SKILL.md +318 -196
- package/skills/supabase-observability/references/alert-configuration.md +40 -0
- package/skills/supabase-observability/references/errors.md +11 -0
- package/skills/supabase-observability/references/examples.md +13 -0
- package/skills/supabase-observability/references/metrics-collection.md +65 -0
- package/skills/supabase-performance-tuning/SKILL.md +304 -160
- package/skills/supabase-performance-tuning/references/caching-strategy.md +49 -0
- package/skills/supabase-performance-tuning/references/errors.md +11 -0
- package/skills/supabase-performance-tuning/references/examples.md +13 -0
- package/skills/supabase-policy-guardrails/SKILL.md +248 -221
- package/skills/supabase-policy-guardrails/references/ci-cost-security.md +484 -0
- package/skills/supabase-policy-guardrails/references/errors.md +11 -0
- package/skills/supabase-policy-guardrails/references/eslint-rules.md +46 -0
- package/skills/supabase-policy-guardrails/references/examples.md +10 -0
- package/skills/supabase-prod-checklist/SKILL.md +474 -84
- package/skills/supabase-prod-checklist/references/errors.md +63 -0
- package/skills/supabase-prod-checklist/references/examples.md +153 -0
- package/skills/supabase-prod-checklist/references/implementation.md +113 -0
- package/skills/supabase-rate-limits/SKILL.md +311 -98
- package/skills/supabase-rate-limits/references/errors.md +11 -0
- package/skills/supabase-rate-limits/references/examples.md +46 -0
- package/skills/supabase-rate-limits/references/implementation.md +66 -0
- package/skills/supabase-reference-architecture/SKILL.md +249 -182
- package/skills/supabase-reference-architecture/references/errors.md +29 -0
- package/skills/supabase-reference-architecture/references/examples.md +116 -0
- package/skills/supabase-reference-architecture/references/key-components.md +244 -0
- package/skills/supabase-reference-architecture/references/project-structure.md +109 -0
- package/skills/supabase-reliability-patterns/SKILL.md +229 -234
- package/skills/supabase-reliability-patterns/references/circuit-breaker.md +36 -0
- package/skills/supabase-reliability-patterns/references/dead-letter-queue.md +48 -0
- package/skills/supabase-reliability-patterns/references/errors.md +11 -0
- package/skills/supabase-reliability-patterns/references/examples.md +11 -0
- package/skills/supabase-reliability-patterns/references/idempotency-keys.md +36 -0
- package/skills/supabase-reliability-patterns/references/offline-degradation-health-dualwrite.md +489 -0
- package/skills/supabase-schema-from-requirements/SKILL.md +373 -34
- package/skills/supabase-sdk-patterns/SKILL.md +388 -99
- package/skills/supabase-sdk-patterns/references/errors.md +11 -0
- package/skills/supabase-sdk-patterns/references/examples.md +45 -0
- package/skills/supabase-sdk-patterns/references/implementation.md +67 -0
- package/skills/supabase-security-basics/SKILL.md +282 -102
- package/skills/supabase-security-basics/references/errors.md +10 -0
- package/skills/supabase-security-basics/references/examples.md +70 -0
- package/skills/supabase-security-basics/references/implementation.md +39 -0
- package/skills/supabase-upgrade-migration/SKILL.md +248 -66
- package/skills/supabase-upgrade-migration/references/errors.md +10 -0
- package/skills/supabase-upgrade-migration/references/examples.md +51 -0
- package/skills/supabase-upgrade-migration/references/implementation.md +29 -0
- package/skills/supabase-webhooks-events/SKILL.md +412 -138
- package/skills/supabase-webhooks-events/references/errors.md +55 -0
- package/skills/supabase-webhooks-events/references/event-handler-pattern.md +106 -0
- package/skills/supabase-webhooks-events/references/examples.md +133 -0
- package/skills/supabase-webhooks-events/references/signature-verification.md +165 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Capacity Planning
|
|
2
|
+
|
|
3
|
+
## Capacity Planning
|
|
4
|
+
|
|
5
|
+
### Metrics to Monitor
|
|
6
|
+
|
|
7
|
+
| Metric | Warning | Critical |
|
|
8
|
+
|--------|---------|----------|
|
|
9
|
+
| CPU Utilization | > 70% | > 85% |
|
|
10
|
+
| Memory Usage | > 75% | > 90% |
|
|
11
|
+
| Request Queue Depth | > 100 | > 500 |
|
|
12
|
+
| Error Rate | > 1% | > 5% |
|
|
13
|
+
| P95 Latency | > 500ms | > 2000ms |
|
|
14
|
+
|
|
15
|
+
### Capacity Calculation
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
interface CapacityEstimate {
|
|
19
|
+
currentRPS: number;
|
|
20
|
+
maxRPS: number;
|
|
21
|
+
headroom: number;
|
|
22
|
+
scaleRecommendation: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function estimateSupabaseCapacity(
|
|
26
|
+
metrics: SystemMetrics
|
|
27
|
+
): CapacityEstimate {
|
|
28
|
+
const currentRPS = metrics.requestsPerSecond;
|
|
29
|
+
const avgLatency = metrics.p50Latency;
|
|
30
|
+
const cpuUtilization = metrics.cpuPercent;
|
|
31
|
+
|
|
32
|
+
// Estimate max RPS based on current performance
|
|
33
|
+
const maxRPS = currentRPS / (cpuUtilization / 100) * 0.7; // 70% target
|
|
34
|
+
const headroom = ((maxRPS - currentRPS) / currentRPS) * 100;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
currentRPS,
|
|
38
|
+
maxRPS: Math.floor(maxRPS),
|
|
39
|
+
headroom: Math.round(headroom),
|
|
40
|
+
scaleRecommendation: headroom < 30
|
|
41
|
+
? 'Scale up soon'
|
|
42
|
+
: headroom < 50
|
|
43
|
+
? 'Monitor closely'
|
|
44
|
+
: 'Adequate capacity',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Error Handling Reference
|
|
2
|
+
|
|
3
|
+
| Issue | Cause | Solution |
|
|
4
|
+
|-------|-------|----------|
|
|
5
|
+
| k6 timeout | Rate limited | Reduce RPS |
|
|
6
|
+
| HPA not scaling | Wrong metrics | Verify metric name |
|
|
7
|
+
| Connection refused | Pool exhausted | Increase pool size |
|
|
8
|
+
| Inconsistent results | Warm-up needed | Add ramp-up phase |
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
## Examples
|
|
2
|
+
|
|
3
|
+
### Quick k6 Test
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
k6 run --vus 10 --duration 30s supabase-load-test.js
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
### Check Current Capacity
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const metrics = await getSystemMetrics();
|
|
13
|
+
const capacity = estimateSupabaseCapacity(metrics);
|
|
14
|
+
console.log('Headroom:', capacity.headroom + '%');
|
|
15
|
+
console.log('Recommendation:', capacity.scaleRecommendation);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Scale HPA Manually
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
kubectl scale deployment supabase-integration --replicas=5
|
|
22
|
+
kubectl get hpa supabase-integration-hpa
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Load Testing With K6
|
|
2
|
+
|
|
3
|
+
## Load Testing with k6
|
|
4
|
+
|
|
5
|
+
### Basic Load Test
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// supabase-load-test.js
|
|
9
|
+
import http from 'k6/http';
|
|
10
|
+
import { check, sleep } from 'k6';
|
|
11
|
+
|
|
12
|
+
export const options = {
|
|
13
|
+
stages: [
|
|
14
|
+
{ duration: '2m', target: 10 }, // Ramp up
|
|
15
|
+
{ duration: '5m', target: 10 }, // Steady state
|
|
16
|
+
{ duration: '2m', target: 50 }, // Ramp to peak
|
|
17
|
+
{ duration: '5m', target: 50 }, // Stress test
|
|
18
|
+
{ duration: '2m', target: 0 }, // Ramp down
|
|
19
|
+
],
|
|
20
|
+
thresholds: {
|
|
21
|
+
http_req_duration: ['p(95)<200'],
|
|
22
|
+
http_req_failed: ['rate<0.01'],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default function () {
|
|
27
|
+
const response = http.post(
|
|
28
|
+
'https://api.supabase.com/v1/resource',
|
|
29
|
+
JSON.stringify({ test: true }),
|
|
30
|
+
{
|
|
31
|
+
headers: {
|
|
32
|
+
'Content-Type': 'application/json',
|
|
33
|
+
'Authorization': `Bearer ${__ENV.SUPABASE_API_KEY}`,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
check(response, {
|
|
39
|
+
'status is 200': (r) => r.status === 200,
|
|
40
|
+
'latency < 200ms': (r) => r.timings.duration < 200,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
sleep(1);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Run Load Test
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Install k6
|
|
51
|
+
brew install k6 # macOS
|
|
52
|
+
# or: sudo apt install k6 # Linux
|
|
53
|
+
|
|
54
|
+
# Run test
|
|
55
|
+
k6 run --env SUPABASE_API_KEY=${SUPABASE_API_KEY} supabase-load-test.js
|
|
56
|
+
|
|
57
|
+
# Run with output to InfluxDB
|
|
58
|
+
k6 run --out influxdb=http://localhost:8086/k6 supabase-load-test.js
|
|
59
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Scaling Patterns
|
|
2
|
+
|
|
3
|
+
## Scaling Patterns
|
|
4
|
+
|
|
5
|
+
### Horizontal Scaling
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
# kubernetes HPA
|
|
9
|
+
apiVersion: autoscaling/v2
|
|
10
|
+
kind: HorizontalPodAutoscaler
|
|
11
|
+
metadata:
|
|
12
|
+
name: supabase-integration-hpa
|
|
13
|
+
spec:
|
|
14
|
+
scaleTargetRef:
|
|
15
|
+
apiVersion: apps/v1
|
|
16
|
+
kind: Deployment
|
|
17
|
+
name: supabase-integration
|
|
18
|
+
minReplicas: 2
|
|
19
|
+
maxReplicas: 20
|
|
20
|
+
metrics:
|
|
21
|
+
- type: Resource
|
|
22
|
+
resource:
|
|
23
|
+
name: cpu
|
|
24
|
+
target:
|
|
25
|
+
type: Utilization
|
|
26
|
+
averageUtilization: 70
|
|
27
|
+
- type: Pods
|
|
28
|
+
pods:
|
|
29
|
+
metric:
|
|
30
|
+
name: supabase_queue_depth
|
|
31
|
+
target:
|
|
32
|
+
type: AverageValue
|
|
33
|
+
averageValue: 100
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Connection Pooling
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { Pool } from 'generic-pool';
|
|
40
|
+
|
|
41
|
+
const supabasePool = Pool.create({
|
|
42
|
+
create: async () => {
|
|
43
|
+
return new SupabaseClient({
|
|
44
|
+
apiKey: process.env.SUPABASE_API_KEY!,
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
destroy: async (client) => {
|
|
48
|
+
await client.close();
|
|
49
|
+
},
|
|
50
|
+
max: 20,
|
|
51
|
+
min: 5,
|
|
52
|
+
idleTimeoutMillis: 30000,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
async function withSupabaseClient<T>(
|
|
56
|
+
fn: (client: SupabaseClient) => Promise<T>
|
|
57
|
+
): Promise<T> {
|
|
58
|
+
const client = await supabasePool.acquire();
|
|
59
|
+
try {
|
|
60
|
+
return await fn(client);
|
|
61
|
+
} finally {
|
|
62
|
+
supabasePool.release(client);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
## Database Table Partitioning
|
|
2
|
+
|
|
3
|
+
For tables with millions/billions of rows, partitioning splits data into smaller physical chunks. Supabase supports PostgreSQL native partitioning (range, list, hash). Queries that include the partition key only scan relevant partitions.
|
|
4
|
+
|
|
5
|
+
### Range Partitioning by Date (Most Common)
|
|
6
|
+
|
|
7
|
+
```sql
|
|
8
|
+
-- Create the partitioned parent table
|
|
9
|
+
CREATE TABLE public.events (
|
|
10
|
+
id bigint GENERATED ALWAYS AS IDENTITY,
|
|
11
|
+
org_id uuid NOT NULL REFERENCES public.organizations(id),
|
|
12
|
+
event_type text NOT NULL,
|
|
13
|
+
payload jsonb,
|
|
14
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
15
|
+
PRIMARY KEY (id, created_at) -- partition key must be in PK
|
|
16
|
+
) PARTITION BY RANGE (created_at);
|
|
17
|
+
|
|
18
|
+
-- Create monthly partitions
|
|
19
|
+
CREATE TABLE public.events_2025_01 PARTITION OF public.events
|
|
20
|
+
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
|
|
21
|
+
CREATE TABLE public.events_2025_02 PARTITION OF public.events
|
|
22
|
+
FOR VALUES FROM ('2025-02-01') TO ('2025-03-01');
|
|
23
|
+
CREATE TABLE public.events_2025_03 PARTITION OF public.events
|
|
24
|
+
FOR VALUES FROM ('2025-03-01') TO ('2025-04-01');
|
|
25
|
+
-- ... create partitions for each month
|
|
26
|
+
|
|
27
|
+
-- Default partition catches anything that doesn't match
|
|
28
|
+
CREATE TABLE public.events_default PARTITION OF public.events DEFAULT;
|
|
29
|
+
|
|
30
|
+
-- Index each partition (PostgreSQL auto-creates on child tables from parent index)
|
|
31
|
+
CREATE INDEX idx_events_org_created ON public.events (org_id, created_at);
|
|
32
|
+
CREATE INDEX idx_events_type ON public.events (event_type);
|
|
33
|
+
|
|
34
|
+
-- Enable RLS on the parent table (policies apply to all partitions)
|
|
35
|
+
ALTER TABLE public.events ENABLE ROW LEVEL SECURITY;
|
|
36
|
+
|
|
37
|
+
CREATE POLICY "Users read own org events" ON public.events
|
|
38
|
+
FOR SELECT USING (
|
|
39
|
+
org_id IN (
|
|
40
|
+
SELECT org_id FROM public.org_members WHERE user_id = auth.uid()
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Automate Partition Creation
|
|
46
|
+
|
|
47
|
+
```sql
|
|
48
|
+
-- Function to auto-create next month's partition
|
|
49
|
+
CREATE OR REPLACE FUNCTION public.create_monthly_partition()
|
|
50
|
+
RETURNS void AS $$
|
|
51
|
+
DECLARE
|
|
52
|
+
next_month date := date_trunc('month', now() + interval '1 month');
|
|
53
|
+
partition_name text;
|
|
54
|
+
start_date text;
|
|
55
|
+
end_date text;
|
|
56
|
+
BEGIN
|
|
57
|
+
partition_name := 'events_' || to_char(next_month, 'YYYY_MM');
|
|
58
|
+
start_date := to_char(next_month, 'YYYY-MM-DD');
|
|
59
|
+
end_date := to_char(next_month + interval '1 month', 'YYYY-MM-DD');
|
|
60
|
+
|
|
61
|
+
-- Skip if partition already exists
|
|
62
|
+
IF NOT EXISTS (
|
|
63
|
+
SELECT 1 FROM pg_class WHERE relname = partition_name
|
|
64
|
+
) THEN
|
|
65
|
+
EXECUTE format(
|
|
66
|
+
'CREATE TABLE public.%I PARTITION OF public.events FOR VALUES FROM (%L) TO (%L)',
|
|
67
|
+
partition_name, start_date, end_date
|
|
68
|
+
);
|
|
69
|
+
RAISE NOTICE 'Created partition: %', partition_name;
|
|
70
|
+
END IF;
|
|
71
|
+
END;
|
|
72
|
+
$$ LANGUAGE plpgsql;
|
|
73
|
+
|
|
74
|
+
-- Schedule via pg_cron (runs on the 25th of each month)
|
|
75
|
+
SELECT cron.schedule(
|
|
76
|
+
'create-events-partition',
|
|
77
|
+
'0 0 25 * *',
|
|
78
|
+
$$SELECT public.create_monthly_partition()$$
|
|
79
|
+
);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Query Partitioned Tables from the SDK
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { createClient } from '@supabase/supabase-js'
|
|
86
|
+
import type { Database } from './database.types'
|
|
87
|
+
|
|
88
|
+
const supabase = createClient<Database>(
|
|
89
|
+
process.env.SUPABASE_URL!,
|
|
90
|
+
process.env.SUPABASE_ANON_KEY!
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
// IMPORTANT: Always include the partition key in your WHERE clause
|
|
94
|
+
// Without it, Postgres scans ALL partitions (defeats the purpose)
|
|
95
|
+
|
|
96
|
+
// GOOD: partition key (created_at) in the filter — scans only 1 partition
|
|
97
|
+
async function getRecentEvents(orgId: string) {
|
|
98
|
+
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()
|
|
99
|
+
|
|
100
|
+
const { data, error } = await supabase
|
|
101
|
+
.from('events')
|
|
102
|
+
.select('id, event_type, payload, created_at')
|
|
103
|
+
.eq('org_id', orgId)
|
|
104
|
+
.gte('created_at', thirtyDaysAgo) // <-- partition key filter
|
|
105
|
+
.order('created_at', { ascending: false })
|
|
106
|
+
.limit(100)
|
|
107
|
+
|
|
108
|
+
if (error) throw new Error(`Events query failed: ${error.message}`)
|
|
109
|
+
return data
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// BAD: no partition key — scans every partition
|
|
113
|
+
async function getAllEvents(orgId: string) {
|
|
114
|
+
const { data, error } = await supabase
|
|
115
|
+
.from('events')
|
|
116
|
+
.select('id, event_type, payload, created_at')
|
|
117
|
+
.eq('org_id', orgId) // no created_at filter = full table scan across all partitions
|
|
118
|
+
// This query gets slower as more partitions are added
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Verify partition pruning with EXPLAIN
|
|
122
|
+
// Run in SQL Editor:
|
|
123
|
+
// EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM events WHERE org_id = '...' AND created_at > '2025-03-01';
|
|
124
|
+
// Look for "Partitions removed: N" in the output
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Drop Old Partitions (Data Retention)
|
|
128
|
+
|
|
129
|
+
```sql
|
|
130
|
+
-- Drop partitions older than 12 months (instant, no vacuum needed)
|
|
131
|
+
CREATE OR REPLACE FUNCTION public.drop_old_partitions(retention_months int DEFAULT 12)
|
|
132
|
+
RETURNS void AS $$
|
|
133
|
+
DECLARE
|
|
134
|
+
cutoff date := date_trunc('month', now() - (retention_months || ' months')::interval);
|
|
135
|
+
partition record;
|
|
136
|
+
BEGIN
|
|
137
|
+
FOR partition IN
|
|
138
|
+
SELECT inhrelid::regclass::text AS name
|
|
139
|
+
FROM pg_inherits
|
|
140
|
+
WHERE inhparent = 'public.events'::regclass
|
|
141
|
+
AND inhrelid::regclass::text ~ 'events_\d{4}_\d{2}'
|
|
142
|
+
LOOP
|
|
143
|
+
-- Extract date from partition name and compare
|
|
144
|
+
IF substring(partition.name FROM 'events_(\d{4}_\d{2})')::date < cutoff THEN
|
|
145
|
+
EXECUTE format('DROP TABLE %s', partition.name);
|
|
146
|
+
RAISE NOTICE 'Dropped old partition: %', partition.name;
|
|
147
|
+
END IF;
|
|
148
|
+
END LOOP;
|
|
149
|
+
END;
|
|
150
|
+
$$ LANGUAGE plpgsql;
|
|
151
|
+
|
|
152
|
+
-- Schedule monthly cleanup
|
|
153
|
+
SELECT cron.schedule('drop-old-partitions', '0 2 1 * *',
|
|
154
|
+
$$SELECT public.drop_old_partitions(12)$$
|
|
155
|
+
);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Output
|
|
159
|
+
|
|
160
|
+
- Read replica client configured for analytics/dashboard queries, primary for writes
|
|
161
|
+
- Connection pooling mode selected (transaction vs session) with correct port
|
|
162
|
+
- Compute tier matched to traffic requirements
|
|
163
|
+
- Storage uploads optimized with CDN cache headers and image transforms
|
|
164
|
+
- Edge Functions deployed to the region closest to users
|
|
165
|
+
- Large tables partitioned by date range with automated partition management
|
|
166
|
+
- Data retention policy via partition drops
|
|
167
|
+
|
|
168
|
+
## Error Handling
|
|
169
|
+
|
|
170
|
+
| Issue | Cause | Solution |
|
|
171
|
+
|-------|-------|----------|
|
|
172
|
+
| `too many connections for role` | Exceeded Supavisor pool limit | Use transaction mode (port 6543), reduce idle connections, upgrade compute |
|
|
173
|
+
| Read replica returns stale data | Replication lag (typically <100ms) | Do not read-after-write on replica; use primary for consistency-critical reads |
|
|
174
|
+
| `no partition of relation "events" found for row` | Insert date outside any partition range | Create a DEFAULT partition or pre-create future partitions |
|
|
175
|
+
| Storage CDN returns old file | Cached at edge | Use content-addressed paths (`hash.ext`) or set shorter `cacheControl` |
|
|
176
|
+
| Edge Function cold start | First request to a region | Use `keep-alive` cron ping or accept ~200ms cold start |
|
|
177
|
+
| `prepared statement already exists` | Transaction mode doesn't support prepared statements | Switch to session mode (port 5432) or disable prepared statements in your ORM |
|
|
178
|
+
|
|
179
|
+
## Examples
|
|
180
|
+
|
|
181
|
+
### Quick Connection Pool Check
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Check how many connections are in use right now
|
|
185
|
+
psql "$DATABASE_URL" -c "SELECT count(*) AS active_connections FROM pg_stat_activity WHERE state = 'active';"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Switch an Existing Table to Partitioned
|
|
189
|
+
|
|
190
|
+
```sql
|
|
191
|
+
-- You cannot ALTER an existing table to be partitioned.
|
|
192
|
+
-- Instead: create partitioned table, migrate data, swap names.
|
|
193
|
+
|
|
194
|
+
-- 1. Create new partitioned table
|
|
195
|
+
CREATE TABLE public.events_partitioned (LIKE public.events INCLUDING ALL)
|
|
196
|
+
PARTITION BY RANGE (created_at);
|
|
197
|
+
|
|
198
|
+
-- 2. Create partitions for existing data range
|
|
199
|
+
CREATE TABLE public.events_p_2025_01 PARTITION OF public.events_partitioned
|
|
200
|
+
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
|
|
201
|
+
-- ... more partitions
|
|
202
|
+
|
|
203
|
+
-- 3. Copy data (do this during low traffic)
|
|
204
|
+
INSERT INTO public.events_partitioned SELECT * FROM public.events;
|
|
205
|
+
|
|
206
|
+
-- 4. Swap tables in a transaction
|
|
207
|
+
BEGIN;
|
|
208
|
+
ALTER TABLE public.events RENAME TO events_old;
|
|
209
|
+
ALTER TABLE public.events_partitioned RENAME TO events;
|
|
210
|
+
COMMIT;
|
|
211
|
+
|
|
212
|
+
-- 5. Verify, then drop old table
|
|
213
|
+
DROP TABLE public.events_old;
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Test Read Replica Lag
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { supabase, supabaseReadOnly } from '../lib/supabase'
|
|
220
|
+
|
|
221
|
+
async function measureReplicaLag() {
|
|
222
|
+
// Write to primary
|
|
223
|
+
const { data: written } = await supabase
|
|
224
|
+
.from('health_checks')
|
|
225
|
+
.insert({ timestamp: new Date().toISOString() })
|
|
226
|
+
.select('id, timestamp')
|
|
227
|
+
.single()
|
|
228
|
+
|
|
229
|
+
// Immediately read from replica
|
|
230
|
+
const start = Date.now()
|
|
231
|
+
let found = false
|
|
232
|
+
|
|
233
|
+
while (!found && Date.now() - start < 5000) {
|
|
234
|
+
const { data } = await supabaseReadOnly
|
|
235
|
+
.from('health_checks')
|
|
236
|
+
.select('id')
|
|
237
|
+
.eq('id', written!.id)
|
|
238
|
+
.maybeSingle()
|
|
239
|
+
|
|
240
|
+
if (data) {
|
|
241
|
+
found = true
|
|
242
|
+
console.log(`Replica lag: ${Date.now() - start}ms`)
|
|
243
|
+
} else {
|
|
244
|
+
await new Promise(r => setTimeout(r, 10))
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!found) console.warn('Replica lag exceeds 5 seconds')
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Resources
|
|
253
|
+
|
|
254
|
+
- [Supabase Read Replicas](https://supabase.com/docs/guides/platform/read-replicas)
|
|
255
|
+
- [Supabase Connection Pooling (Supavisor)](https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pooler)
|
|
256
|
+
- [Supabase Compute Add-ons](https://supabase.com/docs/guides/platform/compute-add-ons)
|
|
257
|
+
- [Supabase Storage CDN](https://supabase.com/docs/guides/storage/cdn/fundamentals)
|
|
258
|
+
- [Supabase Edge Functions](https://supabase.com/docs/guides/functions)
|
|
259
|
+
- [PostgreSQL Table Partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html)
|
|
260
|
+
|
|
261
|
+
## Next Steps
|
|
262
|
+
|
|
263
|
+
For reliability patterns (circuit breakers, offline queues, graceful degradation), see `supabase-reliability-patterns`.
|