💥

Metabase の H2 DB が破損した時に修復せず作り直した話

に公開

こんにちは、BABY JOB 開発部のミヤギです。
今回は、技術的な内容を中心にお届けします。

はじめに

Metabase のデフォルトデータベース ( H2 ) が破損し、RDS( PostgreSQL )へ移行しようとして失敗した際の技術的な記録を共有します。

最終的に「移行断念・新規構築」という判断に至りましたが、その過程で行った調査手順やエラー原因の特定方法を技術ログとして残します。

前提知識:Metabase と H2 データベース

本題に入る前に、Metabase の構成と今回問題となったデータベースについて簡単に触れておきます。

Metabase とは

過去の記事で紹介しているので抜粋します。
https://zenn.dev/babyjob/articles/5e85a71904e811

Metabase社がOSSとして開発している、さまざまなデータソースに対応したデータ可視化ツール(Business Intelligence tool:BIツール)です。
サービスで利用しているDBの接続文字列等を Metabase に登録することで、Metabase 経由でDBクエリの実行や、データのグラフ化、ダッシュボード化などが行うことができます。

H2 データベースとの関係

Metabase 上で作成したユーザー情報や設定情報、「質問(クエリ)」や「ダッシュボード」などのアプリケーションデータを保存するためのデータベースです。
デフォルトで H2 データベース ( ファイルベースの組み込み DB ) を使用します。
ただし、Metabase 公式ドキュメントでは、H2 はあくまで試用や開発用とされており、信頼性の観点から本番環境では速やかに PostgreSQL や MySQL などの外部データベースへ移行することが強く推奨されています。

今回は、この「デフォルトの H2 データベース」を長く運用してしまった結果、データ破損に遭遇した事例となります。

環境

  • Metabase: AWS EC2 上で jar ファイル実行
  • Database: H2 ( デフォルト ) → PostgreSQL ( RDS ) へ移行
  • Version: v0.52.2

1. 発生していた事象と事前の復旧措置

ある日突然、Metabase が起動しなくなるクラッシュが発生。再起動でも解消しなかったため、H2 データベースの修復(ダンプファイルからの再生成)を実施しました。

具体的には、Metabase の jar に含まれる H2 ツールを使用し、以下のようなコマンドでデータベースファイルの再作成を行いました。

# H2 データベースの復旧コマンド ( RunScript )
# SQL ダンプ ( metabase.db.h2.sql ) から新しい DB ファイル ( metabase_recovered ) を作成
sudo java -cp metabase.jar org.h2.tools.RunScript \
    -url "jdbc:h2:./metabase_recovered" \
    -user sa \
    -script metabase.db.h2.sql

これにより Metabase 自体は起動するようになりましたが、動作が不安定でした。
そのため、恒久対応として RDS への移行を実施することにしました。

2. 移行作業とエラー発生

RDS への移行は、Metabase 標準の load-from-h2 コマンドを使用します。

実行した手順

  1. Metabase サービスを停止。
  2. 移行先の PostgreSQL 接続情報を環境変数にセットしてコマンド実行。
# 環境変数の設定
export MB_DB_TYPE=postgres
export MB_DB_DBNAME=metabase
export MB_DB_PORT=5432
export MB_DB_USER=<db_user>
export MB_DB_PASS=<db_password>
export MB_DB_HOST=<rds_endpoint>

# 移行コマンドの実行
java -jar metabase.jar load-from-h2 metabase.db

発生したエラー

処理の途中で以下の例外が発生し、移行プロセスが中断 ( ロールバック ) されました。

ERROR: insert or update on table "metabase_fieldvalues" violates foreign key constraint "fk_fieldvalues_ref_field_id"
Detail: Key (field_id)=(1) is not present in table "metabase_field".

3. 原因調査 ( H2 Shell による内部確認 )

エラー内容は「metabase_fieldvalues テーブルが参照している field_id=1 が、親テーブル metabase_field に存在しない」という 外部キー制約違反 です。

実際にデータの状態を確認するため、Metabase に同梱されている H2 Shell を使用して直接データを参照しました。

3-1. H2 Shell への接続

