본문으로 건너뛰기
cccskills
무료GitHub에 공개

postgres-migrations

Use when changing the database schema - detect the repo's migration tool, ship a new migration in its layout, never edit an applied one, and avoid lock-unsafe DDL on a live table

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

연동 도구PostgreSQL
설치 방법 보기

포함된 파일(1)

  • SKILL.md8.5 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

PostgreSQL Migrations

Overview

Migrations are append-only history that must replay identically on every environment, forever. The cardinal sin is editing a migration that has already run somewhere — it makes environments diverge silently. The second most common mistake is DDL that locks a live table for longer than the app can tolerate.

Core principle: a wrong migration is fixed by a NEW migration, never by editing the old one; and rollback_release reverts code only, so the schema your migration leaves behind must keep working with the previous release's code until a later task removes what it replaced.

Detect the repo's migration tool first

ToolHow to recognise itDown migrationOutside a transaction
golang-migrate*.up.sql / *.down.sql pairsits own .down.sql fileeach CONCURRENTLY statement needs its own migration file (no implicit transaction wrapping across files)
goose-- +goose Up / -- +goose Down in one filesame file, Down section-- +goose NO TRANSACTION directive
FlywayV<n>__name.sqlnone in the open-source editionfollow Flyway's own non-transactional DDL rules
Liquibasechangesets (XML/YAML/SQL)rollback blockper changeset runInTransaction
Atlasatlas.hcl + versioned migrations——
Up-only numbered SQLplain numbered .sql files, no down files (e.g. this repo's own server/migrations/)none exists — don't invent one—

Never introduce a different migration tool into a repo that already has one, and never add down files to a repo that has none — follow what's there (rule repo-conventions-win).

Rules

  • Never edit an applied migration. History replays; a divergent edit corrupts environments that already ran it. A wrong migration gets a new one that corrects it.
  • Never rely on Hibernate/JPA auto-DDL (hbm2ddl=update, Quarkus schema generation) outside tests — schema comes from migrations only (java-persistence).
  • Idempotent where it matters: IF NOT EXISTS, ON CONFLICT, WHERE guards on backfills so a re-run does no harm.
  • Add an explicit index for every new query path the code introduces — Postgres does not auto-index foreign-key columns.
  • Start DDL in a live-table migration with SET lock_timeout = '5s'; so a blocked ALTER fails fast and visibly instead of queueing behind other locks and blocking every other statement on the table.

Rollback reality: what rollback_release actually does

rollback_release reverts the application code only — it never runs a down migration. Two consequences:

  1. Expand/contract. Ship additive changes first (new nullable/defaulted column, new table, dual-write) so the schema works with both the old and new code; drop what's no longer needed in a later task once the old code is gone everywhere.
  2. rollback_plan must say whether the down migration is actually safe to run, because by the time anyone reaches for it, the new schema may already hold data the down migration would silently drop. Write what you'd tell the human, e.g. "Migration 045 is additive; reverting the code is enough. Do NOT run its down migration — it drops the priority column and any values written to it since deploy." A migration that is purely additive needs no down-migration caveat at all; say that instead.
  3. golang-migrate caveat: if a commit is reverted and that removes migration 045's files, a database already at version 45 then fails migrate up with "no migration found for version 45" — decide whether to keep the schema (most common) and run migrate ... force 44 to re-sync the tool's bookkeeping, rather than trying to re-apply a file that no longer exists.

Lock-safety: unsafe vs safe DDL on a live table

ChangeUnsafe (locks / rewrites the table)Safe
Add indexCREATE INDEXCREATE INDEX CONCURRENTLY IF NOT EXISTS (its own migration, outside a transaction)
Foreign keyADD CONSTRAINT ... FOREIGN KEY ... (validates existing rows under lock)... NOT VALID, then VALIDATE CONSTRAINT in a later migration
NOT NULL on an existing columnALTER COLUMN ... SET NOT NULL (full scan under lock on PG <12)CHECK (col IS NOT NULL) NOT VALID → VALIDATE CONSTRAINT → SET NOT NULL (PG ≥12 skips the re-scan once validated); PG 18 also has SET NOT NULL ... NOT VALID directly
Add columnA volatile default (DEFAULT now(), DEFAULT random()) rewrites the tableA constant default is metadata-only since PG 11; for a volatile default, add the column nullable, backfill, then add the default/constraint
Change column typeALTER COLUMN ... TYPE ... (table rewrite)New column + backfill + switch reads/writes, drop the old one later
Rename or drop column/tableRENAME breaks the previous release's code immediatelyExpand/contract: add the new name, dual-write/read, switch, drop later
Backfill existing rowsA single large UPDATE (long lock, huge transaction)Batched UPDATE ... WHERE id IN (SELECT id FROM t WHERE ... LIMIT 5000), looped, outside the DDL transaction

Types and keys (new tables; existing tables follow the repo's own conventions)

timestamptz (never bare timestamp); text with a CHECK constraint instead of varchar(n); bigint generated always as identity or uuid DEFAULT uuidv7() (PG 18+, time-ordered) / gen_random_uuid(); numeric for money, never float/money; UNIQUE NULLS NOT DISTINCT where NULL should count toward the uniqueness check (PG 15+).

Worked Example

Adding a required priority column with an index, on golang-migrate:

-- 043_task_priority.up.sql
ALTER TABLE tasks ADD COLUMN priority TEXT NOT NULL DEFAULT 'medium';

-- 043_task_priority.down.sql
ALTER TABLE tasks DROP COLUMN IF EXISTS priority;
-- 044_task_priority_index.up.sql
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_priority ON tasks (priority);

-- 044_task_priority_index.down.sql
DROP INDEX CONCURRENTLY IF EXISTS idx_tasks_priority;

NOT NULL ... DEFAULT 'medium' is a constant default: metadata-only on PG 11+, no table rewrite, every existing row reads as 'medium'. The index is a separate migration because CREATE INDEX CONCURRENTLY cannot run inside the transaction golang-migrate wraps each file in. rollback_plan: "045/044 are additive; reverting the code is enough, do not run the down migrations unless the column is confirmed unused."

❌ CREATE INDEX idx_tasks_priority ON tasks (priority); inside the same migration as the column add — locks the table for the full index build, and squawk's require-concurrent-index-creation flags exactly this.

Verify

  • Lint: npx --yes squawk-cli <new-file>.sql (or pip install squawk-cli) — fix or explicitly justify every warning it raises (rule IDs like require-concurrent-index-creation, constraint-missing-not-valid, adding-required-field, changing-column-type, renaming-column, ban-drop-column, prefer-identity, prefer-text-field, prefer-timestamptz, require-timeout-settings double as a checklist even without running the tool).
  • Apply up → down → up (or the repo's own migration test) against a disposable postgres:18 container before calling it done.
  • psql -c '\d+ <table>' to confirm the resulting schema matches what you intended.

Common Mistakes

  • Editing an already-applied migration to "fix" it.
  • CREATE INDEX (not CONCURRENTLY) on a table already receiving traffic.
  • A new query path with no supporting index.
  • A non-idempotent backfill that double-applies on re-run, or one large UPDATE instead of batches.
  • rollback_plan left blank on a migration task, or claiming the down migration is safe without checking whether data was written since deploy.
  • hbm2ddl/auto-DDL thinking — schema comes from migrations only.

Red Flags

  • The diff modifies an existing migration file instead of adding a new one.
  • A NOT NULL or type-change migration with no expand/contract story and no mention in rollback_plan.
  • A column or table rename with no plan for the previous release's code still running against it.
  • squawk warnings left unaddressed with no comment explaining why.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 21일 업데이트

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 21일 업데이트

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 21일 업데이트

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 21일 업데이트

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 21일 업데이트

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 21일 업데이트

makifbaysal의 스킬 모두 보기

이 스킬의 문제 신고