scary-production/claude-plugins

Migraciones SQL de PostgreSQL: archivos up/down emparejados, tipos modernos y restricciones, reversión de cada operación, índices concurrentes sin bloqueo de escritura.

¿Qué es claude-plugins?

Este skill especializado para agentes de IA (Claude Code, Cursor, Codex, etc.) proporciona una guía completa sobre migraciones de bases de datos PostgreSQL. Enseña cómo estructurar migraciones en pares de archivos up/down, garantizando que cada cambio sea reversible. Se enfoca en el uso de tipos modernos como `bigint GENERATED ALWAYS AS IDENTITY`, `text` con restricciones CHECK, `timestamptz` y `jsonb`, así como en la gestión de índices en tablas activas mediante `CREATE INDEX CONCURRENTLY`, que evita bloqueos de escritura. También cubre requisitos clave del migrador (transacciones, bloqueo de ejecución paralela, modo sin transacción) y la resolución del problema común 'índice creado, pero plan Seq Scan'. Es útil para desarrolladores que buscan implementar migraciones seguras, eficientes y reversibles en entornos de producción.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/scary-production/claude-plugins/tree/HEAD/plugins/go-postgres/skills/postgres-migrations

Installed? Explore more Investigación y análisis de datos skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Migraciones PostgreSQL

Importante. Cada migración es un par up/down, y down revierte completamente up. La migración se ejecuta en su propia transacción. Hay una excepción: el índice en una tabla que ya tiene datos y está recibiendo escrituras — CREATE INDEX CONCURRENTLY en una migración separada sin transacción, un solo comando por archivo.

La verificación de afirmaciones está en CHECKS.md.

Formato de archivos

migrations/
├── 20250524120000_create_users.up.sql
├── 20250524120000_create_users.down.sql
├── 20250525090000_add_users_phone.up.sql
└── 20250525090000_add_users_phone.down.sql
  • Prefijo: hora UTC YYYYMMDDHHmmss, nombre: descripción breve en snake_case.
  • *.up.sql aplica, *.down.sql revierte. Ambos archivos son obligatorios.
  • No modifiques una migración ya aplicada — escribe una nueva.

Tipos y restricciones

En lugar deEscribePor qué
bigserialbigint GENERATED ALWAYS AS IDENTITYestándar SQL; BY DEFAULT si necesitas insertar tus propios ids (importación)
varchar(n)text NOT NULL CHECK (length(x) <= n)el límite es una regla de datos, cambiarlo es más sencillo
timestamptimestamptzalmacena un instante en el tiempo, no "hora de pared"
flag integerboolean
jsonjsonbformato binario, índices y operadores
uuid de extensiónuuid DEFAULT gen_random_uuid()incorporado desde PostgreSQL 13

Limita la longitud de columnas de texto con CHECK siempre, excepto para texto deliberadamente largo (descripción, comentario). Si la longitud máxima no es obvia, pregunta al usuario.

DatosRestricción
emailCHECK (length(email) <= 320)
nombreCHECK (length(name) <= 255)
teléfonoCHECK (length(phone) <= 50)
slug, códigoCHECK (length(code) <= 100)
URLCHECK (length(url) <= 2048)
descripciónsin restricción o CHECK (length(bio) <= 10000)

Plantilla de tabla:

CREATE TABLE users (
    id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name       text        NOT NULL CHECK (length(name) <= 255),
    email      text        NOT NULL CHECK (length(email) <= 320),
    is_active  boolean     NOT NULL DEFAULT true,
    metadata   jsonb,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now()
);

CREATE UNIQUE INDEX ix_users_email ON users (email);

Reversión

