database-seeding について
database-seeding は、開発・テスト・ステージング環境向けのテストデータを AI エージェントが生成・投入するためのスキルです。スキーマの関係や制約を考慮し、繰り返し実行できるスクリプトと環境別のデータ量に対応します。
主な機能
- 01スキーマと制約の分析
- 02外部キー依存に沿った投入順序
- 03現実的または決定的なデータ生成
- 04再実行可能なシードスクリプト作成
- 05環境別のデータ量設定
- 06行数とリレーション整合性の検証
活用シーン
- 01ローカル開発用データベースへのデータ投入
- 02再現可能な統合テストデータセットの作成
- 03性能テスト用の大規模データ準備
- 04匿名化データを使ったステージング環境の構築
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ seb1n/ awesome-ai-agent-skills --skill database-seedingインストール後、Claude Code で/database-seedingを入力して読み込まれたか確認します。
詳細
データベースのシーディング
このスキルを使うと、AI エージェントは開発、テスト、ステージング環境向けの現実的なテストデータを生成し、データベースに挿入できます。エージェントは、決定論的なジェネレーターや Faker ライブラリを使って冪等性のあるシードスクリプトを作成し、適切な外部キーの順序でリレーショナルデータを処理します。また、環境ごとのシードプロファイル(最小限の開発用データと大規模な負荷テスト用データ)に対応し、データが重複することなくシードを繰り返し実行できるようにします。
ワークフロー
-
対象スキーマを分析する: データベーススキーマを調査し、すべてのテーブル、列、データ型、制約(NOT NULL、UNIQUE、CHECK、外部キー)、リレーションシップを特定します。外部キーの依存関係を満たすために適切な挿入順序を決めます。親テーブルは子テーブルより先にシードする必要があります。
-
シードデータの方針を決める: 用途に応じて適切な方法を選びます。再現可能なテストスイートには、固定シードを使った決定論的データを使用します。現実味のある開発用データには、Faker による生成を使用します。現実的なデータ分布が必要なステージング環境には、匿名化した本番環境のスナップショットを使用します。各テーブルに用意するデータ量を定義します。
-
シードスクリプトを生成する: すべてのスキーマ制約を満たすデータを作成するシードスクリプトを、プロジェクトの言語(Python、JavaScript、SQL など)で記述します。現実的な名前、メールアドレス、住所、日付の生成には、Faker ライブラリまたは同等のものを使用します。固有の値を生成するか、連番ベースのパターンを使用して、一意性制約に対応します。アトミック性を確保するため、挿入処理をトランザクションで囲みます。
-
冪等性を確保する: 安全に再実行できるスクリプトを設計します。
INSERT ON CONFLICT DO NOTHING、UPSERT パターン、またはテーブルを空にしてから挿入する方法を使用します。再実行時の重複や制約違反を避けるため、挿入前に既存のデータを確認します。 -
環境ごとのプロファイルに対応する: 異なるシードプロファイルを作成します。ローカル開発用には小規模なデータセット(テーブルあたり10~50件)、統合テスト用には中規模のデータセット(1,000~10,000件)、パフォーマンステスト用には大規模なデータセット(100K件以上)を用意します。環境変数またはコマンドライン引数でプロファイルを指定します。
-
実行して検証する: 対象のデータベースに対してシードスクリプトを実行し、行数が想定どおりであることを確認します。また、すべての外部キーが既存の行を参照していることを確認し、リレーショナル整合性を検証します。テーブルごとの件数を含むシーディング結果をログに記録します。
対応テクノロジー
- Python: Faker、Factory Boy、SQLAlchemy、psycopg2
- JavaScript/TypeScript: @faker-js/faker、Prisma seed、Knex seed files、TypeORM
- SQL: 生の INSERT 文、COPY FROM CSV
- Ruby: FactoryBot、Faker gem、Rails db:seed
- フレームワーク: Django fixtures、Laravel seeders、Rails seeds.rb
使い方
データベーススキーマ(またはマイグレーションファイルの場所)と、対象環境および希望するデータ量を指定してください。エージェントは、すべての制約とリレーションシップを満たす完全なシードスクリプトを生成します。「複数のタイムゾーンのユーザーを含める」や「直近12か月にわたる注文を作成する」など、特定のデータ特性を指定することもできます。
例
例1:Faker を使った Python のシードスクリプト
リクエスト: 開発用に、ユーザー、商品、注文を含む PostgreSQL データベースにシードデータを投入してください。
"""seed.py — Seed development database with realistic test data."""
import random
from datetime import datetime, timedelta
from faker import Faker
import psycopg2
fake = Faker()
Faker.seed(42) # Deterministic output for reproducibility
random.seed(42)
DB_CONFIG = {
"host": "localhost",
"port": 5432,
"dbname": "dev_db",
"user": "dev_user",
"password": "dev_password",
}
NUM_USERS = 50
NUM_PRODUCTS = 30
NUM_ORDERS = 100
def seed():
conn = psycopg2.connect(**DB_CONFIG)
cur = conn.cursor()
# Seed users
user_ids = []
for _ in range(NUM_USERS):
cur.execute(
"""INSERT INTO users (email, password_hash, full_name, created_at)
VALUES (%s, %s, %s, %s)
ON CONFLICT (email) DO NOTHING
RETURNING id""",
(
fake.unique.email(),
fake.sha256(),
fake.name(),
fake.date_time_between(start_date="-2y", end_date="now"),
),
)
row = cur.fetchone()
if row:
user_ids.append(row[0])
# Seed products
product_ids = []
for i in range(NUM_PRODUCTS):
cur.execute(
"""INSERT INTO products (name, description, price, stock_quantity, sku)
VALUES (%s, %s, %s, %s, %s)
ON CONFLICT (sku) DO NOTHING
RETURNING id""",
(
fake.catch_phrase(),
fake.paragraph(nb_sentences=3),
round(random.uniform(9.99, 499.99), 2),
random.randint(0, 500),
f"SKU-{i+1:05d}",
),
)
row = cur.fetchone()
if row:
product_ids.append(row[0])
# Seed orders with order items
statuses = ["pending", "confirmed", "shipped", "delivered"]
for _ in range(NUM_ORDERS):
user_id = random.choice(user_ids)
status = random.choice(statuses)
items = random.sample(product_ids, k=random.randint(1, 5))
total = 0.0
cur.execute(
"""INSERT INTO orders (user_id, status, total_amount, shipping_address, ordered_at)
VALUES (%s, %s, 0, %s, %s) RETURNING id""",
(user_id, status, fake.address(), fake.date_time_between("-1y", "now")),
)
order_id = cur.fetchone()[0]
for pid in items:
qty = random.randint(1, 4)
price = round(random.uniform(9.99, 499.99), 2)
total += qty * price
cur.execute(
"""INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES (%s, %s, %s, %s)""",
(order_id, pid, qty, price),
)
cur.execute(
"UPDATE orders SET total_amount = %s WHERE id = %s", (round(total, 2), order_id)
)
conn.commit()
cur.close()
conn.close()
print(f"Seeded {len(user_ids)} users, {len(product_ids)} products, {NUM_ORDERS} orders.")
if __name__ == "__main__":
seed()
例2:現実的なテストデータを含む SQL シードファイル
リクエスト: 小規模な開発用データセットの SQL シードファイルを作成してください。
-- seed.sql — Idempotent seed data for local development
-- Run with: psql -U dev_user -d dev_db -f seed.sql
BEGIN;
-- Users
INSERT INTO users (id, email, password_hash, full_name, created_at) VALUES
(1, '[email protected]', 'hash_alice', 'Alice Johnson', '2024-03-15 09:00:00'),
(2, '[email protected]', 'hash_bob', 'Bob Martinez', '2024-05-20 14:30:00'),
(3, '[email protected]', 'hash_carol', 'Carol Chen', '2024-07-01 11:15:00'),
(4, '[email protected]', 'hash_dave', 'Dave Okafor', '2024-09-10 08:45:00'),
(5, '[email protected]', 'hash_eve', 'Eve Andersson', '2024-11-28 16:00:00')
ON CONFLICT (id) DO NOTHING;
-- Products
INSERT INTO products (id, name, description, price, stock_quantity, sku) VALUES
(1, 'Wireless Keyboard', 'Bluetooth mechanical keyboard', 79.99, 150, 'SKU-00001'),
(2, 'USB-C Hub', '7-in-1 USB-C docking station', 49.99, 300, 'SKU-00002'),
(3, 'Noise-Cancelling Headphones', 'Over-ear ANC headphones', 199.99, 75, 'SKU-00003'),
(4, '4K Monitor', '27-inch IPS 4K display', 399.99, 40, 'SKU-00004'),
(5, 'Laptop Stand', 'Adjustable aluminum stand', 34.99, 200, 'SKU-00005')
ON CONFLICT (id) DO NOTHING;
-- Orders
INSERT INTO orders (id, user_id, status, total_amount, shipping_address, ordered_at) VALUES
(1, 1, 'delivered', 129.98, '123 Oak St, Portland, OR 97201', '2024-12-01 10:00:00'),
(2, 2, 'shipped', 199.99, '456 Elm Ave, Austin, TX 78701', '2025-01-05 14:20:00'),
(3, 3, 'confirmed', 484.98, '789 Pine Rd, Seattle, WA 98101', '2025-01-10 09:30:00'),
(4, 1, 'pending', 49.99, '123 Oak St, Portland, OR 97201', '2025-01-12 16:45:00')
ON CONFLICT (id) DO NOTHING;
-- Order items
INSERT INTO order_items (id, order_id, product_id, quantity, unit_price) VALUES
(1, 1, 1, 1, 79.99),
(2, 1, 2, 1, 49.99),
(3, 2, 3, 1, 199.99),
(4, 3, 4, 1, 399.99),
(5, 3, 5, 1, 34.99),
(6, 4, 2, 1, 49.99)
ON CONFLICT (id) DO NOTHING;
-- Reset sequences to avoid conflicts with future inserts
SELECT setval('users_id_seq', (SELECT MAX(id) FROM users));
SELECT setval('products_id_seq', (SELECT MAX(id) FROM products));
SELECT setval('orders_id_seq', (SELECT MAX(id) FROM orders));
SELECT setval('order_items_id_seq', (SELECT MAX(id) FROM order_items));
COMMIT;
ベストプラクティス
- 固定の乱数シードを設定する(例:
Faker.seed(42))ことで、テスト結果の再現性を高め、シード出力の差分に意味を持たせる決定論的なデータを生成できます。 - 外部キーの順序を必ず守ってください。子行より先に親行を挿入します。制約違反を避けるため、スクリプトを作成する前に依存関係のグラフを整理してください。
- シードスクリプトを冪等にするため、ON CONFLICT DO NOTHING または UPSERT のパターンを使用してください。シードを2回実行しても、重複データが作られたりエラーが発生したりせず、同じ結果になるようにします。
- 環境ごとにシードプロファイルを分けてください。ローカル開発用の小規模で高速なシード、CI 統合テスト用のより大規模なシード、負荷テスト用のさらに大規模なシードを用意します。環境変数で制御してください。
- 本番データベースにはテストデータをシードしないでください。安全対策として、シードスクリプトの先頭で環境を確認してください(例:
assert os.environ["ENV"] != "production")。 - 明示的な ID を指定してシードした後は、自動採番シーケンスをリセットし、アプリケーションが新しい行を挿入する際に主キーが衝突しないようにしてください。
エッジケース
- Faker による一意制約の衝突: Faker は大規模なデータセットで一意性を保証しません。重複を避けるには、
fake.unique.email()を使用するか、生成値にカウンターを追加してください。テストの実行ごとにfake.unique.clear()で一意性トラッカーをリセットします。 - 循環外部キー: テーブル A がテーブル B を参照し、テーブル B がテーブル A を参照する場合、まず両テーブルに外部キー列を NULL にして行を挿入し、次に外部キーの値を更新します。
- 大規模なシードデータセットとパフォーマンス: 10,000行を超えるデータをシードする場合は、個別の INSERT 文ではなく、一括挿入方式(PostgreSQL の COPY、MySQL の LOAD DATA INFILE)を使用してください。一括読み込み中はインデックスと制約を無効にし、読み込み後に再度有効にします。
- 時間に依存するテストデータ: アプリケーションのロジックが日付に依存する場合(例:「過去30日間の注文」)、古くなるハードコードされた日付ではなく、現在の日付を基準に日付を生成してください。
- バイナリデータやファイルデータのシード: アップロードされたファイルや画像を参照するテーブルでは、完全なバイナリコンテンツの生成を試みるのではなく、プレースホルダーのファイルパスや、小さな Base64 エンコード済みテスト画像を使用してください。