@evertonferreira96/agents 0.0.0-stage → 0.1.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/.claude/agents/api.md +45 -0
- package/.claude/agents/app.md +41 -0
- package/.claude/agents/architect.md +49 -0
- package/.claude/agents/infra.md +27 -0
- package/.claude/agents/product.md +58 -0
- package/.claude/agents/quick.md +33 -0
- package/.claude/agents/security.md +39 -0
- package/.claude/agents/ui.md +54 -0
- package/.claude/agents/verifier.md +56 -0
- package/.claude/agents/web.md +41 -0
- package/.claude/hooks/config-update-check.mjs +14 -0
- package/.claude/hooks/context-budget.mjs +51 -0
- package/.claude/hooks/graphify-update.sh +16 -0
- package/.claude/hooks/guard-bash.sh +64 -0
- package/.claude/hooks/orchestrator-guard.sh +49 -0
- package/.claude/hooks/post-compact.sh +26 -0
- package/.claude/hooks/readonly-guard.sh +54 -0
- package/.claude/hooks/stop-verify.sh +52 -0
- package/.claude/rules/api.md +21 -0
- package/.claude/rules/db.md +9 -0
- package/.claude/rules/delivery-diagram.md +16 -0
- package/.claude/rules/git-workflow.md +34 -0
- package/.claude/rules/graphify.md +3 -0
- package/.claude/rules/mobile.md +22 -0
- package/.claude/rules/tickets.md +9 -0
- package/.claude/rules/ui.md +14 -0
- package/.claude/rules/web.md +27 -0
- package/.claude/settings.json +114 -0
- package/.claude/skills/animate/RECIPES.md +324 -0
- package/.claude/skills/animate/SKILL.md +208 -0
- package/.claude/skills/animate/agents/openai.yaml +5 -0
- package/.claude/skills/animate-expo/RECIPES.md +385 -0
- package/.claude/skills/animate-expo/SKILL.md +264 -0
- package/.claude/skills/animate-expo/agents/openai.yaml +5 -0
- package/.claude/skills/animation-performance/SKILL.md +223 -0
- package/.claude/skills/animation-performance/agents/openai.yaml +5 -0
- package/.claude/skills/apple-design/SKILL.md +291 -0
- package/.claude/skills/apple-design/agents/openai.yaml +5 -0
- package/.claude/skills/arch-api/SKILL.md +291 -0
- package/.claude/skills/arch-api/agents/openai.yaml +3 -0
- package/.claude/skills/arch-api/references/common.md +174 -0
- package/.claude/skills/arch-api/references/contract.md +289 -0
- package/.claude/skills/arch-api/references/domain.md +301 -0
- package/.claude/skills/arch-api/references/endpoints.md +184 -0
- package/.claude/skills/arch-api/references/review.md +228 -0
- package/.claude/skills/arch-api/references/security.md +151 -0
- package/.claude/skills/arch-app/SKILL.md +278 -0
- package/.claude/skills/arch-app/agents/openai.yaml +3 -0
- package/.claude/skills/arch-app/references/navigation-session.md +257 -0
- package/.claude/skills/arch-app/references/review.md +155 -0
- package/.claude/skills/arch-app/references/screens.md +214 -0
- package/.claude/skills/arch-app/references/services.md +301 -0
- package/.claude/skills/arch-app/references/ui-mobile.md +124 -0
- package/.claude/skills/arch-db/SKILL.md +181 -0
- package/.claude/skills/arch-db/agents/openai.yaml +3 -0
- package/.claude/skills/arch-db/references/indexes-queries.md +251 -0
- package/.claude/skills/arch-db/references/migrations.md +191 -0
- package/.claude/skills/arch-db/references/modeling.md +214 -0
- package/.claude/skills/arch-db/references/mysql.md +262 -0
- package/.claude/skills/arch-db/references/pooling-ops.md +112 -0
- package/.claude/skills/arch-db/references/review.md +143 -0
- package/.claude/skills/arch-db/references/transactions-locks.md +141 -0
- package/.claude/skills/arch-web/SKILL.md +307 -0
- package/.claude/skills/arch-web/agents/openai.yaml +3 -0
- package/.claude/skills/arch-web/references/auth-session-env.md +165 -0
- package/.claude/skills/arch-web/references/blocks.md +198 -0
- package/.claude/skills/arch-web/references/cache.md +124 -0
- package/.claude/skills/arch-web/references/review.md +142 -0
- package/.claude/skills/arch-web/references/services.md +297 -0
- package/.claude/skills/arch-web/references/tests.md +159 -0
- package/.claude/skills/arch-web/references/ui-forms.md +143 -0
- package/.claude/skills/break-ui/CATALOG.md +135 -0
- package/.claude/skills/break-ui/SKILL.md +176 -0
- package/.claude/skills/break-ui/agents/openai.yaml +5 -0
- package/.claude/skills/brief/SKILL.md +91 -0
- package/.claude/skills/brief/agents/openai.yaml +3 -0
- package/.claude/skills/diagnosing-bugs/SKILL.md +138 -0
- package/.claude/skills/diagnosing-bugs/agents/openai.yaml +3 -0
- package/.claude/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
- package/.claude/skills/emil-design-eng/SKILL.md +675 -0
- package/.claude/skills/emil-design-eng/agents/openai.yaml +5 -0
- package/.claude/skills/find-animation-opportunities/SKILL.md +141 -0
- package/.claude/skills/find-animation-opportunities/agents/openai.yaml +5 -0
- package/.claude/skills/graphify/.graphify_version +1 -0
- package/.claude/skills/graphify/SKILL.md +742 -0
- package/.claude/skills/graphify/agents/openai.yaml +3 -0
- package/.claude/skills/graphify/references/add-watch.md +56 -0
- package/.claude/skills/graphify/references/exports.md +91 -0
- package/.claude/skills/graphify/references/extraction-spec.md +70 -0
- package/.claude/skills/graphify/references/github-and-merge.md +46 -0
- package/.claude/skills/graphify/references/hooks.md +33 -0
- package/.claude/skills/graphify/references/query.md +317 -0
- package/.claude/skills/graphify/references/transcribe.md +52 -0
- package/.claude/skills/graphify/references/update.md +221 -0
- package/.claude/skills/grill-me/SKILL.md +7 -0
- package/.claude/skills/grill-me/agents/openai.yaml +5 -0
- package/.claude/skills/grill-with-docs/SKILL.md +64 -0
- package/.claude/skills/grill-with-docs/agents/openai.yaml +5 -0
- package/.claude/skills/grilling/SKILL.md +30 -0
- package/.claude/skills/grilling/agents/openai.yaml +3 -0
- package/.claude/skills/handoff/SKILL.md +28 -0
- package/.claude/skills/handoff/agents/openai.yaml +5 -0
- package/.claude/skills/impeccable/SKILL.md +89 -0
- package/.claude/skills/impeccable/agents/openai.yaml +5 -0
- package/.claude/skills/impeccable/reference/adapt.md +318 -0
- package/.claude/skills/impeccable/reference/adapt.native.md +58 -0
- package/.claude/skills/impeccable/reference/android.md +46 -0
- package/.claude/skills/impeccable/reference/animate.md +89 -0
- package/.claude/skills/impeccable/reference/audit.md +137 -0
- package/.claude/skills/impeccable/reference/audit.native.md +139 -0
- package/.claude/skills/impeccable/reference/bolder.md +33 -0
- package/.claude/skills/impeccable/reference/clarify.md +94 -0
- package/.claude/skills/impeccable/reference/colorize.md +86 -0
- package/.claude/skills/impeccable/reference/component-review.md +63 -0
- package/.claude/skills/impeccable/reference/craft-floor.md +44 -0
- package/.claude/skills/impeccable/reference/craft.md +5 -0
- package/.claude/skills/impeccable/reference/critique.md +809 -0
- package/.claude/skills/impeccable/reference/degraded/asset-producer.md +42 -0
- package/.claude/skills/impeccable/reference/degraded/documenter.md +24 -0
- package/.claude/skills/impeccable/reference/degraded/finish-reviewer.md +38 -0
- package/.claude/skills/impeccable/reference/degraded/manual-edit-applier.md +92 -0
- package/.claude/skills/impeccable/reference/delight.md +70 -0
- package/.claude/skills/impeccable/reference/distill.md +111 -0
- package/.claude/skills/impeccable/reference/doctor.md +54 -0
- package/.claude/skills/impeccable/reference/document.md +416 -0
- package/.claude/skills/impeccable/reference/extract.md +69 -0
- package/.claude/skills/impeccable/reference/generate.md +101 -0
- package/.claude/skills/impeccable/reference/harden.md +345 -0
- package/.claude/skills/impeccable/reference/hooks.md +113 -0
- package/.claude/skills/impeccable/reference/init.md +131 -0
- package/.claude/skills/impeccable/reference/ios.md +51 -0
- package/.claude/skills/impeccable/reference/layout.md +84 -0
- package/.claude/skills/impeccable/reference/live-setup.md +104 -0
- package/.claude/skills/impeccable/reference/live.md +325 -0
- package/.claude/skills/impeccable/reference/mode-operate.md +21 -0
- package/.claude/skills/impeccable/reference/mode-persuade.md +19 -0
- package/.claude/skills/impeccable/reference/mode-read.md +21 -0
- package/.claude/skills/impeccable/reference/new-work.md +154 -0
- package/.claude/skills/impeccable/reference/onboard.md +234 -0
- package/.claude/skills/impeccable/reference/operate.md +61 -0
- package/.claude/skills/impeccable/reference/optimize.md +258 -0
- package/.claude/skills/impeccable/reference/overdrive.md +127 -0
- package/.claude/skills/impeccable/reference/polish.md +105 -0
- package/.claude/skills/impeccable/reference/quieter.md +99 -0
- package/.claude/skills/impeccable/reference/region-map.md +26 -0
- package/.claude/skills/impeccable/reference/routing.md +24 -0
- package/.claude/skills/impeccable/reference/shape.md +59 -0
- package/.claude/skills/impeccable/reference/typeset.md +80 -0
- package/.claude/skills/impeccable/reference/visualize.md +47 -0
- package/.claude/skills/impeccable/scripts/VERSION +1 -0
- package/.claude/skills/impeccable/scripts/command-metadata.json +98 -0
- package/.claude/skills/impeccable/scripts/data/font-index-failures.json +121 -0
- package/.claude/skills/impeccable/scripts/data/font-index.json +1 -0
- package/.claude/skills/impeccable/scripts/impeccable +206 -0
- package/.claude/skills/impeccable/scripts/impeccable.cmd +214 -0
- package/.claude/skills/impeccable/scripts/live-browser-dom.js +281 -0
- package/.claude/skills/impeccable/scripts/live-browser-ignores.js +242 -0
- package/.claude/skills/impeccable/scripts/live-browser-session.js +148 -0
- package/.claude/skills/impeccable/scripts/live-browser.js +13532 -0
- package/.claude/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
- package/.claude/skills/improve-animations/AUDIT.md +115 -0
- package/.claude/skills/improve-animations/PLAN-TEMPLATE.md +73 -0
- package/.claude/skills/improve-animations/SKILL.md +110 -0
- package/.claude/skills/improve-animations/agents/openai.yaml +5 -0
- package/.claude/skills/mobile-native/SKILL.md +311 -0
- package/.claude/skills/mobile-native/agents/openai.yaml +3 -0
- package/.claude/skills/orchestrate/SKILL.md +181 -0
- package/.claude/skills/orchestrate/agents/openai.yaml +3 -0
- package/.claude/skills/pick-ui-library/SKILL.md +85 -0
- package/.claude/skills/pick-ui-library/agents/openai.yaml +5 -0
- package/.claude/skills/ponytail/SKILL.md +52 -0
- package/.claude/skills/ponytail/agents/openai.yaml +3 -0
- package/.claude/skills/ponytail-audit/SKILL.md +103 -0
- package/.claude/skills/ponytail-audit/agents/openai.yaml +3 -0
- package/.claude/skills/ponytail-debt/SKILL.md +44 -0
- package/.claude/skills/ponytail-debt/agents/openai.yaml +3 -0
- package/.claude/skills/ponytail-review/SKILL.md +101 -0
- package/.claude/skills/ponytail-review/agents/openai.yaml +3 -0
- package/.claude/skills/postgres-best-practices/SKILL.md +42 -0
- package/.claude/skills/postgres-best-practices/agents/openai.yaml +3 -0
- package/.claude/skills/postgres-best-practices/references/backup-restore.md +372 -0
- package/.claude/skills/postgres-best-practices/references/bulk-loading.md +315 -0
- package/.claude/skills/postgres-best-practices/references/connection-pooling.md +310 -0
- package/.claude/skills/postgres-best-practices/references/hot-standby.md +221 -0
- package/.claude/skills/postgres-best-practices/references/indexing.md +318 -0
- package/.claude/skills/postgres-best-practices/references/logical-replication.md +610 -0
- package/.claude/skills/postgres-best-practices/references/major-version-upgrades.md +334 -0
- package/.claude/skills/postgres-best-practices/references/performance-diagnostics.md +553 -0
- package/.claude/skills/postgres-best-practices/references/query-optimization.md +294 -0
- package/.claude/skills/postgres-best-practices/references/query-patterns.md +566 -0
- package/.claude/skills/postgres-best-practices/references/schema-design.md +362 -0
- package/.claude/skills/postgres-best-practices/references/security-roles.md +489 -0
- package/.claude/skills/postgres-best-practices/references/transaction-isolation.md +344 -0
- package/.claude/skills/project-docs/SKILL.md +330 -0
- package/.claude/skills/project-docs/agents/openai.yaml +3 -0
- package/.claude/skills/prototype/PICKER.md +197 -0
- package/.claude/skills/prototype/SKILL.md +98 -0
- package/.claude/skills/prototype/agents/openai.yaml +5 -0
- package/.claude/skills/retro/SKILL.md +43 -0
- package/.claude/skills/retro/agents/openai.yaml +5 -0
- package/.claude/skills/review/SKILL.md +74 -0
- package/.claude/skills/review/agents/openai.yaml +3 -0
- package/.claude/skills/review/references/smells.md +23 -0
- package/.claude/skills/review-animations/SKILL.md +120 -0
- package/.claude/skills/review-animations/STANDARDS.md +187 -0
- package/.claude/skills/review-animations/agents/openai.yaml +5 -0
- package/.claude/skills/tdd/SKILL.md +38 -0
- package/.claude/skills/tdd/agents/openai.yaml +3 -0
- package/.claude/skills/tdd/mocking.md +59 -0
- package/.claude/skills/tdd/tests.md +77 -0
- package/.claude/skills/triage/SKILL.md +58 -0
- package/.claude/skills/triage/agents/openai.yaml +3 -0
- package/.claude/skills/ui-ux-pro-max/SKILL.md +378 -0
- package/.claude/skills/ui-ux-pro-max/agents/openai.yaml +5 -0
- package/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.claude/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.claude/skills/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/.claude/skills/writing-for-agents/SKILL.md +81 -0
- package/.claude/skills/writing-for-agents/agents/openai.yaml +3 -0
- package/.worktreeinclude +3 -0
- package/AGENTS.md +44 -0
- package/README.md +14 -2
- package/VERSION +1 -0
- package/bin/agents.mjs +45 -0
- package/docs/adr/README.md +31 -0
- package/docs/brief-template.md +3 -0
- package/docs/plans/README.md +73 -0
- package/package.json +46 -4
- package/scripts/bootstrap.mjs +188 -0
- package/scripts/check-catalog.mjs +43 -0
- package/scripts/skills-update.mjs +51 -0
- package/skills-lock.json +269 -0
- package/templates/CLAUDE.project.md +41 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# Backup & Restore Reference
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
- Backup strategy overview
|
|
5
|
+
- Logical backups (pg_dump / pg_restore)
|
|
6
|
+
- Physical backups (pg_basebackup)
|
|
7
|
+
- Point-in-Time Recovery (PITR)
|
|
8
|
+
- Verification and testing
|
|
9
|
+
- Automation patterns
|
|
10
|
+
|
|
11
|
+
## Backup Strategy Overview
|
|
12
|
+
|
|
13
|
+
| Method | What it captures | Granularity | Speed | Use case |
|
|
14
|
+
|--------|-----------------|-------------|-------|----------|
|
|
15
|
+
| `pg_dump` | Logical (SQL/custom) | Per-database, per-table | Slow on large DBs | Dev snapshots, migrations, selective restore |
|
|
16
|
+
| `pg_dumpall` | All databases + globals | Entire cluster | Slow | Full cluster backup including roles/tablespaces |
|
|
17
|
+
| `pg_basebackup` | Physical (file copy) | Entire cluster | Fast | Production backups, PITR base, replica setup |
|
|
18
|
+
| Continuous archiving | WAL segments | Incremental | Continuous | PITR — restore to any point in time |
|
|
19
|
+
|
|
20
|
+
**Production recommendation**: `pg_basebackup` + continuous WAL archiving for PITR capability. Supplement with periodic `pg_dump` for portable, version-independent backups.
|
|
21
|
+
|
|
22
|
+
## Logical Backups (pg_dump / pg_restore)
|
|
23
|
+
|
|
24
|
+
### pg_dump Formats
|
|
25
|
+
|
|
26
|
+
| Format | Flag | Parallel restore? | Selective restore? | Notes |
|
|
27
|
+
|--------|------|-------------------|-------------------|-------|
|
|
28
|
+
| Custom | `-Fc` | Yes | Yes | **Recommended default** — compressed, flexible |
|
|
29
|
+
| Directory | `-Fd` | Yes | Yes | One file per table, good for large DBs |
|
|
30
|
+
| Plain SQL | `-Fp` | No | No (manual editing) | Human-readable, good for version control |
|
|
31
|
+
| Tar | `-Ft` | No | Yes | Compatibility option |
|
|
32
|
+
|
|
33
|
+
### Common pg_dump Patterns
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Full database backup (custom format — recommended)
|
|
37
|
+
pg_dump -Fc -f backup.dump mydb
|
|
38
|
+
|
|
39
|
+
# With compression level (0-9, default varies by format)
|
|
40
|
+
pg_dump -Fc -Z 6 -f backup.dump mydb
|
|
41
|
+
|
|
42
|
+
# Parallel dump (directory format only, 4 workers)
|
|
43
|
+
pg_dump -Fd -j 4 -f backup_dir/ mydb
|
|
44
|
+
|
|
45
|
+
# Schema only (no data)
|
|
46
|
+
pg_dump -Fc --schema-only -f schema.dump mydb
|
|
47
|
+
|
|
48
|
+
# Data only (no schema)
|
|
49
|
+
pg_dump -Fc --data-only -f data.dump mydb
|
|
50
|
+
|
|
51
|
+
# Specific tables
|
|
52
|
+
pg_dump -Fc -t orders -t customers -f subset.dump mydb
|
|
53
|
+
|
|
54
|
+
# Specific schema
|
|
55
|
+
pg_dump -Fc -n public -f public_schema.dump mydb
|
|
56
|
+
|
|
57
|
+
# Exclude large tables
|
|
58
|
+
pg_dump -Fc -T audit_log -T event_archive -f without_logs.dump mydb
|
|
59
|
+
|
|
60
|
+
# Include CREATE DATABASE in output
|
|
61
|
+
pg_dump -Fc --create -f backup_with_create.dump mydb
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### pg_restore Patterns
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Restore to an existing (empty) database
|
|
68
|
+
pg_restore -d mydb backup.dump
|
|
69
|
+
|
|
70
|
+
# Parallel restore (4 workers — significantly faster for large DBs)
|
|
71
|
+
pg_restore -d mydb -j 4 backup.dump
|
|
72
|
+
|
|
73
|
+
# Create the database during restore
|
|
74
|
+
pg_restore --create -d postgres backup.dump
|
|
75
|
+
|
|
76
|
+
# List contents of a backup (inspect before restoring)
|
|
77
|
+
pg_restore --list backup.dump
|
|
78
|
+
|
|
79
|
+
# Restore specific tables only
|
|
80
|
+
pg_restore -d mydb -t orders -t customers backup.dump
|
|
81
|
+
|
|
82
|
+
# Schema only
|
|
83
|
+
pg_restore -d mydb --schema-only backup.dump
|
|
84
|
+
|
|
85
|
+
# Data only (schema already exists)
|
|
86
|
+
pg_restore -d mydb --data-only backup.dump
|
|
87
|
+
|
|
88
|
+
# Clean (drop) objects before recreating
|
|
89
|
+
pg_restore -d mydb --clean --if-exists backup.dump
|
|
90
|
+
|
|
91
|
+
# Restore atomically and stop on the first error
|
|
92
|
+
pg_restore -d mydb --single-transaction -j 1 backup.dump
|
|
93
|
+
# Note: --single-transaction is incompatible with -j > 1
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Selective restore patterns do not automatically include dependencies. Schema-qualify table patterns when names may exist in multiple schemas, and verify required types, sequences, constraints, and referenced tables separately.
|
|
97
|
+
|
|
98
|
+
### Restoring Plain SQL Dumps
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Plain SQL dumps are restored with psql, not pg_restore
|
|
102
|
+
psql -d mydb -f backup.sql
|
|
103
|
+
|
|
104
|
+
# With error handling
|
|
105
|
+
psql -d mydb -v ON_ERROR_STOP=1 -f backup.sql
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### pg_dumpall — Cluster-Wide Backup
|
|
109
|
+
|
|
110
|
+
`pg_dumpall` is the only way to back up global objects (roles, tablespaces):
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Full cluster (all databases + globals) — plain SQL only
|
|
114
|
+
pg_dumpall -f cluster_backup.sql
|
|
115
|
+
|
|
116
|
+
# Globals only (roles, tablespaces) — use alongside per-database pg_dump
|
|
117
|
+
pg_dumpall --globals-only -f globals.sql
|
|
118
|
+
|
|
119
|
+
# Roles only
|
|
120
|
+
pg_dumpall --roles-only -f roles.sql
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Best practice**: Use `pg_dumpall --globals-only` for roles/tablespaces, then `pg_dump -Fc` per database for data. This gives you parallel restore capability while preserving globals.
|
|
124
|
+
|
|
125
|
+
### Optimizer Statistics in Backups (PG18+)
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Dump optimizer statistics (speeds up post-restore query performance)
|
|
129
|
+
pg_dump -Fc --statistics -f backup.dump mydb
|
|
130
|
+
|
|
131
|
+
# Statistics only (no schema or data)
|
|
132
|
+
pg_dump -Fc --statistics-only -f stats.dump mydb
|
|
133
|
+
|
|
134
|
+
# Skip statistics
|
|
135
|
+
pg_dump -Fc --no-statistics -f backup.dump mydb
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Without statistics, the planner uses default estimates after restore until `ANALYZE` runs on all tables. Dumping statistics avoids the post-restore performance dip.
|
|
139
|
+
|
|
140
|
+
### Performance Tips for Large Databases
|
|
141
|
+
|
|
142
|
+
1. **Use parallel dump/restore** (`-Fd -j N`) — scales well with CPU cores
|
|
143
|
+
2. **Dump to fast storage** — local NVMe, not network mounts
|
|
144
|
+
3. **Increase `maintenance_work_mem`** on the restore target for faster index rebuilds
|
|
145
|
+
4. **Disable triggers during data-only restore**: `pg_restore --disable-triggers` (requires superuser)
|
|
146
|
+
5. **Drop indexes before restore, recreate after** — faster than incremental index maintenance during bulk inserts
|
|
147
|
+
6. **Restore schema first, then data, then indexes**:
|
|
148
|
+
```bash
|
|
149
|
+
pg_restore -d mydb --section=pre-data backup.dump
|
|
150
|
+
pg_restore -d mydb --data-only -j 4 backup.dump
|
|
151
|
+
pg_restore -d mydb --section=post-data -j 4 backup.dump
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Physical Backups (pg_basebackup)
|
|
155
|
+
|
|
156
|
+
`pg_basebackup` creates a byte-for-byte copy of the entire cluster, suitable as a base for PITR or setting up replicas.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Basic backup (plain format)
|
|
160
|
+
pg_basebackup -D /backup/base -Fp -Xs -P
|
|
161
|
+
|
|
162
|
+
# Compressed tar format
|
|
163
|
+
pg_basebackup -D /backup/base -Ft -z -Xs -P
|
|
164
|
+
|
|
165
|
+
# With checkpoint mode (fast = don't wait for next scheduled checkpoint)
|
|
166
|
+
pg_basebackup -D /backup/base -Fp -Xs -P --checkpoint=fast
|
|
167
|
+
|
|
168
|
+
# To a remote server
|
|
169
|
+
pg_basebackup -h primary_host -U repl_user -D /backup/base -Fp -Xs -P
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
| Flag | Meaning |
|
|
173
|
+
|------|---------|
|
|
174
|
+
| `-D` | Target directory |
|
|
175
|
+
| `-Fp` | Plain format (ready-to-use data directory) |
|
|
176
|
+
| `-Ft` | Tar format (one tar per tablespace) |
|
|
177
|
+
| `-z` | Compress (with tar format) |
|
|
178
|
+
| `-Xs` | Stream WAL during backup (ensures consistency) |
|
|
179
|
+
| `-P` | Show progress |
|
|
180
|
+
| `--checkpoint=fast` | Start backup immediately (don't wait for next checkpoint) |
|
|
181
|
+
|
|
182
|
+
### Prerequisites
|
|
183
|
+
|
|
184
|
+
The connection used by `pg_basebackup` must authenticate as a superuser or a role with `REPLICATION` privilege. `max_wal_senders` must also have enough capacity for the backup connection:
|
|
185
|
+
|
|
186
|
+
```sql
|
|
187
|
+
-- On the primary
|
|
188
|
+
CREATE ROLE backup_user WITH REPLICATION LOGIN PASSWORD '...';
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
And `pg_hba.conf` must allow replication connections:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
host replication backup_user backup_server_ip/32 scram-sha-256
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
[PG15+] `--target=server:/path` writes the backup on the database server. A non-superuser using this target needs both `REPLICATION` privilege for the backup connection and membership in `pg_write_server_files` for the server-side write. `--target` is unavailable in PG14 and cannot be combined with `-Xstream`; use `-Xfetch` or `-Xnone`.
|
|
198
|
+
|
|
199
|
+
## Point-in-Time Recovery (PITR)
|
|
200
|
+
|
|
201
|
+
PITR lets you restore a database to any specific point in time — invaluable for recovering from accidental data deletion or corruption.
|
|
202
|
+
|
|
203
|
+
### How PITR Works
|
|
204
|
+
|
|
205
|
+
1. Take a **base backup** (`pg_basebackup`)
|
|
206
|
+
2. Continuously **archive WAL segments** as they're produced
|
|
207
|
+
3. To recover: restore the base backup, then **replay WAL** up to the target time
|
|
208
|
+
|
|
209
|
+
### Step 1: Configure WAL Archiving
|
|
210
|
+
|
|
211
|
+
In `postgresql.conf`:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
archive_mode = on
|
|
215
|
+
archive_command = 'cp %p /archive/wal/%f' # or use pgBackRest, barman, etc.
|
|
216
|
+
# archive_library = '' # PG15+: use archive modules instead of shell commands
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Requires a restart after enabling `archive_mode`.
|
|
220
|
+
|
|
221
|
+
Verify archiving is working:
|
|
222
|
+
|
|
223
|
+
```sql
|
|
224
|
+
SELECT * FROM pg_stat_archiver;
|
|
225
|
+
-- Check: archived_count is increasing, last_failed_time is NULL
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Step 2: Take Base Backups Regularly
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# Weekly base backup (adjust frequency based on WAL volume)
|
|
232
|
+
pg_basebackup -D /backup/base_$(date +%Y%m%d) -Ft -z -Xs -P --checkpoint=fast
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Step 3: Recovery
|
|
236
|
+
|
|
237
|
+
To recover to a specific point in time:
|
|
238
|
+
|
|
239
|
+
1. Stop PostgreSQL
|
|
240
|
+
2. Replace the data directory with the base backup
|
|
241
|
+
3. Create `recovery.signal` (PG12+) or `recovery.conf` (PG11-)
|
|
242
|
+
4. Configure recovery target in `postgresql.conf`:
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
# In postgresql.conf (PG12+):
|
|
246
|
+
restore_command = 'cp /archive/wal/%f %p'
|
|
247
|
+
recovery_target_time = '2024-06-15 14:30:00'
|
|
248
|
+
recovery_target_action = 'promote' # 'pause' to inspect before promoting
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
5. Start PostgreSQL — it replays WAL up to the target time, then promotes to read-write
|
|
252
|
+
|
|
253
|
+
### Recovery Target Options
|
|
254
|
+
|
|
255
|
+
```text
|
|
256
|
+
-- Recover to a specific time
|
|
257
|
+
recovery_target_time = '2024-06-15 14:30:00+00'
|
|
258
|
+
|
|
259
|
+
-- Recover to a specific transaction ID
|
|
260
|
+
recovery_target_xid = '12345678'
|
|
261
|
+
|
|
262
|
+
-- Recover to a named restore point
|
|
263
|
+
recovery_target_name = 'before_migration'
|
|
264
|
+
|
|
265
|
+
-- Stop as soon as a consistent state is reached
|
|
266
|
+
-- (for an online backup, normally the point where the backup ended)
|
|
267
|
+
recovery_target = 'immediate'
|
|
268
|
+
|
|
269
|
+
-- Recover to a specific WAL position
|
|
270
|
+
recovery_target_lsn = '0/1A2B3C4D'
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
To recover through the end of all available WAL, omit every `recovery_target*` setting. `recovery_target = 'immediate'` is an explicit early stopping target, so later available WAL can remain unapplied. PostgreSQL may still replay the WAL required to make an online backup consistent.
|
|
274
|
+
|
|
275
|
+
### Creating Named Restore Points
|
|
276
|
+
|
|
277
|
+
Before risky operations, create a named restore point:
|
|
278
|
+
|
|
279
|
+
```sql
|
|
280
|
+
SELECT pg_create_restore_point('before_schema_migration');
|
|
281
|
+
SELECT pg_create_restore_point('before_bulk_delete');
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
This gives you an exact target to recover to if the operation goes wrong.
|
|
285
|
+
|
|
286
|
+
## Verification and Testing
|
|
287
|
+
|
|
288
|
+
### Verify Backup Integrity
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
# List contents without restoring
|
|
292
|
+
pg_restore --list backup.dump
|
|
293
|
+
|
|
294
|
+
# Verify a plain-format physical backup (PG13+)
|
|
295
|
+
pg_verifybackup /backup/base_20240615
|
|
296
|
+
|
|
297
|
+
# PG18+: verify a tar-format backup directly
|
|
298
|
+
pg_verifybackup --no-parse-wal /backup/base_tar_20240615
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Test Restores Regularly
|
|
302
|
+
|
|
303
|
+
**A backup that hasn't been tested is not a backup.** Schedule regular restore tests:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
# Restore to a test database
|
|
307
|
+
createdb mydb_restore_test
|
|
308
|
+
pg_restore -d mydb_restore_test backup.dump
|
|
309
|
+
|
|
310
|
+
# Verify row counts
|
|
311
|
+
psql -d mydb_restore_test -c "
|
|
312
|
+
SELECT schemaname, relname, n_live_tup
|
|
313
|
+
FROM pg_stat_user_tables
|
|
314
|
+
ORDER BY n_live_tup DESC;
|
|
315
|
+
"
|
|
316
|
+
|
|
317
|
+
# Clean up
|
|
318
|
+
dropdb mydb_restore_test
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Post-Restore Checklist
|
|
322
|
+
|
|
323
|
+
After restoring a backup:
|
|
324
|
+
|
|
325
|
+
1. **Run `ANALYZE`** on all tables — optimizer statistics may be stale or missing
|
|
326
|
+
```sql
|
|
327
|
+
ANALYZE; -- all tables
|
|
328
|
+
```
|
|
329
|
+
2. **Verify sequences** — if restoring data-only, sequences may not match the data
|
|
330
|
+
```sql
|
|
331
|
+
SELECT sequencename, last_value FROM pg_sequences WHERE schemaname = 'public';
|
|
332
|
+
```
|
|
333
|
+
3. **Check for invalid indexes** — concurrent index builds may have been in progress
|
|
334
|
+
```sql
|
|
335
|
+
SELECT indexrelid::regclass, indisvalid FROM pg_index WHERE NOT indisvalid;
|
|
336
|
+
```
|
|
337
|
+
4. **Verify replication slots are clean** — stale slots from the source won't work
|
|
338
|
+
```sql
|
|
339
|
+
SELECT slot_name, active FROM pg_replication_slots;
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Automation Patterns
|
|
343
|
+
|
|
344
|
+
### Scripted Backup with Retention
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
#!/bin/bash
|
|
348
|
+
BACKUP_DIR="/backup/pg"
|
|
349
|
+
RETENTION_DAYS=30
|
|
350
|
+
DB_NAME="mydb"
|
|
351
|
+
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
352
|
+
|
|
353
|
+
# Dump
|
|
354
|
+
pg_dump -Fc -Z 6 -f "${BACKUP_DIR}/${DB_NAME}_${TIMESTAMP}.dump" "${DB_NAME}"
|
|
355
|
+
|
|
356
|
+
# Verify dump was created and is non-empty
|
|
357
|
+
if [ ! -s "${BACKUP_DIR}/${DB_NAME}_${TIMESTAMP}.dump" ]; then
|
|
358
|
+
echo "ERROR: Backup file is empty or missing" >&2
|
|
359
|
+
exit 1
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
# Clean old backups
|
|
363
|
+
find "${BACKUP_DIR}" -name "${DB_NAME}_*.dump" -mtime +${RETENTION_DAYS} -delete
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Dedicated Backup Tools
|
|
367
|
+
|
|
368
|
+
For production environments, consider purpose-built tools that handle scheduling, retention, compression, and PITR:
|
|
369
|
+
|
|
370
|
+
- **pgBackRest** — parallel backup/restore, incremental backups, S3/GCS/Azure support, built-in PITR
|
|
371
|
+
- **Barman** — backup management with retention policies, remote backup, WAL archiving
|
|
372
|
+
- **pg_probackup** — incremental backups with page-level tracking, merge, validation
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# Bulk Data Loading Reference
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
- COPY vs INSERT performance
|
|
5
|
+
- COPY FROM patterns
|
|
6
|
+
- Optimizing large loads
|
|
7
|
+
- ETL staging patterns
|
|
8
|
+
- Bulk updates and deletes
|
|
9
|
+
|
|
10
|
+
## COPY vs INSERT Performance
|
|
11
|
+
|
|
12
|
+
| Method | Rows/sec (typical) | Use case |
|
|
13
|
+
|--------|-------------------|----------|
|
|
14
|
+
| Single-row INSERT | ~1,000-5,000 | Application writes |
|
|
15
|
+
| Multi-row INSERT (VALUES) | ~10,000-50,000 | Batch inserts from code |
|
|
16
|
+
| `COPY FROM` | ~100,000-500,000+ | Bulk loading from files or streams |
|
|
17
|
+
| `COPY FROM` (binary) | ~200,000-1,000,000+ | Maximum throughput (binary format) |
|
|
18
|
+
|
|
19
|
+
`COPY` is **10-100x faster** than individual INSERTs because it bypasses per-row overhead (parsing, planning, WAL per statement).
|
|
20
|
+
|
|
21
|
+
## COPY FROM Patterns
|
|
22
|
+
|
|
23
|
+
### From a File
|
|
24
|
+
|
|
25
|
+
```sql
|
|
26
|
+
-- CSV with header
|
|
27
|
+
COPY orders FROM '/path/to/orders.csv' WITH (FORMAT csv, HEADER true);
|
|
28
|
+
|
|
29
|
+
-- Tab-delimited
|
|
30
|
+
COPY orders FROM '/path/to/orders.tsv' WITH (FORMAT text);
|
|
31
|
+
|
|
32
|
+
-- Custom delimiter
|
|
33
|
+
COPY orders FROM '/path/to/orders.dat' WITH (DELIMITER '|');
|
|
34
|
+
|
|
35
|
+
-- With NULL handling
|
|
36
|
+
COPY orders FROM '/path/to/orders.csv'
|
|
37
|
+
WITH (FORMAT csv, HEADER true, NULL '');
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### From STDIN (Piped Data)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Pipe from another command
|
|
44
|
+
cat orders.csv | psql -d mydb -c "\COPY orders FROM STDIN WITH (FORMAT csv, HEADER true)"
|
|
45
|
+
|
|
46
|
+
# Pipe from gzip
|
|
47
|
+
gunzip -c orders.csv.gz | psql -d mydb -c "\COPY orders FROM STDIN WITH (FORMAT csv, HEADER true)"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### From Application Code
|
|
51
|
+
|
|
52
|
+
Most drivers support COPY protocol for streaming data:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
# Python (psycopg 3)
|
|
56
|
+
with conn.cursor() as cur:
|
|
57
|
+
with cur.copy("COPY orders (id, customer_id, total) FROM STDIN") as copy:
|
|
58
|
+
for row in data:
|
|
59
|
+
copy.write_row(row)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
// Node.js (pg-copy-streams)
|
|
64
|
+
const { from } = require('pg-copy-streams');
|
|
65
|
+
const stream = client.query(from('COPY orders FROM STDIN WITH (FORMAT csv)'));
|
|
66
|
+
fileStream.pipe(stream);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### COPY TO (Export)
|
|
70
|
+
|
|
71
|
+
```sql
|
|
72
|
+
-- Export to CSV
|
|
73
|
+
COPY orders TO '/path/to/orders.csv' WITH (FORMAT csv, HEADER true);
|
|
74
|
+
|
|
75
|
+
-- Export query results
|
|
76
|
+
COPY (SELECT id, total FROM orders WHERE created_at > '2024-01-01')
|
|
77
|
+
TO '/path/to/recent.csv' WITH (FORMAT csv, HEADER true);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Error Handling (PG17+)
|
|
81
|
+
|
|
82
|
+
```sql
|
|
83
|
+
-- Skip invalid rows instead of failing the entire COPY
|
|
84
|
+
COPY orders FROM '/path/to/orders.csv'
|
|
85
|
+
WITH (FORMAT csv, HEADER true, ON_ERROR ignore);
|
|
86
|
+
|
|
87
|
+
-- PG18+: limit how many errors to tolerate
|
|
88
|
+
COPY orders FROM '/path/to/orders.csv'
|
|
89
|
+
WITH (FORMAT csv, HEADER true, ON_ERROR ignore, REJECT_LIMIT 100);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Optimizing Large Loads
|
|
93
|
+
|
|
94
|
+
### 1. Drop Indexes, Load, Recreate
|
|
95
|
+
|
|
96
|
+
Building indexes incrementally during COPY is much slower than building them once after:
|
|
97
|
+
|
|
98
|
+
```sql
|
|
99
|
+
-- Before load: drop non-essential indexes
|
|
100
|
+
DROP INDEX idx_orders_customer_id;
|
|
101
|
+
DROP INDEX idx_orders_created_at;
|
|
102
|
+
|
|
103
|
+
-- Load data
|
|
104
|
+
COPY orders FROM '/path/to/orders.csv' WITH (FORMAT csv, HEADER true);
|
|
105
|
+
|
|
106
|
+
-- After load: recreate indexes (concurrently if table is in use)
|
|
107
|
+
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
|
|
108
|
+
CREATE INDEX idx_orders_created_at ON orders(created_at);
|
|
109
|
+
|
|
110
|
+
-- Update statistics
|
|
111
|
+
ANALYZE orders;
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 2. Disable Triggers During Load
|
|
115
|
+
|
|
116
|
+
```sql
|
|
117
|
+
-- Disable all triggers on the table
|
|
118
|
+
ALTER TABLE orders DISABLE TRIGGER ALL;
|
|
119
|
+
|
|
120
|
+
-- Load data
|
|
121
|
+
COPY orders FROM '/path/to/orders.csv' WITH (FORMAT csv, HEADER true);
|
|
122
|
+
|
|
123
|
+
-- Re-enable triggers
|
|
124
|
+
ALTER TABLE orders ENABLE TRIGGER ALL;
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`DISABLE TRIGGER ALL` requires superuser privileges when the table has foreign-key or other internally generated constraint triggers. A table owner can use `DISABLE TRIGGER USER` to disable only user-defined triggers. Disabling constraint triggers can admit invalid data, so validate constraints before re-enabling writes.
|
|
128
|
+
|
|
129
|
+
### 3. Increase maintenance_work_mem
|
|
130
|
+
|
|
131
|
+
Larger `maintenance_work_mem` speeds up index creation after the load:
|
|
132
|
+
|
|
133
|
+
```sql
|
|
134
|
+
SET maintenance_work_mem = '1GB'; -- for the duration of the load session
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 4. Disable Autovacuum During Load
|
|
138
|
+
|
|
139
|
+
For very large bulk loads, temporarily disable autovacuum to avoid competing I/O:
|
|
140
|
+
|
|
141
|
+
```sql
|
|
142
|
+
ALTER TABLE orders SET (autovacuum_enabled = false);
|
|
143
|
+
|
|
144
|
+
-- Load data...
|
|
145
|
+
|
|
146
|
+
ALTER TABLE orders SET (autovacuum_enabled = true);
|
|
147
|
+
VACUUM ANALYZE orders;
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 5. Use Unlogged Tables for Staging
|
|
151
|
+
|
|
152
|
+
Unlogged tables skip WAL writes — 2-3x faster for writes but **data is lost on crash**:
|
|
153
|
+
|
|
154
|
+
```sql
|
|
155
|
+
CREATE UNLOGGED TABLE staging_orders (LIKE orders INCLUDING ALL);
|
|
156
|
+
|
|
157
|
+
COPY staging_orders FROM '/path/to/orders.csv' WITH (FORMAT csv, HEADER true);
|
|
158
|
+
|
|
159
|
+
-- Transform and move to the real table
|
|
160
|
+
INSERT INTO orders SELECT * FROM staging_orders;
|
|
161
|
+
|
|
162
|
+
DROP TABLE staging_orders;
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 6. Batch Size for Programmatic Inserts
|
|
166
|
+
|
|
167
|
+
When COPY isn't available, use multi-row INSERT with batches of 100-1,000 rows:
|
|
168
|
+
|
|
169
|
+
```sql
|
|
170
|
+
-- Single round-trip for 1,000 rows
|
|
171
|
+
INSERT INTO orders (customer_id, total, created_at)
|
|
172
|
+
VALUES
|
|
173
|
+
(1, 99.99, now()),
|
|
174
|
+
(2, 149.99, now()),
|
|
175
|
+
-- ... up to ~1,000 rows per statement
|
|
176
|
+
(1000, 79.99, now());
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Beyond ~1,000 rows per statement, parse overhead increases. Use COPY for larger batches.
|
|
180
|
+
|
|
181
|
+
### 7. Parallel Loading into Partitioned Tables
|
|
182
|
+
|
|
183
|
+
Load data into individual partitions concurrently from separate sessions:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# Session 1
|
|
187
|
+
psql -c "COPY events_2024_q1 FROM '/data/q1.csv' WITH (FORMAT csv)"
|
|
188
|
+
|
|
189
|
+
# Session 2 (concurrent)
|
|
190
|
+
psql -c "COPY events_2024_q2 FROM '/data/q2.csv' WITH (FORMAT csv)"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## ETL Staging Patterns
|
|
194
|
+
|
|
195
|
+
### Staging Table with Upsert
|
|
196
|
+
|
|
197
|
+
```sql
|
|
198
|
+
-- Create staging table (temporary or unlogged)
|
|
199
|
+
CREATE TEMP TABLE staging_customers (LIKE customers INCLUDING DEFAULTS);
|
|
200
|
+
|
|
201
|
+
-- Load raw data
|
|
202
|
+
COPY staging_customers FROM '/path/to/customers.csv' WITH (FORMAT csv, HEADER true);
|
|
203
|
+
|
|
204
|
+
-- Upsert into production table
|
|
205
|
+
INSERT INTO customers (id, name, email, updated_at)
|
|
206
|
+
SELECT id, name, email, now()
|
|
207
|
+
FROM staging_customers
|
|
208
|
+
ON CONFLICT (id)
|
|
209
|
+
DO UPDATE SET
|
|
210
|
+
name = EXCLUDED.name,
|
|
211
|
+
email = EXCLUDED.email,
|
|
212
|
+
updated_at = EXCLUDED.updated_at;
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Staging Table with MERGE (PG15+)
|
|
216
|
+
|
|
217
|
+
```sql
|
|
218
|
+
MERGE INTO customers AS target
|
|
219
|
+
USING staging_customers AS source
|
|
220
|
+
ON target.id = source.id
|
|
221
|
+
WHEN MATCHED AND source.name IS DISTINCT FROM target.name THEN
|
|
222
|
+
UPDATE SET name = source.name, email = source.email, updated_at = now()
|
|
223
|
+
WHEN NOT MATCHED THEN
|
|
224
|
+
INSERT (id, name, email, updated_at)
|
|
225
|
+
VALUES (source.id, source.name, source.email, now());
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Swap Table Pattern
|
|
229
|
+
|
|
230
|
+
For full-refresh loads where you replace all data:
|
|
231
|
+
|
|
232
|
+
```sql
|
|
233
|
+
-- Load into a new table
|
|
234
|
+
CREATE TABLE orders_new (LIKE orders INCLUDING ALL);
|
|
235
|
+
COPY orders_new FROM '/path/to/orders.csv' WITH (FORMAT csv, HEADER true);
|
|
236
|
+
ANALYZE orders_new;
|
|
237
|
+
|
|
238
|
+
-- Atomic swap (brief exclusive lock)
|
|
239
|
+
BEGIN;
|
|
240
|
+
ALTER TABLE orders RENAME TO orders_old;
|
|
241
|
+
ALTER TABLE orders_new RENAME TO orders;
|
|
242
|
+
DROP TABLE orders_old;
|
|
243
|
+
COMMIT;
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`LIKE ... INCLUDING ALL` does not copy foreign keys, triggers, rules, grants, row-level security policies, or publication membership. Recreate and verify those objects before swapping, or use a data-only refresh when the original table's identity and dependencies must remain unchanged.
|
|
247
|
+
|
|
248
|
+
## Bulk Updates and Deletes
|
|
249
|
+
|
|
250
|
+
### Chunked Deletes
|
|
251
|
+
|
|
252
|
+
Large DELETEs lock rows and generate WAL. This loop limits each statement to 10,000 rows:
|
|
253
|
+
|
|
254
|
+
```sql
|
|
255
|
+
-- Delete in batches of 10,000
|
|
256
|
+
DO $$
|
|
257
|
+
DECLARE
|
|
258
|
+
rows_deleted int;
|
|
259
|
+
BEGIN
|
|
260
|
+
LOOP
|
|
261
|
+
DELETE FROM audit_log
|
|
262
|
+
WHERE id IN (
|
|
263
|
+
SELECT id FROM audit_log
|
|
264
|
+
WHERE created_at < now() - interval '1 year'
|
|
265
|
+
LIMIT 10000
|
|
266
|
+
);
|
|
267
|
+
GET DIAGNOSTICS rows_deleted = ROW_COUNT;
|
|
268
|
+
EXIT WHEN rows_deleted = 0;
|
|
269
|
+
-- Optional: brief pause to reduce WAL pressure
|
|
270
|
+
PERFORM pg_sleep(0.1);
|
|
271
|
+
END LOOP;
|
|
272
|
+
END $$;
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The entire `DO` block is still one transaction: locks, WAL, and dead rows accumulate until it finishes. For true transaction-level batching, execute one limited DELETE per client transaction and commit between batches.
|
|
276
|
+
|
|
277
|
+
### Chunked Updates
|
|
278
|
+
|
|
279
|
+
The same statement-size pattern works for large updates:
|
|
280
|
+
|
|
281
|
+
```sql
|
|
282
|
+
DO $$
|
|
283
|
+
DECLARE
|
|
284
|
+
rows_updated int;
|
|
285
|
+
BEGIN
|
|
286
|
+
LOOP
|
|
287
|
+
UPDATE orders
|
|
288
|
+
SET status = 'archived'
|
|
289
|
+
WHERE id IN (
|
|
290
|
+
SELECT id FROM orders
|
|
291
|
+
WHERE status = 'completed'
|
|
292
|
+
AND created_at < now() - interval '2 years'
|
|
293
|
+
LIMIT 10000
|
|
294
|
+
FOR UPDATE SKIP LOCKED
|
|
295
|
+
);
|
|
296
|
+
GET DIAGNOSTICS rows_updated = ROW_COUNT;
|
|
297
|
+
EXIT WHEN rows_updated = 0;
|
|
298
|
+
END LOOP;
|
|
299
|
+
END $$;
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
As with the DELETE loop, the `DO` block commits only once. Drive batches from the client or a transaction-controlling procedure invoked outside an explicit transaction when each batch must commit independently.
|
|
303
|
+
|
|
304
|
+
### Partition Drop Instead of Delete
|
|
305
|
+
|
|
306
|
+
If data is partitioned by time, dropping a partition is instant vs. a slow DELETE:
|
|
307
|
+
|
|
308
|
+
```sql
|
|
309
|
+
-- Instant: drop old partition
|
|
310
|
+
ALTER TABLE events DETACH PARTITION events_2023_q1 CONCURRENTLY;
|
|
311
|
+
DROP TABLE events_2023_q1;
|
|
312
|
+
|
|
313
|
+
-- vs. slow: delete rows
|
|
314
|
+
-- DELETE FROM events WHERE occurred_at < '2024-01-01'; -- don't do this
|
|
315
|
+
```
|