updown
CREATE TABLE tDROP TABLE IF EXISTS t
ALTER TABLE t ADD COLUMN cALTER TABLE t DROP COLUMN IF EXISTS c
CREATE INDEX ixDROP INDEX IF EXISTS ix
CREATE INDEX CONCURRENTLY ixDROP INDEX CONCURRENTLY IF EXISTS ix — también sin transacción
CREATE TYPE t AS ENUM (...)DROP TYPE IF EXISTS t
ALTER TYPE t ADD VALUE 'v'No existe DROP VALUE en PostgreSQL — recrear el tipo o down vacío
ALTER TABLE t ALTER COLUMN c SET NOT NULLALTER TABLE t ALTER COLUMN c DROP NOT NULL
ALTER TABLE t ADD CONSTRAINT cALTER TABLE t DROP CONSTRAINT IF EXISTS c

IF EXISTS en down para que la reversión sea idempotente.

Cambios típicos

Columna con valor por defecto. Con un DEFAULT constante, la tabla no se reescribe (PostgreSQL 11+), el comando es instantáneo. Con un default volátil (gen_random_uuid(), clock_timestamp()), la tabla se reescribe completamente bajo un bloqueo exclusivo — en una tabla grande y activa, agrega la columna sin default y rellénala por lotes.

ALTER TABLE users ADD COLUMN role text NOT NULL DEFAULT 'user' CHECK (length(role) <= 50);

Clave foránea — crea un índice sobre la columna referenciada de inmediato: PostgreSQL no lo crea automáticamente, y sin él, las eliminaciones de padres y los joins se hacen por escaneo secuencial.

CREATE TABLE orders (
    id      bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    user_id bigint NOT NULL REFERENCES users (id),
    total   numeric NOT NULL
);
CREATE INDEX ix_orders_user_id ON orders (user_id);

Agregar valor a un enum. ALTER TYPE ... ADD VALUE funciona dentro de una transacción, pero el nuevo valor no se puede usar hasta después del commit (55P04 unsafe use of new value). Si el migrador ejecuta la migración en una transacción, agrega el valor y úsalo (UPDATE, DEFAULT, CHECK) en migraciones separadas.

ALTER TYPE payment_method ADD VALUE 'bank_transfer';

down: No existe DROP VALUE. El valor es inofensivo — deja down vacío con un comentario; si no lo es — recrea el tipo (nuevo tipo, ALTER COLUMN ... TYPE ... USING, eliminar el anterior).

Índices en tablas activas

Si la tabla ya tiene datos y está recibiendo escrituras, usa CREATE INDEX CONCURRENTLY (CIC). Un CREATE INDEX normal bloquea las escrituras durante toda la construcción del índice. Si el índice se crea junto con la tabla en la misma migración, usa CREATE INDEX normal. El tamaño de la base no importa: una transacción pendiente puede romper el índice nuevo sin importar el tamaño (ver abajo).

up:

CREATE INDEX CONCURRENTLY ix_orders_customer_id ON orders (customer_id);

down:

DROP INDEX CONCURRENTLY IF EXISTS ix_orders_customer_id;
  • Migración separada sin transacción, un solo comando por archivo. Dentro de un bloque de transacción, CIC está prohibido ("cannot run inside a transaction block"). Varios comandos enviados en una sola solicitud son ejecutados por PostgreSQL como una transacción implícita, y CIC falla con el mismo error.
  • Si el migrador no soporta migraciones sin transacción, no pongas CIC en una migración normal. Indica al usuario que se necesita este modo (sección "Qué exigir al migrador"), o ejecuta CIC por separado y registra la migración manualmente.
  • CIC espera a que terminen todas las transacciones con instantáneas antiguas, incluso aquellas que solo leen otras tablas. La migración puede tomar mucho tiempo.
  • Después de aplicar, verifica el índice. CIC no es transaccional: si falla, queda un índice INVALID, y la migración en sí no lo mostrará.
SELECT c.relname, i.indisvalid, i.indcheckxmin
FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid
WHERE c.relname = 'ix_orders_customer_id';
  • indisvalid = false — ejecuta DROP INDEX CONCURRENTLY IF EXISTS y vuelve a crearlo, o REINDEX INDEX CONCURRENTLY.
  • Reintentar una migración CIC fallida. Sin IF NOT EXISTS, fallará con "relation already exists" sobre el índice INVALID. Con IF NOT EXISTS es peor: el comando omitirá silenciosamente el índice INVALID y la migración "pasará". Por lo tanto, no escribas IF NOT EXISTS en CIC: primero elimina el índice inválido y luego reintenta.

