BluePeriod Docs
開発

マイグレーション管理ガイド

データベースマイグレーションの管理方針と運用手順

05. マイグレーション管理ガイド

1. 概要

このドキュメントは、本アプリケーションで使用される2つのデータベースのマイグレーション管理に関する包括的なガイドです。

  1. Supabase(PostgreSQL): サーバーサイドのデータベース。ユーザーアカウント、公開作品などの永続化に使用。
  2. IndexedDB(Dexie): クライアントサイドのデータベース。プロジェクト、チャットなどのプライベートデータの永続化に使用。

マイグレーションの実行方法、重要な変更履歴、ベストプラクティスを記載しています。


A. Supabase(PostgreSQL)マイグレーション

A.1. マイグレーションの基本

A.1.1. マイグレーションとは

マイグレーションは、データベーススキーマの変更を管理するための仕組みです。各マイグレーションファイルは、特定の時点でのスキーマ変更を記録し、バージョン管理を可能にします。

A.1.2. マイグレーションファイルの場所

/supabase/migrations/
├── 20251119034343_create_profiles_and_trigger.sql
├── 20251120224100_create_published_projects.sql
├── 20251123000001_add_get_popular_tags.sql
└── 20251123094000_add_profiles_fkey.sql

A.1.3. 命名規則

マイグレーションファイルは以下の形式で命名されます:

YYYYMMDDHHmmss_description.sql
  • タイムスタンプ: マイグレーションの作成日時(UTC)
  • 説明: 変更内容を簡潔に表す英語の説明

A.2. マイグレーションの実行方法

A.2.1. ローカル環境での実行

ローカルのSupabaseインスタンスに対してマイグレーションを適用:

# ローカルSupabaseを起動
bunx supabase start

# マイグレーションを適用
bunx supabase db reset

注意: db resetはデータベースを完全にリセットし、すべてのマイグレーションを最初から適用します。

A.2.2. リモート環境への適用

本番環境(Supabase Cloud)に対してマイグレーションを適用:

# リモートデータベースに接続して適用
bunx supabase db push

重要: 本番環境へのマイグレーション適用は慎重に行ってください。適用前に必ずバックアップを取得することを推奨します。

A.2.3. マイグレーションの作成

新しいマイグレーションファイルを作成:

# 空のマイグレーションファイルを生成
bunx supabase migration new <description>

# 例
bunx supabase migration new add_user_preferences_table

生成されたファイルにSQLを記述し、変更を適用します。

A.3. 重要なマイグレーション履歴

A.3.1. 20251119034343_create_profiles_and_trigger.sql

目的: ユーザープロフィール管理の基盤構築

主な変更:

  • public.profiles テーブルの作成
  • Row Level Security (RLS) ポリシーの設定
  • 新規ユーザー登録時に自動的にプロフィールを作成するトリガー関数 handle_new_user() の実装

影響範囲: 認証システム全体

注意点: このマイグレーションは認証機能の基盤となるため、ロールバックは推奨されません。


A.3.2. 20251120224100_create_published_projects.sql

目的: 作品公開機能の実装

主な変更:

  • public.published_projects テーブルの作成
  • パフォーマンス最適化のためのインデックス追加
    • idx_published_projects_user_id: ユーザーごとの一覧表示を高速化
    • idx_published_projects_published_at: 新着順の表示を高速化
    • idx_published_projects_tags: タグ検索を高速化(GINインデックス)
  • RLSポリシーの設定
  • covers ストレージバケットの作成
  • 更新日時自動更新トリガー handle_project_update() の実装

影響範囲: 作品公開・閲覧機能全体

関連ドキュメント: /doc/system/04_database_schema_supabase.md


目的: 人気タグ取得機能の追加

主な変更:

  • get_popular_tags() 関数の作成
  • タグの出現頻度を集計し、上位10件を返す

影響範囲: Exploreページのタグフィルタリング機能

使用例:

SELECT * FROM get_popular_tags();

