Skip to content
Back to skills

Postgresql

ASecurity

Design PostgreSQL schemas, indexes, and safe online migrations; diagnose locks and slow queries with EXPLAIN; configure pooling, auth, and backup/restore without PgBouncer or invalid-index traps.

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 22, 2026
ai-agentsrustgosqlrailstestinggitdatabasebackenddevops

Works with

  • cursor
  • cli

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned October 22, 2026

npx -y skills add alivirgo/Major-AI-Skills --skill postgresql --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Postgresql?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Postgresql
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/alivirgo-postgresql/badge)](https://www.skillsdirectory.com/skills/alivirgo-postgresql)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: postgresql
description: "Design PostgreSQL schemas, indexes, and safe online migrations; diagnose locks and slow queries with EXPLAIN; configure pooling, auth, and backup/restore without PgBouncer or invalid-index traps."
category: devops
risk: safe
source: self
source_type: self
date_added: "2026-08-26"
tags: ["postgresql", "sql", "indexes", "migrations", "pgbouncer", "vacuum", "backup", "connection-pooling", "explain"]
tools: ["claude", "cursor", "gemini", "codex"]
---

# PostgreSQL Administration & SQL AI Skill Guide

## Overview & Engine Architecture

PostgreSQL is a relational database with **MVCC**: updates create new row versions; dead tuples remain until **VACUUM**. Writers never block readers on row data, but **DDL and some migrations take strong locks**. Connection storms are a common production failure mode—apps should pool (PgBouncer, RDS Proxy, or app pool) and cap `max_connections`.

```
App / ORM
    -> PgBouncer (session vs transaction pool) OR app pool
        -> Postmaster
            -> backends (one per session)
            -> shared_buffers + WAL
            -> heap / TOAST / indexes (B-tree, GIN, GiST, BRIN)
            -> autovacuum + pg_stat_statements
```

---

## When to use / when not to

**Use when**

- Designing tables, constraints, and indexes for real query shapes
- Planning **online** index or schema changes on production data
- Diagnosing lock waits, bloat, or slow plans with `EXPLAIN (ANALYZE, BUFFERS)`
- Configuring roles, TLS, and backup/restore for managed or self-hosted Postgres

**Do not use when**

- The workload is append-only analytics at huge scale (consider `@clickhouse`, `@duckdb`, `@snowflake`)
- You need flexible document nesting without joins (`@mongodb`)
- Someone asks to disable SSL verification or commit superuser passwords to git—refuse and use secrets managers

---

## Operational Capabilities & Agent Directives

1. **Connection safety**: Size pools to `(cores * 2–4)` on the DB side, not “one connection per request.” Never run migrations through **PgBouncer transaction pooling** without session pool or direct DB URL—advisory locks break.
2. **Migration safety**: `CREATE INDEX CONCURRENTLY` and `DROP INDEX CONCURRENTLY` **cannot run inside a transaction block**. One statement per migration when DDL transactions are disabled; drop invalid indexes before retry—never rely on `IF NOT EXISTS` after a failed concurrent build.
3. **Index discipline**: Index for equality filters first, then sort keys, then range; use partial indexes for hot subsets. Prefer `CONCURRENTLY` in prod; validate with `pg_index.indisvalid`.
4. **Lock awareness**: Long transactions block vacuum and concurrent index builds. Use `lock_timeout` / `statement_timeout` on migration roles; expand/contract for column type changes.
5. **Auth**: Least-privilege roles; separate migration role from app role. TLS to server; never embed passwords in repo connection strings.
6. **Backup/restore**: Understand **PITR** (WAL archiving) vs logical dumps (`pg_dump`). Restoring a dump to prod without rename/isolation destroys data—require explicit approval and restore targets.
7. **Observability**: Enable `pg_stat_statements`; correlate `pg_locks`, `pg_stat_activity`, and `EXPLAIN` on representative data volumes—not empty dev DBs.

---

## Production examples

### Schema + partial index (online)

```sql
CREATE TABLE orders (
  id            bigserial PRIMARY KEY,
  customer_id   bigint NOT NULL REFERENCES customers(id),
  status        text NOT NULL,
  total_cents   integer NOT NULL CHECK (total_cents >= 0),
  created_at    timestamptz NOT NULL DEFAULT now()
);

-- Production: CONCURRENTLY, outside a transaction wrapper
DROP INDEX CONCURRENTLY IF EXISTS orders_customer_created_idx;
CREATE INDEX CONCURRENTLY orders_customer_created_idx
  ON orders (customer_id, created_at DESC)
  WHERE status <> 'cancelled';
```

### Safe migration pattern (expand/contract)

1. Add nullable column or new table; deploy app that dual-writes.
2. Backfill in keyed batches (`WHERE id > $last LIMIT 5000`) with short transactions.
3. Switch reads to new column; deploy.
4. Drop old column in a later release with `CONCURRENTLY` index swaps as needed.

### Connection string hygiene (app role)

```text
postgresql://app_ro:***@db.internal:5432/app?sslmode=verify-full&connect_timeout=10&statement_timeout=30000
```

Migrations: use **direct** Postgres or PgBouncer **session** mode URL, not transaction pool.

### Diagnosis

```sql
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT customer_id, sum(total_cents)
FROM orders
WHERE created_at >= now() - interval '30 days'
GROUP BY customer_id;

SELECT pid, wait_event_type, wait_event, query
FROM pg_stat_activity
WHERE state <> 'idle' AND query NOT LIKE '%pg_stat_activity%';

SELECT indexrelid::regclass, indisvalid
FROM pg_index
WHERE NOT indisvalid;
```

---

## Technical troubleshooting matrix

| Failure signature | Root cause | Diagnostic & fix |
| :--- | :--- | :--- |
| `CREATE INDEX CONCURRENTLY cannot run inside a transaction block` | ORM migration wrapped in transaction | Disable DDL transaction for that migration only; single statement |
| Migration “succeeds” but queries slow | **Invalid** index left after timeout | `pg_index.indisvalid = false`; `DROP INDEX CONCURRENTLY` then recreate |
| `ConcurrentMigrationError` / stuck advisory lock | PgBouncer **transaction** pooling | Session pool for migrations; kill stray `advisory` locks |
| `too many connections` | Unbounded app pools | PgBouncer; lower per-service pool; raise `max_connections` only after math |
| Rising dead tuples / bloat | Long txs / autovacuum blocked | Find blockers in `pg_stat_activity`; tune autovacuum; avoid long `REPEATABLE READ` reports |
| Seq Scan on large table | Missing or invalid index | Add partial/covering index; `ANALYZE`; check casts/wrappers on indexed columns |
| Restore “worked” but app broken | Restored over production or wrong timeline | Restore to new cluster/DB; repoint apps; verify extensions and roles |

---

## Best practices

- Use **`pg_stat_statements`** for top queries; fix plans before buying bigger hardware.
- Set **`idle_in_transaction_session_timeout`** on app roles to prevent connection leaks holding locks.
- Prefer **keyset pagination** (`WHERE id > $cursor ORDER BY id LIMIT n`) over large `OFFSET`.
- For FKs: index the referencing column; understand `VALIDATE CONSTRAINT` vs full table lock on add.
- Managed Postgres: confirm extension allowlist, parameter groups, and whether `CONCURRENTLY` is permitted from your migration runner.

---

## Limitations

- Logical replication, partitioning, and citus-style sharding need dedicated runbooks—not one-size fits all.
- ORM migrations often hide lock duration; always review generated SQL for table rewrites.
- This skill does not replace storage/IOPS capacity planning or HA failover testing.

---

## Related skills

- `@supabase` — Postgres product surface with RLS
- `@prisma` — ORM migrations still need SQL review
- `@redis` — cache-aside in front of hot reads
- `@vault` — dynamic DB credentials

---

## Agent Operational Directive

> **MANDATORY**: Never run production DDL inside a transaction that includes `CONCURRENTLY`. Never route schema migrations through PgBouncer transaction pooling without an explicit session-pool path. After any failed concurrent index build, check for invalid indexes before marking migrations complete. Do not put credentials in git or suggest `sslmode=disable` on untrusted networks.

---

## Source anchors (research)

- [PostgreSQL CREATE INDEX CONCURRENTLY](https://www.postgresql.org/docs/current/sql-createindex.html)
- [Postgres Migration Safety Handbook — CONCURRENTLY in transactions](https://migrationpilot.dev/handbook/concurrently-inside-transaction)
- [Heroku — PgBouncer advisory locks and migrations](https://help.heroku.com/UYH8N2WW/why-do-i-receive-activerecord-concurrentmigrationerror-when-running-rails-migrations-using-pgbouncer)
- [Flyway #2895 — transaction pooling vs advisory locks](https://github.com/flyway/flyway/issues/2895)
- [Invalid index trap with IF NOT EXISTS (2024)](https://www.shayon.dev/post/2024/225/stop-relying-on-if-not-exists-for-concurrent-index-creation-in-postgresql/)

Files in this skill

  • SKILL.md8.1 KB
  • gemini.md2 KB
  • gpt.md2.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.