@pageai/ralph-loop 1.8.0 → 1.10.0
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/.agent/PROMPT.md +3 -2
- package/.agents/skills/mysql/SKILL.md +81 -0
- package/.agents/skills/mysql/references/character-sets.md +66 -0
- package/.agents/skills/mysql/references/composite-indexes.md +59 -0
- package/.agents/skills/mysql/references/connection-management.md +70 -0
- package/.agents/skills/mysql/references/covering-indexes.md +47 -0
- package/.agents/skills/mysql/references/data-types.md +69 -0
- package/.agents/skills/mysql/references/deadlocks.md +72 -0
- package/.agents/skills/mysql/references/explain-analysis.md +66 -0
- package/.agents/skills/mysql/references/fulltext-indexes.md +28 -0
- package/.agents/skills/mysql/references/index-maintenance.md +110 -0
- package/.agents/skills/mysql/references/isolation-levels.md +49 -0
- package/.agents/skills/mysql/references/json-column-patterns.md +77 -0
- package/.agents/skills/mysql/references/n-plus-one.md +77 -0
- package/.agents/skills/mysql/references/online-ddl.md +53 -0
- package/.agents/skills/mysql/references/partitioning.md +92 -0
- package/.agents/skills/mysql/references/primary-keys.md +70 -0
- package/.agents/skills/mysql/references/query-optimization-pitfalls.md +117 -0
- package/.agents/skills/mysql/references/replication-lag.md +46 -0
- package/.agents/skills/mysql/references/row-locking-gotchas.md +63 -0
- package/.agents/skills/postgres/SKILL.md +46 -0
- package/.agents/skills/postgres/references/backup-recovery.md +41 -0
- package/.agents/skills/postgres/references/index-optimization.md +69 -0
- package/.agents/skills/postgres/references/indexing.md +61 -0
- package/.agents/skills/postgres/references/memory-management-ops.md +39 -0
- package/.agents/skills/postgres/references/monitoring.md +59 -0
- package/.agents/skills/postgres/references/mvcc-transactions.md +38 -0
- package/.agents/skills/postgres/references/mvcc-vacuum.md +41 -0
- package/.agents/skills/postgres/references/optimization-checklist.md +19 -0
- package/.agents/skills/postgres/references/partitioning.md +79 -0
- package/.agents/skills/postgres/references/process-architecture.md +46 -0
- package/.agents/skills/postgres/references/ps-cli-api-insights.md +53 -0
- package/.agents/skills/postgres/references/ps-cli-commands.md +72 -0
- package/.agents/skills/postgres/references/ps-connection-pooling.md +72 -0
- package/.agents/skills/postgres/references/ps-connections.md +37 -0
- package/.agents/skills/postgres/references/ps-extensions.md +27 -0
- package/.agents/skills/postgres/references/ps-insights.md +62 -0
- package/.agents/skills/postgres/references/query-patterns.md +80 -0
- package/.agents/skills/postgres/references/replication.md +49 -0
- package/.agents/skills/postgres/references/schema-design.md +66 -0
- package/.agents/skills/postgres/references/storage-layout.md +41 -0
- package/.agents/skills/postgres/references/wal-operations.md +42 -0
- package/README.md +2 -2
- package/bin/cli.js +3 -1
- package/bin/lib/shadcn.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Replication Lag Awareness
|
|
3
|
+
description: Read-replica consistency pitfalls and mitigations
|
|
4
|
+
tags: mysql, replication, lag, read-replicas, consistency, gtid
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Replication Lag
|
|
8
|
+
|
|
9
|
+
MySQL replication is asynchronous by default. Reads from a replica may return stale data.
|
|
10
|
+
|
|
11
|
+
## The Core Problem
|
|
12
|
+
1. App writes to primary: `INSERT INTO orders ...`
|
|
13
|
+
2. App immediately reads from replica: `SELECT * FROM orders WHERE id = ?`
|
|
14
|
+
3. Replica hasn't applied the write yet — returns empty or stale data.
|
|
15
|
+
|
|
16
|
+
## Detecting Lag
|
|
17
|
+
```sql
|
|
18
|
+
-- On the replica
|
|
19
|
+
SHOW REPLICA STATUS\G
|
|
20
|
+
-- Key field: Seconds_Behind_Source (0 = caught up, NULL = not replicating)
|
|
21
|
+
```
|
|
22
|
+
**Warning**: `Seconds_Behind_Source` measures relay-log lag, not true wall-clock staleness. It can underreport during long-running transactions because it only updates when transactions commit.
|
|
23
|
+
|
|
24
|
+
**GTID-based lag**: for more accurate tracking, compare `@@global.gtid_executed` (replica) to primary GTID position, or use `WAIT_FOR_EXECUTED_GTID_SET()` to wait for a specific transaction.
|
|
25
|
+
|
|
26
|
+
**Note**: parallel replication with `replica_parallel_type=LOGICAL_CLOCK` requires `binlog_format=ROW`. Statement-based replication (`binlog_format=STATEMENT`) is more limited for parallel apply.
|
|
27
|
+
|
|
28
|
+
## Mitigation Strategies
|
|
29
|
+
|
|
30
|
+
| Strategy | How | Trade-off |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| **Read from primary** | Route critical reads to primary after writes | Increases primary load |
|
|
33
|
+
| **Sticky sessions** | Pin user to primary for N seconds after a write | Adds session affinity complexity |
|
|
34
|
+
| **GTID wait** | `SELECT WAIT_FOR_EXECUTED_GTID_SET('gtid', timeout)` on replica | Adds latency equal to lag |
|
|
35
|
+
| **Semi-sync replication** | Primary waits for >=1 replica ACK before committing | Higher write latency |
|
|
36
|
+
|
|
37
|
+
## Common Pitfalls
|
|
38
|
+
- **Large transactions cause lag spikes**: A single `INSERT ... SELECT` of 1M rows replays as one big transaction on the replica. Break into batches.
|
|
39
|
+
- **DDL blocks replication**: `ALTER TABLE` with `ALGORITHM=COPY` on primary replays on replica, blocking other relay-log events during execution. `INSTANT` and `INPLACE` DDL are less blocking but still require brief metadata locks.
|
|
40
|
+
- **Long queries on replica**: A slow `SELECT` on the replica can block relay-log application. Use `replica_parallel_workers` (8.0+) with `replica_parallel_type=LOGICAL_CLOCK` for parallel apply. Note: LOGICAL_CLOCK requires `binlog_format=ROW` and `slave_preserve_commit_order=ON` (or `replica_preserve_commit_order=ON`) to preserve commit order.
|
|
41
|
+
- **IO thread bottlenecks**: Network latency, disk I/O, or `relay_log_space_limit` exhaustion can cause lag even when the SQL apply thread isn't saturated. Monitor `Relay_Log_Space` and connectivity.
|
|
42
|
+
|
|
43
|
+
## Guidelines
|
|
44
|
+
- Assume replicas are always slightly behind. Design reads accordingly.
|
|
45
|
+
- Use GTID-based replication for reliable failover and lag tracking.
|
|
46
|
+
- Monitor `Seconds_Behind_Source` with alerting (>5s warrants investigation).
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: InnoDB Row Locking Gotchas
|
|
3
|
+
description: Gap locks, next-key locks, and surprise escalation
|
|
4
|
+
tags: mysql, innodb, locking, gap-locks, next-key-locks, concurrency
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Row Locking Gotchas
|
|
8
|
+
|
|
9
|
+
InnoDB uses row-level locking, but the actual locked range is often wider than expected.
|
|
10
|
+
|
|
11
|
+
## Next-Key Locks (REPEATABLE READ)
|
|
12
|
+
InnoDB's default isolation level uses next-key locks for **locking reads** (`SELECT ... FOR UPDATE`, `SELECT ... FOR SHARE`, `UPDATE`, `DELETE`) to prevent phantom reads. A range scan locks every gap in that range. Plain `SELECT` statements use consistent reads (MVCC) and don't acquire locks.
|
|
13
|
+
|
|
14
|
+
**Exception**: a unique index search with a unique search condition (e.g., `WHERE id = 5` on a unique `id`) locks only the index record, not the gap. Gap/next-key locks still apply for range scans and non-unique searches.
|
|
15
|
+
|
|
16
|
+
```sql
|
|
17
|
+
-- Locks rows with id 5..10 AND the gaps between them and after the range
|
|
18
|
+
SELECT * FROM orders WHERE id BETWEEN 5 AND 10 FOR UPDATE;
|
|
19
|
+
-- Another session inserting id=7 blocks until the lock is released.
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Gap Locks on Non-Existent Rows
|
|
23
|
+
`SELECT ... FOR UPDATE` on a row that doesn't exist still places a gap lock:
|
|
24
|
+
```sql
|
|
25
|
+
-- No row with id=999 exists, but this locks the gap around where 999 would be
|
|
26
|
+
SELECT * FROM orders WHERE id = 999 FOR UPDATE;
|
|
27
|
+
-- Concurrent INSERTs into that gap are blocked.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Index-Less UPDATE/DELETE = Full Scan and Broad Locking
|
|
31
|
+
If the WHERE column has no index, InnoDB must scan all rows and locks every row examined (often effectively all rows in the table). This is not table-level locking—InnoDB doesn't escalate locks—but rather row-level locks on all rows:
|
|
32
|
+
```sql
|
|
33
|
+
-- No index on status → locks all rows (not a table lock, but all row locks)
|
|
34
|
+
UPDATE orders SET processed = 1 WHERE status = 'pending';
|
|
35
|
+
-- Fix: CREATE INDEX idx_status ON orders (status);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## SELECT ... FOR SHARE (Shared Locks)
|
|
39
|
+
`SELECT ... FOR SHARE` acquires shared (S) locks instead of exclusive (X) locks. Multiple sessions can hold shared locks simultaneously, but exclusive locks are blocked:
|
|
40
|
+
|
|
41
|
+
```sql
|
|
42
|
+
-- Session 1: shared lock
|
|
43
|
+
SELECT * FROM orders WHERE id = 5 FOR SHARE;
|
|
44
|
+
|
|
45
|
+
-- Session 2: also allowed (shared lock)
|
|
46
|
+
SELECT * FROM orders WHERE id = 5 FOR SHARE;
|
|
47
|
+
|
|
48
|
+
-- Session 3: blocked until shared locks are released
|
|
49
|
+
UPDATE orders SET status = 'processed' WHERE id = 5;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Gap/next-key locks can still apply in REPEATABLE READ, so inserts into locked gaps may be blocked even with shared locks.
|
|
53
|
+
|
|
54
|
+
## INSERT ... ON DUPLICATE KEY UPDATE
|
|
55
|
+
Takes an exclusive next-key lock on the index entry. If multiple sessions do this concurrently on nearby key values, gap-lock deadlocks are common.
|
|
56
|
+
|
|
57
|
+
## Lock Escalation Misconception
|
|
58
|
+
InnoDB does **not** automatically escalate row locks to table locks. When a missing index causes "table-wide" locking, it's because InnoDB scans and locks all rows individually—not because locks were escalated.
|
|
59
|
+
|
|
60
|
+
## Mitigation Strategies
|
|
61
|
+
- **Use READ COMMITTED** when gap locks cause excessive blocking (gap locks disabled in RC except for FK/duplicate-key checks).
|
|
62
|
+
- **Keep transactions short** — hold locks for milliseconds, not seconds.
|
|
63
|
+
- **Ensure WHERE columns are indexed** to avoid full-table lock scans.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: postgres
|
|
3
|
+
description: PostgreSQL best practices, query optimization, connection troubleshooting, and performance improvement. Load when working with Postgres databases.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: planetscale
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# PlanetScale Postgres
|
|
11
|
+
|
|
12
|
+
## Generic Postgres
|
|
13
|
+
|
|
14
|
+
| Topic | Reference | Use for |
|
|
15
|
+
| ---------------------- | ---------------------------------------------------------------- | --------------------------------------------------------- |
|
|
16
|
+
| Schema Design | [references/schema-design.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/schema-design.md) | Tables, primary keys, data types, foreign keys |
|
|
17
|
+
| Indexing | [references/indexing.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/indexing.md) | Index types, composite indexes, performance |
|
|
18
|
+
| Index Optimization | [references/index-optimization.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/index-optimization.md) | Unused/duplicate index queries, index audit |
|
|
19
|
+
| Partitioning | [references/partitioning.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/partitioning.md) | Large tables, time-series, data retention |
|
|
20
|
+
| Query Patterns | [references/query-patterns.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/query-patterns.md) | SQL anti-patterns, JOINs, pagination, batch queries |
|
|
21
|
+
| Optimization Checklist | [references/optimization-checklist.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/optimization-checklist.md) | Pre-optimization audit, cleanup, readiness checks |
|
|
22
|
+
| MVCC and VACUUM | [references/mvcc-vacuum.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/mvcc-vacuum.md) | Dead tuples, long transactions, xid wraparound prevention |
|
|
23
|
+
|
|
24
|
+
## Operations and Architecture
|
|
25
|
+
|
|
26
|
+
| Topic | Reference | Use for |
|
|
27
|
+
| ---------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------- |
|
|
28
|
+
| Process Architecture | [references/process-architecture.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/process-architecture.md) | Multi-process model, connection pooling, auxiliary processes |
|
|
29
|
+
| Memory Architecture | [references/memory-management-ops.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/memory-management-ops.md) | Shared/private memory layout, OS page cache, OOM prevention |
|
|
30
|
+
| MVCC Transactions | [references/mvcc-transactions.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/mvcc-transactions.md) | Isolation levels, XID wraparound, serialization errors |
|
|
31
|
+
| WAL and Checkpoints | [references/wal-operations.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/wal-operations.md) | WAL internals, checkpoint tuning, durability, crash recovery |
|
|
32
|
+
| Replication | [references/replication.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/replication.md) | Streaming replication, slots, sync commit, failover |
|
|
33
|
+
| Storage Layout | [references/storage-layout.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/storage-layout.md) | PGDATA structure, TOAST, fillfactor, tablespaces, disk mgmt |
|
|
34
|
+
| Monitoring | [references/monitoring.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/monitoring.md) | pg_stat views, logging, pg_stat_statements, host metrics |
|
|
35
|
+
| Backup and Recovery | [references/backup-recovery.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/backup-recovery.md) | pg_dump, pg_basebackup, PITR, WAL archiving, backup tools |
|
|
36
|
+
|
|
37
|
+
## PlanetScale-Specific
|
|
38
|
+
|
|
39
|
+
| Topic | Reference | Use for |
|
|
40
|
+
| ------------------ | ---------------------------------------------------------------------------- | ----------------------------------------------------- |
|
|
41
|
+
| Connection Pooling | [references/ps-connection-pooling.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/ps-connection-pooling.md) | PgBouncer, pool sizing, pooled vs direct |
|
|
42
|
+
| Extensions | [references/ps-extensions.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/ps-extensions.md) | Supported extensions, compatibility |
|
|
43
|
+
| Connections | [references/ps-connections.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/ps-connections.md) | Connection troubleshooting, drivers, SSL |
|
|
44
|
+
| Insights | [references/ps-insights.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/ps-insights.md) | Slow queries, MCP server, pscale CLI |
|
|
45
|
+
| CLI Commands | [references/ps-cli-commands.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/ps-cli-commands.md) | pscale CLI reference, branches, deploy requests, auth |
|
|
46
|
+
| CLI API Insights | [references/ps-cli-api-insights.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/ps-cli-api-insights.md) | Query insights via `pscale api`, schema analysis |
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Backup and Recovery
|
|
3
|
+
description: Logical/physical backups, PITR, WAL archiving, backup tools, and recovery strategies
|
|
4
|
+
tags: postgres, backup, recovery, pitr, pg_dump, pg_basebackup, wal-archiving, operations
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Backup and Recovery
|
|
8
|
+
|
|
9
|
+
**FUNDAMENTAL RULE: Backups are useless until you've successfully tested recovery.**
|
|
10
|
+
|
|
11
|
+
## Logical Backups (pg_dump)
|
|
12
|
+
Exports as SQL or custom format; portable across PG versions and architectures. Formats: `-Fp` (plain SQL), `-Fc` (custom compressed, selective restore), `-Fd` (directory, parallel with `-j`), `-Ft` (tar, avoid). Use `-Fd -j 4` for large DBs. Restore: `pg_restore -d dbname file.dump`; add `-j` for parallel restore. Selective table restore: `pg_restore -t tablename`. Slow for large DBs; RPO = backup frequency (typically 24h).
|
|
13
|
+
|
|
14
|
+
## Physical Backups (pg_basebackup)
|
|
15
|
+
Copies raw PGDATA; same major version and platform required; cross-architecture works if same endianness (e.g., x86_64 ↔ ARM64). Faster for large clusters; includes all databases. Flags: `-Ft -z -P` for compressed tar with progress. Manual alternative: `pg_backup_start()` → copy PGDATA → `pg_backup_stop()` (complex; must write returned `backup_label`).
|
|
16
|
+
|
|
17
|
+
## PITR (Point-in-Time Recovery)
|
|
18
|
+
Requires base backup + continuous WAL archiving. Restores to any timestamp, transaction, or named restore point. Without PITR: restore only to backup time (potentially lose hours). With PITR: RPO = minutes. `archive_command` must return 0 ONLY when file is safely stored—premature 0 = data loss risk. `wal_level` must be `replica` or `logical` (not `minimal`).
|
|
19
|
+
|
|
20
|
+
## WAL Archiving
|
|
21
|
+
`archive_mode=on`, `archive_command='test ! -f /archive/%f && cp %p /archive/%f'`. **Test archive command as postgres user** (not root) since permission issues are common. Monitor `pg_stat_archiver` for `failed_count`, `last_archived_time`. Archive failures prevent WAL recycling → disk fills.
|
|
22
|
+
|
|
23
|
+
## Tool Comparison
|
|
24
|
+
| Tool | Use case |
|
|
25
|
+
|------|----------|
|
|
26
|
+
| pg_dump | Small DBs, migrations, selective restore |
|
|
27
|
+
| pg_basebackup | Basic PITR, built-in |
|
|
28
|
+
| pgBackRest | Production—parallel, incremental, S3/GCS/Azure, retention |
|
|
29
|
+
| Barman | Enterprise PITR, retention policies |
|
|
30
|
+
| WAL-G | Cloud-native, S3/GCS/Azure |
|
|
31
|
+
|
|
32
|
+
## RPO/RTO
|
|
33
|
+
Logical only: RPO = backup interval (hours); RTO = hours. PITR: RPO = minutes; RTO = hours. Synchronous replication: RPO = 0; RTO = seconds to minutes (failover).
|
|
34
|
+
|
|
35
|
+
## Operational Rules
|
|
36
|
+
- Verify integrity with `pg_verifybackup` (PG 13+)
|
|
37
|
+
- Test recovery / PITR regularly
|
|
38
|
+
- Take backups from standby to avoid impacting primary
|
|
39
|
+
- Retention: 7 daily, 4 weekly, 12 monthly
|
|
40
|
+
- Monitor archive growth and backup age
|
|
41
|
+
- **Never assume backups work without testing**
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Index Optimization Queries
|
|
3
|
+
description: Index audit queries
|
|
4
|
+
tags: postgres, indexes, unused-indexes, duplicate-indexes, optimization
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Index Optimization
|
|
8
|
+
|
|
9
|
+
## Identify Unused Indexes
|
|
10
|
+
|
|
11
|
+
Query to find unused indexes:
|
|
12
|
+
|
|
13
|
+
```sql
|
|
14
|
+
-- indexes with 0 scans (check pg_stat_reset / pg_postmaster_start_time first)
|
|
15
|
+
SELECT
|
|
16
|
+
s.schemaname,
|
|
17
|
+
s.relname AS table_name,
|
|
18
|
+
s.indexrelname AS index_name,
|
|
19
|
+
pg_size_pretty(pg_relation_size(s.indexrelid)) AS index_size
|
|
20
|
+
FROM pg_catalog.pg_stat_user_indexes s
|
|
21
|
+
JOIN pg_catalog.pg_index i ON s.indexrelid = i.indexrelid
|
|
22
|
+
WHERE s.idx_scan = 0
|
|
23
|
+
AND 0 <> ALL (i.indkey) -- exclude expression indexes
|
|
24
|
+
AND NOT i.indisunique -- exclude UNIQUE indexes
|
|
25
|
+
AND NOT EXISTS ( -- exclude constraint-backing indexes
|
|
26
|
+
SELECT 1 FROM pg_catalog.pg_constraint c
|
|
27
|
+
WHERE c.conindid = s.indexrelid
|
|
28
|
+
)
|
|
29
|
+
ORDER BY pg_relation_size(s.indexrelid) DESC;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Indexes Per Table Guidelines
|
|
33
|
+
|
|
34
|
+
- **< 5**: Normal
|
|
35
|
+
- **5-10**: Monitor (Verify necessity)
|
|
36
|
+
- **> 10**: Audit required (High write overhead)
|
|
37
|
+
|
|
38
|
+
```sql
|
|
39
|
+
SELECT relname AS table, count(*) as index_count
|
|
40
|
+
FROM pg_stat_user_indexes
|
|
41
|
+
GROUP BY relname
|
|
42
|
+
ORDER BY count(*) DESC;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Identify Unused Indexes
|
|
46
|
+
|
|
47
|
+
Indexes with identical definitions (after normalizing names) on the same table are duplicates:
|
|
48
|
+
|
|
49
|
+
```sql
|
|
50
|
+
SELECT
|
|
51
|
+
schemaname || '.' || tablename AS table,
|
|
52
|
+
array_agg(indexname) AS duplicate_indexes,
|
|
53
|
+
pg_size_pretty(sum(pg_relation_size((schemaname || '.' || indexname)::regclass))) AS total_size
|
|
54
|
+
FROM pg_indexes
|
|
55
|
+
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
|
|
56
|
+
GROUP BY schemaname, tablename,
|
|
57
|
+
regexp_replace(indexdef, 'INDEX \S+ ON ', 'INDEX ON ')
|
|
58
|
+
HAVING count(*) > 1;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Always confirm with a human before dropping or removing any indexes identified by the queries above.** Even indexes with 0 scans may be needed for infrequent but critical queries, and stats may have been reset recently.
|
|
62
|
+
|
|
63
|
+
## Per-table Index Count Guidelines
|
|
64
|
+
|
|
65
|
+
| Index Count | Recommendation |
|
|
66
|
+
| ----------- | ------------------------------------------- |
|
|
67
|
+
| <5 | Normal |
|
|
68
|
+
| 5-10 | Review for unused/duplicates |
|
|
69
|
+
| >10 | Audit required - significant write overhead |
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Indexing Best Practices
|
|
3
|
+
description: Index design guide
|
|
4
|
+
tags: postgres, indexes, composite, partial, covering, gin, brin
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Indexing Best Practices
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
1. **Always index foreign key columns** — PostgreSQL does not auto-create these
|
|
12
|
+
2. **Index columns in WHERE, JOIN, and ORDER BY** clauses
|
|
13
|
+
3. **Don't over-index** — each index slows writes and uses storage
|
|
14
|
+
4. **Verify with EXPLAIN ANALYZE** — confirm indexes are actually used
|
|
15
|
+
|
|
16
|
+
## Composite Indexes
|
|
17
|
+
|
|
18
|
+
Put equality columns first, then range/sort columns:
|
|
19
|
+
|
|
20
|
+
```sql
|
|
21
|
+
-- WHERE status = 'active' AND created_at > '2026-01-01'
|
|
22
|
+
CREATE INDEX order_status_created_idx ON order (status, created_at);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
A composite index on `(a, b)` supports queries on `a` + `b` and `a` alone, but not `b` alone.
|
|
26
|
+
|
|
27
|
+
## Partial Indexes
|
|
28
|
+
|
|
29
|
+
Reduce index size by filtering to common query patterns.
|
|
30
|
+
Only use if index size is problematic but the index is needed for performance.
|
|
31
|
+
|
|
32
|
+
```sql
|
|
33
|
+
CREATE INDEX order_active_idx ON order (customer_id)
|
|
34
|
+
WHERE status = 'active';
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Covering Indexes
|
|
38
|
+
|
|
39
|
+
Consider creating covering indexes for commonly executed query patterns that return only 1 or a small number of columns.
|
|
40
|
+
|
|
41
|
+
## Index Types
|
|
42
|
+
|
|
43
|
+
| Type | Use Case | Example |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| B-tree (default) | Equality, range, sorting | `WHERE id = 1`, `ORDER BY date` |
|
|
46
|
+
| GIN | Arrays, JSONB, full-text | `WHERE tags @> ARRAY['x']` |
|
|
47
|
+
| GiST | Geometric, range types, full-text | PostGIS, `tsrange`, `tsvector` |
|
|
48
|
+
| BRIN | Large sequential/time-series | Append-only logs, events (requires physical row order correlation) |
|
|
49
|
+
|
|
50
|
+
```sql
|
|
51
|
+
CREATE INDEX metadata_idx ON order USING GIN (metadata); -- JSONB
|
|
52
|
+
CREATE INDEX event_created_idx ON event USING BRIN (created_at); -- time-series
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Guidelines
|
|
56
|
+
|
|
57
|
+
- Name indexes consistently: `{table}_{column}_idx`
|
|
58
|
+
- Review for unused indexes periodically
|
|
59
|
+
- **Always confirm with a human before removing or dropping any indexes** — even unused ones may serve a purpose not reflected in recent stats
|
|
60
|
+
- Use partial indexes for frequently filtered subsets
|
|
61
|
+
- Use covering indexes on hot read paths
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Memory Architecture and OOM Prevention
|
|
3
|
+
description: PostgreSQL shared/private memory layout, OS page cache interaction, and OOM avoidance strategies
|
|
4
|
+
tags: postgres, memory, shared_buffers, work_mem, oom, architecture, operations
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Memory Architecture and OOM Prevention
|
|
8
|
+
|
|
9
|
+
## Memory Areas
|
|
10
|
+
|
|
11
|
+
- **Shared memory**: `shared_buffers` — main data cache, all processes, requires restart to change.
|
|
12
|
+
- **Private per backend**: `work_mem` (sorts/hashes/joins, per-operation); `maintenance_work_mem` (VACUUM, CREATE INDEX, ALTER TABLE ADD FOREIGN KEY); `temp_buffers` (8MB default).
|
|
13
|
+
- **Planner hint only**: `effective_cache_size` is NOT allocated — set to ~50–75% of total RAM.
|
|
14
|
+
- **Hash multiplier**: `hash_mem_multiplier` (default 2.0) means hash ops use up to 2× `work_mem`.
|
|
15
|
+
|
|
16
|
+
## Memory Multiplication Danger
|
|
17
|
+
|
|
18
|
+
Maximum potential: `work_mem × operations_per_query × (parallel_workers + 1) × connections` (leader participates by default via `parallel_leader_participation = on`; hash operations use up to `hash_mem_multiplier × work_mem`, default 2.0). Example: 128MB work_mem, 3 ops (2 sorts + 1 hash join), 2 parallel workers, 100 connections → 2 sorts at 128MB = 256MB, 1 hash join at 128MB × 2.0 = 256MB, per process = 512MB, × 3 processes (2 workers + leader) = 1536MB/query, × 100 connections = **~150GB** worst case. This case is rare.
|
|
19
|
+
Not all queries hit limits at once, but high concurrency + large datasets approach it. This is a common cause of OOM in containerized/Kubernetes deployments. Plan capacity with a 1.5–2× safety margin.
|
|
20
|
+
|
|
21
|
+
## OS Page Cache (Double Buffering)
|
|
22
|
+
|
|
23
|
+
Data exists in both `shared_buffers` and OS page cache. A miss in shared_buffers can still hit OS cache (avoiding disk I/O). Extremely large shared_buffers can hurt performance: less OS cache, slower startup, heavier checkpoints. Optimal split depends on workload (OLTP vs OLAP).
|
|
24
|
+
|
|
25
|
+
## OOM Prevention
|
|
26
|
+
|
|
27
|
+
- Implement connection pooling to reduce total backend count.
|
|
28
|
+
- Reduce `work_mem` globally; use per-session overrides for heavy queries only.
|
|
29
|
+
- Lower `max_parallel_workers_per_gather` in high-concurrency systems.
|
|
30
|
+
- Set `statement_timeout` to kill runaway queries.
|
|
31
|
+
- Monitor: `dmesg -T | grep "killed process"` and `temp_blks_written` in pg_stat_statements.
|
|
32
|
+
|
|
33
|
+
## Operational Rules
|
|
34
|
+
|
|
35
|
+
- Tune per-session first, global last.
|
|
36
|
+
- Suspect OOM when memory spikes during high concurrency, dashboards, or large batch jobs.
|
|
37
|
+
- Increase memory only after confirming spill behavior (`temp_blks_written > 0`).
|
|
38
|
+
- `maintenance_work_mem` can be set much higher (1–2GB) — fewer processes use it. Cap autovacuum with `autovacuum_work_mem` to avoid `autovacuum_max_workers × maintenance_work_mem` memory spikes.
|
|
39
|
+
- `shared_buffers` change requires full restart; `work_mem` is per-session changeable.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Monitoring
|
|
3
|
+
description: Essential PostgreSQL monitoring views, pg_stat_statements, logging, host metrics, and statistics management
|
|
4
|
+
tags: postgres, monitoring, pg_stat_statements, logging, pgbadger, metrics, operations
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Monitoring
|
|
8
|
+
|
|
9
|
+
## Essential Views
|
|
10
|
+
|
|
11
|
+
- **pg_stat_activity**: First stop when something is wrong — running queries, states, wait events, locks.
|
|
12
|
+
- **pg_stat_statements**: Execution stats for all SQL. Requires `shared_preload_libraries = 'pg_stat_statements'` and `CREATE EXTENSION pg_stat_statements`.
|
|
13
|
+
- **pg_stat_database**: Cache hit ratio, temp files, deadlocks, connections per database.
|
|
14
|
+
- **pg_stat_user_tables**: `seq_scan` vs `idx_scan`, dead tuples, last vacuum/analyze times.
|
|
15
|
+
- **pg_stat_user_indexes**: Find unused indexes (`idx_scan = 0` with large size).
|
|
16
|
+
- **pg_stat_bgwriter**: `buffers_clean`, `maxwritten_clean`, `buffers_alloc`. Pre-PG 17 also had `buffers_checkpoint`, `buffers_backend` (high = backends bypassing bgwriter). PG 17+ moved checkpoint stats to `pg_stat_checkpointer`.
|
|
17
|
+
- **pg_stat_checkpointer** (PG 17+): Checkpoint frequency (`num_timed`, `num_requested`), write/sync time.
|
|
18
|
+
|
|
19
|
+
## Key Queries
|
|
20
|
+
|
|
21
|
+
```sql
|
|
22
|
+
-- Slow queries (with cache hit ratio)
|
|
23
|
+
SELECT query, calls, mean_exec_time,
|
|
24
|
+
100.0 * shared_blks_hit / nullif(shared_blks_hit + shared_blks_read, 0) AS cache_hit_pct
|
|
25
|
+
FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 10;
|
|
26
|
+
|
|
27
|
+
-- Connection counts / states
|
|
28
|
+
SELECT state, count(*) FROM pg_stat_activity GROUP BY state;
|
|
29
|
+
|
|
30
|
+
-- Dead tuples (vacuum candidates)
|
|
31
|
+
SELECT relname, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC;
|
|
32
|
+
-- last_autovacuum = <null> means autovacuum has not run on this table
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Blocking: use `pg_blocking_pids(pid)` with `pg_stat_activity` to find blocked and blocking sessions.
|
|
36
|
+
|
|
37
|
+
## Logging — First Line of Defense
|
|
38
|
+
|
|
39
|
+
PostgreSQL is extremely vocal about problems. **Always check logs first**: `tail -f /var/log/postgresql/postgresql-*.log`.
|
|
40
|
+
|
|
41
|
+
Key settings: `log_min_duration_statement` (OLTP: 1–3s, analytics: 30–60s, dev: 100–500ms). Enable `log_checkpoints=on`, `log_connections=on`, `log_disconnections=on`, `log_lock_waits=on`, `log_temp_files=0`. Use CSV log format for pgBadger analysis; pgBadger generates HTML reports with query stats and performance graphs.
|
|
42
|
+
|
|
43
|
+
## pg_activity
|
|
44
|
+
|
|
45
|
+
Interactive top-like tool (pip install pg_activity). Run on DB host for OS metrics alongside PG metrics. Combines `pg_stat_activity` with CPU/memory/I/O context.
|
|
46
|
+
|
|
47
|
+
## Host Metrics — Critical
|
|
48
|
+
|
|
49
|
+
PostgreSQL cannot report these. **Monitor them yourself:**
|
|
50
|
+
|
|
51
|
+
- **CPU**: Steal time >10% in VMs bad; load average > core count; context switches >100k/sec.
|
|
52
|
+
- **Memory**: Any swap = performance degradation. Check `dmesg` for OOM kills.
|
|
53
|
+
- **Disk I/O**: `iostat -x` — `%util=100%` means saturated; `await` >10ms = high latency.
|
|
54
|
+
- **Disk space**: >90% critical (VACUUM fails, writes fail). Check inode usage too.
|
|
55
|
+
- **Network**: Packet loss >0% = problems; high retransmits = instability.
|
|
56
|
+
|
|
57
|
+
## Statistics Management
|
|
58
|
+
|
|
59
|
+
Stats accumulate since last reset or restart; check `stats_reset` timestamp. `pg_stat_statements_reset()` clears query stats; `pg_stat_reset()` clears database stats. Reset after major maintenance, config changes, or perf testing — not routinely. Prefer snapshotting stats to external monitoring (Prometheus, Datadog) over resetting. **Always confirm with a human before resetting statistics** — resetting destroys historical performance baselines and can make it harder to identify unused indexes or regressions.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: MVCC Transactions and Concurrency
|
|
3
|
+
description: Transaction isolation levels, XID wraparound prevention, serialization errors, and long-transaction impact
|
|
4
|
+
tags: postgres, mvcc, transactions, isolation, xid-wraparound, concurrency, serialization
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# MVCC Transactions and Concurrency
|
|
8
|
+
|
|
9
|
+
## Transaction Isolation Levels
|
|
10
|
+
|
|
11
|
+
- **READ UNCOMMITTED** — treated as READ COMMITTED in PostgreSQL; no dirty reads ever.
|
|
12
|
+
- **READ COMMITTED** (default): new snapshot per statement; can see different data within same tx.
|
|
13
|
+
- **REPEATABLE READ**: snapshot at first query; can cause serialization errors on write conflicts.
|
|
14
|
+
- **SERIALIZABLE**: strongest; transactions appear serial; requires retry logic in app code.
|
|
15
|
+
|
|
16
|
+
Readers never block writers; writers never block readers (only writer-writer conflicts on same row). No lock escalation — row locks never degrade to table locks.
|
|
17
|
+
|
|
18
|
+
## XID Wraparound
|
|
19
|
+
|
|
20
|
+
32-bit transaction IDs wrap at ~2 billion (2^31). `VACUUM FREEZE` replaces old XIDs with FrozenXID (value 2, always visible). Without freeze: after wraparound, old rows appear "in the future" and become **invisible**. Data physically exists but is invisible to all queries — looks like total data loss. PostgreSQL emergency shutdown at 2B XIDs to prevent this. XID wraparound should be avoided at all cost.
|
|
21
|
+
|
|
22
|
+
Warning messages start at ~1.4B XIDs; shutdown at 2B. Recovery requires single-user mode VACUUM — can take hours to days on large DBs. **Never disable autovacuum** — it's your protection against wraparound.
|
|
23
|
+
|
|
24
|
+
## XID Age Monitoring
|
|
25
|
+
|
|
26
|
+
```sql
|
|
27
|
+
SELECT datname, age(datfrozenxid),
|
|
28
|
+
ROUND(100.0 * age(datfrozenxid) / 2147483648, 2) AS pct
|
|
29
|
+
FROM pg_database ORDER BY age(datfrozenxid) DESC;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Long Transaction Impact
|
|
33
|
+
|
|
34
|
+
A single long-running transaction blocks VACUUM from removing dead tuples across the **entire database**. Causes table bloat, increased disk, slower queries, cache pollution. `idle_in_transaction` connections are the #1 operational MVCC issue. Set `idle_in_transaction_session_timeout` (30s–5min). Dead tuples waste I/O on seq scans and cause useless heap lookups from indexes.
|
|
35
|
+
|
|
36
|
+
## Serialization Errors
|
|
37
|
+
|
|
38
|
+
Apps **must** handle "could not serialize access" with retry logic. More common in REPEATABLE READ and SERIALIZABLE. Smaller, faster transactions reduce conflict frequency.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: MVCC and VACUUM
|
|
3
|
+
description: MVCC internals, VACUUM/autovacuum tuning, and bloat prevention
|
|
4
|
+
tags: postgres, mvcc, vacuum, autovacuum, xid, bloat, dead-tuples
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# MVCC and VACUUM
|
|
8
|
+
|
|
9
|
+
## MVCC
|
|
10
|
+
|
|
11
|
+
Every `UPDATE` creates a new tuple and marks the old one dead; `DELETE` marks tuples dead. Dead tuples accumulate until `VACUUM` reclaims space. Each transaction gets a 32-bit XID (2^32 ≈ 4B values, but modular comparison means the effective danger zone is 2^31 ≈ 2B). VACUUM must freeze old XIDs to prevent wraparound.
|
|
12
|
+
|
|
13
|
+
## VACUUM vs VACUUM FULL
|
|
14
|
+
|
|
15
|
+
`VACUUM` is non-blocking (ShareUpdateExclusive lock) and marks dead space reusable. `VACUUM FULL` rewrites the table and requires an AccessExclusive lock — use only as a last resort. For online bloat reduction prefer `pg_squeeze` or `pg_repack`.
|
|
16
|
+
|
|
17
|
+
## Autovacuum Tuning
|
|
18
|
+
|
|
19
|
+
Triggers when dead tuples > `Min(autovacuum_vacuum_max_threshold, autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor * reltuples)`. `autovacuum_vacuum_max_threshold` defaults to 100M (PG 18+), capping the threshold for very large tables. Also triggers on inserts exceeding `autovacuum_vacuum_insert_threshold + autovacuum_vacuum_insert_scale_factor * reltuples * pct_not_frozen` (ensures insert-only tables get frozen; PG 13+). For large/hot tables, set per-table overrides:
|
|
20
|
+
|
|
21
|
+
- `autovacuum_vacuum_scale_factor` — default 0.2; lower to 0.01–0.05 for large tables.
|
|
22
|
+
- `autovacuum_vacuum_cost_delay` — default 2 ms; set to 0 on fast storage.
|
|
23
|
+
- `autovacuum_vacuum_cost_limit` — default -1 (uses `vacuum_cost_limit`, effectively 200); raise to 1000–2000 on fast storage.
|
|
24
|
+
- `autovacuum_freeze_max_age` — default 200M; triggers anti-wraparound vacuum.
|
|
25
|
+
- `vacuum_failsafe_age` — default 1.6B; last-resort mode (PG 14+) that disables throttling and skips index vacuuming when wraparound is imminent.
|
|
26
|
+
|
|
27
|
+
## Key Monitoring Queries
|
|
28
|
+
|
|
29
|
+
Dead tuples: `SELECT relname, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC;`
|
|
30
|
+
|
|
31
|
+
XID age: `SELECT datname, age(datfrozenxid) AS xid_age FROM pg_database ORDER BY xid_age DESC;`
|
|
32
|
+
|
|
33
|
+
Long transactions: `SELECT pid, state, now() - xact_start AS tx_age FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_start;`
|
|
34
|
+
|
|
35
|
+
## Best Practices
|
|
36
|
+
|
|
37
|
+
- Keep transactions short; set `idle_in_transaction_session_timeout` (30s–5min).
|
|
38
|
+
- Alert when `age(datfrozenxid)` exceeds 40–50% of wraparound (~800M–1B).
|
|
39
|
+
- Tune autovacuum per-table for write-heavy tables; don't change global defaults first.
|
|
40
|
+
- Fix application transaction scope before adjusting vacuum parameters.
|
|
41
|
+
- Never disable autovacuum globally.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Database Optimization Checklist
|
|
3
|
+
description: Optimize checklist
|
|
4
|
+
tags: postgres, optimization, indexes, partitioning, maintenance
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Optimization Checklist
|
|
8
|
+
|
|
9
|
+
When optimizing performance, check the following:
|
|
10
|
+
|
|
11
|
+
- Look for unused indexes (0 scans; exclude unique/primary indexes and verify stats age first)
|
|
12
|
+
- Look for duplicate indexes
|
|
13
|
+
- Archive audit/log tables >10GB
|
|
14
|
+
- Review tables >500GB for partitioning (>100GB for time-series/logs)
|
|
15
|
+
- Verify all extensions are supported
|
|
16
|
+
- Check for circular foreign key dependencies
|
|
17
|
+
- Consider alternatives to UUID primary keys for large tables
|
|
18
|
+
- Configure connection pooling for OLTP workloads
|
|
19
|
+
- **Always confirm with a human before removing any indexes, dropping partitions, archiving tables, or performing other destructive actions**
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Table Partitioning Guide
|
|
3
|
+
description: Partition guide
|
|
4
|
+
tags: postgres, partitioning, range, list, pg_partman, data-retention
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Table Partitioning
|
|
8
|
+
|
|
9
|
+
Plan partitioning upfront for tables expected to grow large. Retrofitting later requires a migration.
|
|
10
|
+
|
|
11
|
+
## When to Partition
|
|
12
|
+
|
|
13
|
+
Partitioning benefits maintenance (vacuum, index builds) and data retention more than pure query speed.
|
|
14
|
+
|
|
15
|
+
| Table Type | Size Threshold | Row Threshold |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| General tables | >100 GB (or >RAM) | >20M rows |
|
|
18
|
+
| Time-series / logs | >50 GB | >10M rows |
|
|
19
|
+
|
|
20
|
+
Use the lower thresholds for append-heavy, time-ordered data with retention needs (logs, events, audit trails, metrics).
|
|
21
|
+
|
|
22
|
+
## Range Partitioning (Most Common)
|
|
23
|
+
|
|
24
|
+
```sql
|
|
25
|
+
-- EXAMPLE
|
|
26
|
+
CREATE TABLE event (
|
|
27
|
+
id BIGINT GENERATED ALWAYS AS IDENTITY,
|
|
28
|
+
event_type TEXT NOT NULL,
|
|
29
|
+
payload JSONB,
|
|
30
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
31
|
+
PRIMARY KEY (id, created_at) -- Partition key MUST be part of PK
|
|
32
|
+
) PARTITION BY RANGE (created_at);
|
|
33
|
+
|
|
34
|
+
CREATE TABLE event_2026_01 PARTITION OF event
|
|
35
|
+
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
|
|
36
|
+
|
|
37
|
+
CREATE TABLE event_2026_02 PARTITION OF event
|
|
38
|
+
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## List Partitioning
|
|
42
|
+
|
|
43
|
+
Useful for partitioning by region, tenant, or category:
|
|
44
|
+
|
|
45
|
+
```sql
|
|
46
|
+
-- EXAMPLE
|
|
47
|
+
CREATE TABLE order (
|
|
48
|
+
id BIGINT GENERATED ALWAYS AS IDENTITY,
|
|
49
|
+
region TEXT NOT NULL,
|
|
50
|
+
total NUMERIC(10,2),
|
|
51
|
+
PRIMARY KEY (id, region) -- Partition key MUST be part of PK
|
|
52
|
+
) PARTITION BY LIST (region);
|
|
53
|
+
|
|
54
|
+
CREATE TABLE order_us PARTITION OF order FOR VALUES IN ('us');
|
|
55
|
+
CREATE TABLE order_eu PARTITION OF order FOR VALUES IN ('eu');
|
|
56
|
+
CREATE TABLE order_default PARTITION OF order DEFAULT; -- catches unmatched values
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Partition Management
|
|
60
|
+
|
|
61
|
+
- Use `pg_partman` (extension) to automate partition creation and cleanup.
|
|
62
|
+
- Use `DETACH PARTITION` to remove a partition while retaining it as a standalone table (e.g., for archiving).
|
|
63
|
+
- Use `DETACH PARTITION ... CONCURRENTLY` (PG 14+) to avoid `ACCESS EXCLUSIVE` locks on the parent table.
|
|
64
|
+
- Drop old partitions for data retention instead of `DELETE` to avoid vacuum overhead and bloat.
|
|
65
|
+
- Create future partitions ahead of time to avoid insert failures.
|
|
66
|
+
- **Always confirm with a human before detaching or dropping partitions.** These are destructive actions — detaching removes data from the partitioned table, and dropping permanently deletes the data.
|
|
67
|
+
|
|
68
|
+
```sql
|
|
69
|
+
-- DESTRUCTIVE: confirm with a human before executing
|
|
70
|
+
ALTER TABLE event DETACH PARTITION event_2025_01 CONCURRENTLY;
|
|
71
|
+
DROP TABLE event_2025_01;
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Guidelines & Limitations
|
|
75
|
+
|
|
76
|
+
- **Primary Keys**: Partition key columns MUST be included in the `PRIMARY KEY` and any `UNIQUE` constraints.
|
|
77
|
+
- **Global Uniqueness**: Global unique constraints on non-partition columns are NOT supported.
|
|
78
|
+
- **Indexes**: Indexes defined on the parent are automatically created on all partitions (and future ones).
|
|
79
|
+
- **Pruning**: Ensure queries filter by the partition key to enable "partition pruning" (skipping unrelated partitions).
|