El índice existe, pero el plan es Seq Scan

El índice es válido, pero el planificador no lo usa: verifica indcheckxmin (consulta anterior). Si es true: las consultas no usarán el índice mientras existan transacciones iniciadas antes de su creación. La causa es un UPDATE: quedaron cadenas de versiones HOT en la tabla a las que el índice no hace referencia. Solo INSERT no produce esto. No recrees el índice ni busques la causa en las estadísticas. Busca transacciones antiguas:

SELECT pid, xact_start, backend_xmin, state, left(query, 60) AS query
FROM pg_stat_activity
WHERE backend_xmin IS NOT NULL
ORDER BY xact_start;

Espera a que terminen o finalízalas (pg_terminate_backend(pid)) — solo después de acordarlo con el usuario. Para reproducir antes de un lanzamiento: en una sesión, una transacción abierta con repeatable read leyendo la tabla; en otra, un UPDATE de filas y un CREATE INDEX; indcheckxmin se volverá true.

Qué exigir al migrador

  • Cada migración en su propia transacción, junto con el registro en el diario de migraciones aplicadas: una fallida se revierte por completo, las ya aplicadas permanecen.
  • Bloqueo contra ejecución paralela (por ejemplo, advisory lock): réplicas que se inicien juntas no aplican lo mismo dos veces.
  • Modo sin transacción para migraciones individuales — una marca en el archivo (por ejemplo, primera línea -- migrate:no-transaction). Dicho archivo se envía como un solo comando, fuera de la transacción. Costo: la aplicación y el registro en el diario ya no son atómicos. Si el proceso falla entre ambos, el reintento fallará con "already exists"; verifica indisvalid y, si el índice es válido, registra la migración manualmente en el diario.
  • Creación del par de archivos sin conexión a la base de datos.

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

scary-production/claude-plugins

Работа с PostgreSQL в Go через pgxpool (pgx v5) и avito-tech/go-transaction-manager: пул и устойчивый старт, структуры с тегами db и CollectRows, репозиторий через DefaultTrOrDB, транзакции через Do, propagation и savepoint, повтор транзакции при 40001/40P01/55P03, защита от SQL-инъекций, классификация ошибок и ON CONFLICT, batch и CopyFrom. Конкурентная запись и блокировки — скилл postgres-concurrency. PostgreSQL in Go via pgxpool and go-transaction-manager: pool, repositories, transactions, retries, errors, bulk loads.

scary-production/claude-plugins

Конкурентная запись в PostgreSQL без потерянных обновлений и write skew: условие в UPDATE, SELECT FOR NO KEY UPDATE против FOR UPDATE, exclusion-ограничения против пересечения интервалов, serializable и повтор при 40001/40P01/55P03, очередь задач на SKIP LOCKED, lock_timeout и NOWAIT, advisory-блокировки, разбор зависаний. Примеры — Go (pgx и go-transaction-manager), правила — про сам PostgreSQL. PostgreSQL concurrency: lost updates, write skew, row locks, exclusion constraints, isolation and retries, SKIP LOCKED queues, advisory locks.

scary-production/claude-plugins

Как найти, что тормозит в PostgreSQL, и доказать, что правка помогла. Профиль по pg_stat_statements, сравнение вариантов по числу страниц (BUFFERS), а не по секундам, разбор плана от места, где оценка строк впервые разошлась с фактом. N+1 из Go-цикла сворачиваем в один запрос. Правим условия, FK, статистику и память, а не боремся с планировщиком. Postgres query tuning: profile first, compare by buffers, fix row estimates, remove N+1.

scary-production/claude-plugins

Показывает стили строки состояния на примере или включает один из них (full, compact, bars, minimal) в настройках пользователя или проекта.

Skills relacionados