# H2 Shell の起動
# -url: データベースファイルのパス ( 拡張子.mv.dbは含めない )
# -user: デフォルトは sa
# -password: デフォルトは空文字 ( 設定している場合は指定 )
java -cp metabase.jar org.h2.tools.Shell -url "jdbc:h2:./metabase.db" -user sa -password ""

3-2. 不整合データの確認

Shell に入った後、エラーログに出ていた field_id=1 の状態を確認しました。

① 親テーブル ( metabase_field ) の確認
まず、本来あるはずのカラム定義データを確認します。

sql> SELECT * FROM metabase_field WHERE id = 1;
(No rows selected)

結果は0件。親データが存在しない ことが確定しました。

② 子テーブル ( metabase_fieldvalues ) の確認
次に、エラーの原因となっている参照元データを確認します。

sql> SELECT count(*) FROM metabase_fieldvalues WHERE field_id = 1;
COUNT(*)
5

親定義が存在しないにも関わらず、それを参照する子データが存在する ことが確認できました。

さらに、他にも同様の「孤児レコード」がないか確認するため、以下のクエリを実行しました。

sql> SELECT count(distinct field_id) FROM metabase_fieldvalues 
     WHERE field_id NOT IN (SELECT id FROM metabase_field);
COUNT(DISTINCT FIELD_ID)
211

調査の結果、id=1 だけでなく、合計で 211 種類 ものフィールド定義が欠損したまま孤児レコードだけが残っている、深刻な状態であることが判明しました。

3-3. 結論

以前の破損時、または修復プロセスの過程で、「テーブル定義データ ( 親 ) はロストしたが、キャッシュ的な値データ ( 子 ) だけが生き残った」状態になっていました。

H2 ( Metabase のデフォルト設定 ) ではこの不整合が許容されていましたが、PostgreSQL は参照整合性を厳格にチェックするため、このデータをインポートできずエラーとなっていました。

4. 対応の検討と決定

案 A:孤児レコードを削除して再トライ

H2 Shell から DELETE 文を発行し、整合性が取れていないデータを削除してから移行する案です。

-- 例: 親が存在しない子レコードを削除
DELETE FROM metabase_fieldvalues WHERE field_id NOT IN (SELECT id FROM metabase_field);
  • 判定 : 却下
  • 移行エラーは回避できるかもしれないが、id=1(おそらく初期に作成された重要なカラム定義)自体が消えている事実は変わらない。
  • 定義が消えた状態で、それを利用していた Metabase 上の「質問( Question )」やダッシュボードが正しく動く保証がない。

案 B:新規構築(採用)

不整合なDBを引き継ぐリスクを避け、環境をリフレッシュする案です。

[ 作業の流れ ]

  1. 新規構築 : EC2 インスタンスを作り直し、RDS ( PostgreSQL ) を接続して Metabase をクリーンインストール。
  2. データ移行 : 移行ツールは使わず、必要なダッシュボードやSQLクエリを手動で移植 ( または SQL ファイルから復元 ) 。
  3. 切り替え : ユーザーへ新環境を案内。
  • 判定 : 採用
  • 潜在的なデータの不整合リスクを完全に排除し、クリーンな環境で再出発できる。
  • 手動移行の工数はかかるが、将来的なトラブル対応コストや運用リスクと比較すると、作り直す方が安全かつ確実であると判断した。

5. まとめ

Metabase の load-from-h2 コマンドで外部キー制約エラーが出る場合、移行元の H2 データベースですでに論理的なデータ欠損 ( 親レコードの消失 ) が起きている可能性が高いです。

その場合、無理にデータを修復して移行するよりも、「諦めて作り直す」方が、将来的な運用リスクを排除できる最善手となることがあります。
H2 Shell を使えば内部データの不整合を特定できるため、同様のエラーに遭遇した際はまず SELECT ... WHERE id = ... 等で、エラーに出ている id の生存確認をすることをお勧めします。

また、本番運用において H2 データベースはあくまで試用・小規模向けであり、早めに RDS 等のマネージド DB へ移行することを強く推奨します。

BABY JOB  テックブログ

Discussion