レガシー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を選択しました。
- 型安全
- マイグレーションが整っている
- 開発体験が良い
一見、合理的な選択です
やろうとしたこと
王道の流れです。
- Prismaでintrospection
- schema生成
- migrationで新DB構築
- アプリ切り替え
この時点では「いけそう」に見えます
何が起きたか
結論:普通に壊れます
問題①: 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