A.3.4. 20251123094000_add_profiles_fkey.sql

目的: published_projectsprofiles のリレーションシップ強化

背景:

  • 当初、published_projects.user_idauth.users.id のみを参照していた
  • Supabaseのクエリで profiles を直接JOINできず、エラーが発生
  • .select('*, profiles(username, avatar_url)') のようなクエリを可能にするため

主な変更:

  • published_projects.user_id から public.profiles.id への外部キー制約を追加
  • カスケード削除を設定(ユーザー削除時にプロジェクトも削除)

影響範囲:

  • Exploreページでの作者情報の取得
  • すべての公開プロジェクト関連のクエリ

技術的詳細: この変更により、以下のようなシンプルなクエリが可能になりました:

supabase
  .from('published_projects')
  .select('*, profiles(username, avatar_url)')

関連レポート: /doc/log/reports/2025-11-23_report_nextjs15-supabase-fixes.md


A.3.5. 20251128000000_add_get_tags_by_context.sql

目的: コンテキスト依存のタグフィルタリング機能の実装

主な変更:

  • get_tags_by_context() RPC関数の作成
  • get_popular_tags() の機能を包含し、さらにユーザー固有のコンテキスト(Library, My Works)に対応

影響範囲:

  • Explore, Library, My Worksページのタグフィルター
  • get_popular_tags は将来的に廃止またはこの関数へのラッパーとなる可能性があります

使用例:

-- ライブラリ内のタグを取得
SELECT * FROM get_tags_by_context('user-uuid', 'library');

-- 自分の作品のタグを取得
SELECT * FROM get_tags_by_context('user-uuid', 'my_works');

関連レポート: /doc/log/reports/2025-11-28_report_context_aware_tag_filter.md



A.3.6. 20251225125500_create_prompt_assets.sql

目的: AIプロンプトのライブラリ管理機能の実装

主な変更:

  • public.prompt_assets テーブルの作成
  • ローカル IndexedDB との同期を前提としたスキーマ構成
  • RLSポリシーの設定(作成者のみが操作可能)
  • 更新日時自動更新トリガーの実装

影響範囲:

  • 新機能「プロンプトマネージャー」
  • Supabase クラウド同期機能

関連レポート: /doc/log/reports/2025-12-25_report_prompt-manager-implementation.md


A.3.7. 20260214140000_create_stripe_tables.sql

目的: Stripeサブスクリプション統合の基礎構築および権限判定ロジックの実装(Phase 1〜3)

主な変更:

  • public.customers テーブルの作成(Stripe顧客ID管理)
  • public.subscriptions テーブルの作成(購読ステータス管理)
  • パフォーマンス最適化: subscriptions(user_id) にインデックスを追加
  • 権限判定の実装: public.is_pro_user() SQL関数の定義。subscriptions テーブルを参照し、active または trialing ステータスの有無を返します。
  • RLSポリシーの設定:
    • customers, subscriptions の閲覧を本人に制限。
    • Webhook経由の更新(Service Role使用)を許可。

