0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

初期データ投入の攻略ガイド: SeedとMigrationのちょうどいい関係

Posted at

はじめに

本記事は個人開発でGo言語を使用してAPI開発をした際のコミットした内容に基づいて生成AIから文章を生成した内容を一部書き加えたものになります
MigrationとSeeed、仲良くやるコツをサクッと整理します

TL;DR

  • 役割分担: Migrationはスキーマ+最低限の必須データ、Seedは環境初期化やサンプル
  • 再実行性: Seedは必ずidempotent(何度でも安全)
  • up/downの原則: upは「足す」、downは「upで足した分だけ消す」
  • 順序大事: 親テーブル → 子テーブル → 必須データ → (任意)Seed
  • 環境分離: サンプルは本番に混ぜない dev用Seedを分ける

Seed と Migration の線引き

  • Migration 行き: そのバージョンに必須の最小データ(例: 初期ロール、設定フラグ、管理者1件)
  • Seed 行き: デモ/サンプル/検証用や環境依存(dev/stg のみ流す)
  • 迷ったら: 「本番で必須?」と「再実行して壊れない?」の2軸で判定

基本ルール

  • 再実行OK: ON CONFLICT DO NOTHING または WHERE NOT EXISTS (...)
  • 限定的down: upで入れた行を一意に特定してDELETE
  • 環境分離: db/seeds/dev/* などに分け、本番はSeedを流さない
  • トランザクション: 1ファイル=1トランザクション(ツール依存、推奨)

SQLスニペット

  • 必須データをMigrationで入れる(存在チェック)
  INSERT INTO example_records (example_key, happened_at)
  SELECT 'ex-001','2024-07-01 18:30:00'
  WHERE NOT EXISTS (
    SELECT 1 FROM example_records
    WHERE example_key='ex-001'
      AND happened_at='2024-07-01 18:30:00'
  );
  • ロールバック(upの分だけ消す)
DELTE FROM ecample_records
WHERE example_key = 'ex-001'
    AND happened_at = '2024-07-01 18:30:00';
  • 開発用 Seed(PostgreSQLの UPSERT で再実行OK)
     先に一意制約を作ること
-- 初回に一意制約(またはユニークインデックス)
CREATE UNIQUE INDEX IF NOT EXISTS ux_example_records_key_at
    ON example_records(example_key, happened_at);

-- 開発データの投入(競合時は何もしない)
INSERT INTO example_records(example_key, happened_at)
VALUES ('dev-001','2024-07-02 18:45:00')
  ON CONFLICT (example_key, happened_at) DO NOTHING;

EXISTSの SELECT 1って何?

  • 意味: EXISTS/NOT EXISTSは「行の有無」だけを見る 列値は評価に使わないためSELECT 1でもSELECT *でも同じ
  • 動作: 条件に合う行を1件でも見つけた地点で真/偽が決まる(短絡評価)
  • 余談: 多くのRDBMSで実行結果は同等 読みやすさ優先でOK

Migrationの流し方

ここではgolang-migrateを想定 マイグレーションはdb/migrations配下に置く

前提 DSN

  • Docker ネットワーク越し: postgres://postgres:password@db:5432/example_records?sslmode=disbale
  • ローカル接続: postgres://postgres:password@localhost:5432/example_records?sslmode=disable

DBの設定(docker-compose.yaml)

docker-compose.yaml
services:
  db:
    image: postgres:15
    ports:
      - "${POSTGRES_PORT:-5432}:5432"
    environment:
      POSTGRES_DB: example_records
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    volumes:
      - db_data:/var/lib/postgresql/data
    restart: always

volumes:
  db_data:

DB起動

docker compose up -d db

Docker で migrate(おすすめ)

  • 全て上げる
docker run --rm \
-v "$PWD/db/migrations":/migrations \
--network $(basename "$PWD")_default \
migrate/migrate -path=/migrations \
-database "postgres://postgres:password@db:5432/examle_records?sslmode=disable" up
  • 全て戻す
docker run --rm \
-v "$PWD/db/migrations":/migrations \
--network $(basename "$PWD")_default \
migrate/migrate -path=/migrations \
-database "postgres://postgres:password@db:5432/examle_records?sslmode=disable" down
  • 1本だけ上げる / 1本だけ戻す
# 進める
docker run --rm -v "$PWD/db/migrations":/migrations --network $(basename "$PWD")_default \
migrate/migrate -path=/migrations -database "postgres://postgres:password@db:5432/examle_records?sslmode=disable" up 1
# 戻す
docker run --rm -v "$PWD/db/migrations":/migrations --network $(basename "$PWD")_default \
migrate/migrate -path=/migrations -database "postgres://postgres:password@db:5432/examle_records?sslmode=disable" down 1
  • バージョン確認 / goto / force
docker run --rm -v "$PWD/db/migrations":/migrations --network $(basename "$PWD")_default \
migrate/migrate -path=/migrations -database "postgres://postgres:password@db:5432/examle_records?sslmode=disable" version

docker run --rm -v "$PWD/db/migrations":/migrations --network $(basename "$PWD")_default \
migrate/migrate -path=/migrations -database "postgres://postgres:password@db:5432/examle_records?sslmode=disable" goto 20250730134221

docker run --rm -v "$PWD/db/migrations":/migrations --network $(basename "$PWD")_default \
migrate/migrate -path=/migrations -database "postgres://postgres:password@db:5432/examle_records?sslmode=disable" force <version>
  • Linux なら host ネットワークで簡易実行
docker run --rm --network host \
-v "$PWD/db/migrations":/migrations \
migrate/migrate -path=/migrations \
-database "postgres://postgres:password@localhost:5432/examle_records?sslmode=disable" up

ローカル CLI(migrate を入れてる場合)

# macOS なら brew install golang-migrate
migrate -path db/migrations -database "postgres://postgres:password@localhost:5432/examle_records?sslmode=disable" up
migrate -path db/migrations -database "postgres://postgres:password@localhost:5432/examle_records?sslmode=disable" down 1
migrate -path db/migrations -database "postgres://postgres:password@localhost:5432/examle_records?sslmode=disable" goto 20250730134221
migrate -path db/migrations -database "postgres://postgres:password@localhost:5432/examle_records?sslmode=disable" force <version>

新しいマイグレーション作成

  • Docker で生成
    docker run --rm \
      -v "$PWD/db/migrations":/migrations \
      -u $(id -u):$(id -g) \
      migrate/migrate create -dir /migrations -ext sql -format "20060102150405" add_example_feature
    
  • ローカルで生成
    migrate create -ext sql -dir db/migrations -format "20060102150405" add_example_feature
    
  • 生成物
    • YYYYMMDDHHMMSS_add_example_feature.up.sql
    • YYYYMMDDHHMMSS_add_example_feature.down.sql

Seed の流し方(本番は非推奨、dev だけ)

  • Docker の psql で実行
    docker run --rm -i \
      --network $(basename "$PWD")_default \
      -v "$PWD/db/seeds/dev":/seeds \
      postgres:15 psql "postgres://postgres:password@db:5432/examle_records?sslmode=disable" \
      -f /seeds/001_example_seed.sql
    
  • ローカル psql で実行
    psql "postgres://postgres:password@localhost:5432/examle_records?sslmode=disable" \
      -f db/seeds/dev/001_example_seed.sql
    

よくある落とし穴

  • up/down の逆転: up で DELETE、down で INSERT は逆
  • 重複発生: 一意キーが無いまま INSERT を繰り返す
  • 依存逆順: 親より先に子テーブル作成や FK 付与
  • 本番汚染: サンプルを本番 Migration に混ぜる

まとめ

  • Migration は「歴史(スキーマ+最小限の必須データ)」、Seed は「初期化スクリプト(何度でもOK)」
  • 重複防止は DB の一意制約と UPSERT で堅く。WHERE NOT EXISTS は補助的に
  • 迷ったら「本番に必要?」と「再実行しても壊れない?」の 2 軸で切り分け
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?