🚀

レガシーMySQL(EUC-JP & 0000-00-00)をPrismaで移行しようとして普通に詰んだ話

に公開

レガシーMySQL(EUC-JP & 0000-00-00)をPrismaで移行しようとして普通に詰んだ話

はじめに

レガシーDBの移行で地獄をみた話を書きます。

今回、EUC-JP + アンチパターン満載の古いDBを、
型安全ORMである Prisma ORM を使って移行しようとして、普通に詰みました。
EUC-JPなんてこの世に存在してないよ!とかいう人もいると思いますが、この世には存在します。
そんな人に届けば良いなと思います。

結論から言うと、

ORMは"壊れたデータ"を救ってくれない

という話です。

ただし単なる失敗談ではなく、最終的には
Drizzle ORM に切り替えることで現実的な解決に辿り着きました。

この記事では、「なぜ詰んだのか」「なぜDrizzleならいけたのか」を構造的に整理します。

背景

対象は典型的なレガシー構成です。

  • DB: MySQL
  • 文字コード: EUC-JP
  • 日付: 0000-00-00 が普通に存在
  • 制約: ゆるい or 実質なし
  • 実態: 「データが仕様」

移行の目的は以下。

  • UTF-8化
  • スキーマ整理
  • アプリの近代化

そのためにPrismaを選択しました。

  • 型安全
  • マイグレーションが整っている
  • 開発体験が良い

一見、合理的な選択です

やろうとしたこと

王道の流れです。

  1. Prismaでintrospection
  2. schema生成
  3. migrationで新DB構築
  4. アプリ切り替え

この時点では「いけそう」に見えます

何が起きたか

結論:普通に壊れます

問題①: 0000-00-00

SELECT id, created_at
FROM users
WHERE created_at = '0000-00-00';

普通に大量にヒットします。

しかしPrismaでは:

  • DateTimeとして扱えない
  • パース失敗
  • runtime error

「存在しているデータが読めない」

問題②: 文字コード(EUC-JP)

レガシー環境で見落としがちですが、EUC-JPは単なる「古い文字コード」ではなく、移行時に複数のリスクを内包しています。

■ マッピング不能な文字の欠損

EUC-JPに含まれずUTF-8にのみ存在する文字(特殊な絵文字や、後から追加された漢字など)を扱う場合、

  • DB側で変換エラー
  • ? などへの置換

が発生します。

サイレントにデータが壊れるリスクがある

■ インデックスとパフォーマンス

文字コード変換がクエリ実行時に挟まることで、

  • インデックスが効かない
  • 関数インデックスの最適化が崩れる

特に検索系クエリで顕在化しやすい

■ Prisma ORM Migrateとの相性

Prisma Migrateはデフォルトで

  • UTF-8(utf8mb4)

のテーブルを生成します。

そのため、

既存のEUC-JP前提のDDLをUTF-8に変換する必要がある

という問題が発生します。

実際にはこれをプロシージャで吸収しようと試みましたが、

  • データ整合性の常時監視が必要
  • 変換ロジックが分散
  • 運用が複雑化

具体的には、INSERT,UPDATEをトリガーとしてutf8mb4のテーブルに文字コードを変換して、MySQL内で同期する方法です。

現実的には破綻しました(非推奨)

まとめ

EUC-JP → UTF-8の変換は不可能ではありませんが、

  • DB側で変換機構を持つ(プロシージャ / プロキシ)
  • 監視・運用コスト増大
  • 障害点の増加

システム全体の複雑性が上がる

このコストを許容できるなら、

DB自体をUTF-8に移行するのが本命

です。

ただし今回は、

  • 既存アプリへの影響が大きい
  • リスクが高い

という理由から別の手段を検討しました。

問題③: スキーマと実態の乖離

  • DB定義: DATE
  • 実態: NULLの代替値(0000-00-00)

型が嘘をついている

なぜ死んだのか

これはPrismaの問題ではありません。

Prismaの前提

  • スキーマ = 真実
  • 型 = 厳密
  • データ = 正しい

クリーンな世界用

レガシーDBの現実

  • データ = 真実
  • スキーマ = 参考
  • 例外だらけ

汚い世界

前提が噛み合っていない

これが詰んだ理由です。

解決案として出た選択肢

① レガシーDBを修正

  • 根本解決
  • ただし既存アプリへの影響が大きい

現実的に重い

② 中間コンバーター(Lambda)

  • DB → Lambda → 新システム

デメリット:

  • 障害点が増える
  • コスト増
  • データの真実が分散

アーキテクチャとして微妙

③ ORM側で吸収

ここで Drizzle ORM を採用

なぜDrizzleならいけたのか

Prisma

createdAt: DateTime

不正データを扱えない

Drizzle

// select時の変換ロジック
sql`CASE 
  WHEN created_at = '0000-00-00' THEN NULL 
  ELSE created_at 
END`

変換ロジックをORMではなくSQLライクに明示的に書ける

さらに重要な違い

Prisma ORM もRaw SQLは書けます。
ただし、

型システムと強く結合している

ため、不正値を返すクエリを書くと、型エラーが発生し実行時にも落ちます。

一方で Drizzle ORM は、

  • スキーマ定義とクエリが分離されている
  • クエリ側に変換ロジックを自由に差し込める

不整合を"制御して扱える設計"になっている

本質的な違い

Prisma

正しいデータしか扱えない

Drizzle

壊れたデータも扱える

設計として何が起きているか

これはツールの話ではなく、設計の話です。

吸収レイヤーの選択

レイヤー 内容
DB データを修正
中間層 変換
アプリ 吸収

今回の判断は

アプリ層で吸収

なぜこの判断が合理的だったか

  • DB修正コストが高い
  • 影響範囲が広い
  • 不整合が局所的

この条件なら

アプリで握る方が現実的

ただしリスクもある

リスク

  • 不整合を温存
  • 暗黙知化
  • 将来の事故

対策

  • 変換ロジックの共通化
  • コメント・ドキュメント明記
  • 将来的なDB修正余地を残す

結論

  • Prismaは悪くない
  • Drizzleが優れているわけでもない

前提が違うだけ

Prismaは「理想の世界」で戦うツール
Drizzleは「現実の世界」で戦うツール

学び

ORMはデータの嘘を吸収しない

おわりに

レガシーDB移行で一番危険なのは、

「ツールがなんとかしてくれる」と思うこと

です。

現実は逆で、

データの歪みをどこで受け止めるかを設計する

必要があります。

Discussion