影響範囲:

  • サブスクリプション管理機能全体
  • アプリケーション全体でのPro権限判定(isPro
  • ストレージバケット(user_backups)のアクセス制御

関連レポート:


A.4. マイグレーション作成のベストプラクティス

A.4.1. 基本原則

  1. 冪等性を保つ: マイグレーションは何度実行しても同じ結果になるように記述する

    -- 良い例: IF NOT EXISTSを使用
    CREATE TABLE IF NOT EXISTS my_table (...);
    
    -- 悪い例: 既存テーブルがあるとエラーになる
    CREATE TABLE my_table (...);
  2. 小さく保つ: 1つのマイグレーションで1つの論理的な変更のみを行う

  3. コメントを充実させる: 変更の理由と影響範囲を明記する

  4. インデックスを忘れない: パフォーマンスに影響する可能性があるカラムには適切なインデックスを追加

A.4.2. 外部キー制約の追加

外部キー制約を追加する際は、以下の点に注意:

-- カスケード削除の設定を明示的に指定
ALTER TABLE child_table
ADD CONSTRAINT child_table_parent_id_fkey
FOREIGN KEY (parent_id) REFERENCES parent_table(id) ON DELETE CASCADE;

A.4.3. RLSポリシーの設定

Row Level Security (RLS) は必ず有効化し、適切なポリシーを設定:

-- RLSを有効化
ALTER TABLE my_table ENABLE ROW LEVEL SECURITY;

-- 読み取りポリシー
CREATE POLICY "Public read access"
ON my_table FOR SELECT
USING (true);

-- 書き込みポリシー(認証済みユーザーのみ)
CREATE POLICY "Authenticated users can insert"
ON my_table FOR INSERT
WITH CHECK (auth.uid() = user_id);

A.4.4. インデックスの追加

頻繁にクエリされるカラムにはインデックスを追加:

-- 通常のインデックス
CREATE INDEX idx_table_column ON table_name(column_name);

-- 降順インデックス(新着順など)
CREATE INDEX idx_table_created_at ON table_name(created_at DESC);

-- 配列カラムのGINインデックス
CREATE INDEX idx_table_tags ON table_name USING GIN (tags);

A.5. ロールバック手順

A.5.1. ローカル環境

ローカル環境では、データベースをリセットして特定のマイグレーションまで戻すことができます:

# データベースを完全にリセット
bunx supabase db reset

# 特定のマイグレーションまで適用(手動)
# 不要なマイグレーションファイルを一時的に移動してから reset

A.5.2. リモート環境

警告: 本番環境でのロールバックは非常に危険です。以下の手順を慎重に実行してください。

  1. バックアップの取得: 必ず最新のバックアップを取得
  2. ロールバックマイグレーションの作成: 変更を元に戻すSQLを記述
  3. テスト: ローカル環境で十分にテスト
  4. 適用: bunx supabase db push で適用

ロールバックマイグレーションの例:

-- 20251123094000_add_profiles_fkey.sql のロールバック
-- ファイル名: 20251123100000_rollback_profiles_fkey.sql

ALTER TABLE published_projects
DROP CONSTRAINT IF EXISTS published_projects_user_id_profiles_fkey;

A.6. 型定義の同期

A.6.1. TypeScript型定義の自動生成

重要: マイグレーション適用後は、必ずTypeScript型定義を更新してください。

# リモートデータベースから型定義を生成
bunx supabase gen types typescript --linked > next-app/src/lib/types/supabase.ts

A.6.2. 型定義とドキュメントの整合性確認

マイグレーション適用後のチェックリスト:

  • TypeScript型定義を自動生成
  • /doc/system/04_database_schema_supabase.md を更新
  • 型定義とドキュメントの内容が一致していることを確認
  • TypeScriptのビルドエラーがないことを確認

A.6.3. よくある不整合

以下のような不整合に注意:

  • カラム名の違い(例: created_at vs published_at
  • NULL許容性の違い
  • デフォルト値の有無
  • 外部キー関係の欠落

A.7. トラブルシューティング

A.7.1. マイグレーション適用エラー

エラー: relation "table_name" already exists

解決策: マイグレーションに IF NOT EXISTS を追加


エラー: foreign key constraint fails

解決策:

  1. 参照先のテーブルが存在することを確認
  2. 参照先のカラムに一意制約があることを確認
  3. 既存データが制約を満たしていることを確認

エラー: permission denied for schema public

解決策: Supabase CLIが正しく認証されていることを確認

bunx supabase login
bunx supabase link --project-ref <your-project-ref>

A.7.2. ローカルとリモートの不整合

ローカルとリモートのスキーマが異なる場合:

# リモートのスキーマをローカルに反映
bunx supabase db pull

# 差分を確認
bunx supabase db diff

B. IndexedDB(Dexie)マイグレーション

B.1. 概要

IndexedDB はブラウザ内のデータベースであり、Dexie.js ライブラリを通じて操作されます。スキーマ変更は db.ts 内のバージョン管理機能で自動的に行われます。

  • 実装ファイル: next-app/src/lib/db.ts
  • 型定義ファイル: next-app/src/lib/types.ts
  • データベース名: blue-period-db

B.2. マイグレーションの仕組み

Dexie では、データベースを開く際にスキーマバージョンを指定し、ユーザーのデータベースが古い場合は自動的にアップグレードされます。

// db.ts
export class BluePeriodDatabase extends Dexie {
  // ...

  this.version(12).stores({
    projects: '&id, title, author, updatedAt',
    chatPrompts: '&id',
    // ...
  })

  // Version 13: UUID migration for chat tables
  this.version(13).stores({
    characters: '&id, name, updatedAt',
    chatSessions: '&id, character_id, updatedAt, createdAt',
    chatMessages: '&id, session_id, createdAt, updatedAt',
  })
}

重要: Dexie のマイグレーションは自動的に実行されます。ユーザーがアプリケーションを開くと、必要なアップグレードが行われます。

B.3. スキーマバージョン履歴

バージョン変更内容
1-9初期バージョン(詳細不明)
10updatedAt インデックス追加(同期パフォーマンス向上)
11deleted_items テーブル追加(Tombstone Pattern対応)
12writingPrompts テーブル削除(データは project.content.writingPrompts に統合)
13characters, chatSessions, chatMessages テーブルをドロップ(UUID移行の準備)
14characters, chatSessions, chatMessages テーブルをUUID主キーで再作成
20sessionArtifacts テーブル追加(エージェント用一時コンテキスト保存用)
21全テーブルの日付正規化(Temporalオブジェクトによる索引不備の修正)および ChatMessage への updatedAt 追加

B.3.1. バージョン 21 の詳細

目的: IndexedDBの索引が効かなくなる問題(Temporalポリフィルオブジェクトの混入)を根本的に修正し、全データの整合性を確保する。

主な変更:

  • src/lib/utils/date.tsensureDate を使用した多層防御の導入。
  • projects, characters, chatSessions, chatMessages, promptAssets, sessionArtifacts の全レコードを走査し、日付フィールドを標準の Date オブジェクトに変換。
  • ChatMessage インターフェースに updatedAt フィールドを正式に追加し、過去のメッセージには createdAt をコピー。
  • 移行ロジックを src/lib/db/migrations.ts に分離。

B.4. マイグレーションの実装手順

B.4.1. 新しいバージョンを追加

  1. db.ts で新しいバージョンを追加:
this.version(14).stores({
  // 新しいスキーマ定義
})
  1. 必要に応じて TypeScript 型定義を更新 (types.ts)
  2. 変更するテーブルを使用するコードを更新
  3. テストして動作を確認

B.4.2. スキーマ定義の構文

// auto-increment(数値)の主キー
this.version(1).stores({
  myTable: '++id, name',  // ++id は auto-increment
})

// UUID(文字列)の主キー
this.version(2).stores({
  myTable: '&id, name',  // &id はインラインの主キー(文字列)
})

// 複合インデックス
this.version(3).stores({
  myTable: '&id, [field1+field2]',  // field1 と field2 の複合インデックス
})

B.4.3. 主キー型の変更について

重要: Dexieは主キー型の変更(++id&id)を直接サポートしていません。主キー型を変更する場合は、以下の2段階マイグレーションが必要です。

// ステップ1: テーブルをドロップ
this.version(13).stores({
  myTable: null,  // テーブルを削除
})

// ステップ2: 新しい主キー型で再作成
this.version(14).stores({
  myTable: '&id, name',  // 新しいスキーマで再作成
})

注意点:

  • この操作により、既存データはすべて削除されます
  • 必要に応じてアップグレードフックでデータをエクスポート/インポートしてください
  • 開発段階であればデータ削除を受け入れるのが最も簡潔です

B.5. 型定義の更新

スキーマ変更後は、対応する TypeScript 型を更新してください。

// types.ts
export interface MyRecord {
  id: string;  // 主キーの型をスキーマに合わせる
  name: string;
  createdAt: Date;
  updatedAt: Date;
}

B.6. チェックリスト

IndexedDB マイグレーション実施後のチェックリスト:

  • db.ts で新しいバージョンを追加
  • types.ts で型定義を更新
  • 該当するテーブルを使用するコードを更新
  • TypeScript のビルドエラーがないことを確認
  • ブラウザで動作確認(既存データがマイグレーションされること)
  • /doc/system/04_database_schema.md を更新
  • /doc/system/10_sync_architecture.md のバージョン履歴を更新
  • 必要に応じて /doc/log/reports/ にレポートを作成

B.7. トラブルシューティング

マイグレーションが実行されない

現象: スキーマ変更してもデータベースが更新されない

原因: Dexie はデータベース名とバージョン番号で変更を検出します。同じバージョン番号でスキーマを変更しても、マイグレーションは実行されません。

解決策: 必ず新しいバージョン番号を指定してください

既存データが失われる

現象: マイグレーション後に既存データが消える

原因: テーブルの主キー型を変更した場合、Dexie は既存データを自動的に変換しません

解決策:

  • 主キー型の変更は慎重に検討してください
  • 必要であれば、アップグレードフックでデータ移行処理を実装してください
  • 詳細は Dexieのドキュメントを参照してください

関連ドキュメント

  • /doc/system/04_database_schema.md - IndexedDB スキーマの詳細
  • /doc/system/04_database_schema_supabase.md - Supabase スキーマの詳細
  • /doc/system/10_sync_architecture.md - クラウド同期アーキテクチャ
  • /doc/system/02_technology_stack.md - 技術スタックの説明
  • /doc/log/reports/ - 各マイグレーションの詳細レポート

最終更新: 2026-02-14

On this page

05. マイグレーション管理ガイド
1. 概要
A. Supabase(PostgreSQL)マイグレーション
A.1. マイグレーションの基本
A.1.1. マイグレーションとは
A.1.2. マイグレーションファイルの場所
A.1.3. 命名規則
A.2. マイグレーションの実行方法
A.2.1. ローカル環境での実行
A.2.2. リモート環境への適用
A.2.3. マイグレーションの作成
A.3. 重要なマイグレーション履歴
A.3.1. 20251119034343_create_profiles_and_trigger.sql
A.3.2. 20251120224100_create_published_projects.sql
A.3.3. 20251123000001_add_get_popular_tags.sql
A.3.4. 20251123094000_add_profiles_fkey.sql
A.3.5. 20251128000000_add_get_tags_by_context.sql
A.3.6. 20251225125500_create_prompt_assets.sql
A.3.7. 20260214140000_create_stripe_tables.sql
A.4. マイグレーション作成のベストプラクティス
A.4.1. 基本原則
A.4.2. 外部キー制約の追加
A.4.3. RLSポリシーの設定
A.4.4. インデックスの追加
A.5. ロールバック手順
A.5.1. ローカル環境
A.5.2. リモート環境
A.6. 型定義の同期
A.6.1. TypeScript型定義の自動生成
A.6.2. 型定義とドキュメントの整合性確認
A.6.3. よくある不整合
A.7. トラブルシューティング
A.7.1. マイグレーション適用エラー
A.7.2. ローカルとリモートの不整合
B. IndexedDB(Dexie)マイグレーション
B.1. 概要
B.2. マイグレーションの仕組み
B.3. スキーマバージョン履歴
B.3.1. バージョン 21 の詳細
B.4. マイグレーションの実装手順
B.4.1. 新しいバージョンを追加
B.4.2. スキーマ定義の構文
B.4.3. 主キー型の変更について
B.5. 型定義の更新
B.6. チェックリスト
B.7. トラブルシューティング
マイグレーションが実行されない
既存データが失われる
関連ドキュメント