/note/tech

Should You Stop Using Prisma? Why Database ORMs Might Be the Worst Thing That Happened to...

要約:

■ 1. Prismaの問題提起

  • 3年間のPrisma本番運用後、パフォーマンス問題とサーバーレスコスト高騰を経験
  • データベースアクセスを容易にすると約束したツールが最大のボトルネックに
  • ORM全般の根本的問題:生産性と型安全性を約束したが複雑さとパフォーマンス問題をもたらした

■ 2. Prismaの魅力と落とし穴

  • 魅力的なマーケティング:
    • 型安全なデータベースアクセス
    • 自動マイグレーション
    • SQLを書く必要がない
    • 単一のスキーマファイルで全てを生成
  • クリーンで読みやすく型安全なコードに見えるが実際は問題だらけ

■ 3. パフォーマンスの現実

  • バンドルサイズ:
    • Drizzleは約1.5MB
    • Prismaは約6.5MB
    • サーバーレス環境では重大な影響
  • N+1問題の深刻化:
    • 一見単純なクエリが実際には数十のデータベースラウンドトリップを生成
    • 単一のJOINクエリであるべきものが47個の個別SQLステートメントを生成した事例
    • フロントエンドでTanStack Queryを使ってこの問題を回避したのにバックエンドでPrismaにより再現

■ 4. サーバーレスでの悪夢

  • AWS Lambdaのコールドスタートが200msから2.5秒に増加
  • Prismaが実行前に必要な処理:
    • Rustベースのクエリエンジンバイナリのロード
    • 内部GraphQLサーバーの起動
    • コネクションプーリングの初期化
    • クライアントインターフェースの生成
  • Drizzleのような軽量ソリューションではサーバーレス関数のロードと実行がPrismaより遥かに高速

■ 5. 開発者体験の幻想

  • Prismaの最大のセールスポイントは開発者体験(DX)
  • 実態:シンプルなことに対する優れたDXは複雑なことに対する酷いDXを意味する
  • スキーマロックイン:
    • 全てがschema.prismaファイルを中心に回る
    • PrismaのDSLに収まらないことをする場合は困難
    • PostgreSQLのJSONB演算子などデータベース固有機能の使用が困難
    • 複数テーブルの式(CTE)を含む複雑なクエリは生SQLに戻る必要があり、パラダイムを混在させ型安全性を失う

■ 6. コード生成の問題

  • スキーマを変更するたびにprisma generateを実行する必要
  • 数千行のTypeScriptコードを再生成
  • IDEがフリーズし、ビルドプロセスが遅延
  • 実行を忘れると型とデータベースが同期しなくなる
  • Drizzleではスキーマへの変更が即座に反映される(コード生成不要)

■ 7. マイグレーションの悪夢

  • デモでは素晴らしく見えるが本番では別の話
  • データ損失の設計:
    • カラム名変更時にPrismaは名前変更として検出せず、古いカラムを削除して新しいカラムを作成
    • そのカラムの全データが消失
  • Drizzleは名前変更の可能性を検出すると対話モードに入り意図を選択させる
  • ブラックボックス問題:
    • PrismaがSQLマイグレーションを生成するが手書きとは異なる
    • 冗長で非効率な場合があり、レビューが困難
    • マイグレーションをカスタマイズする際はツールと戦うことになる

■ 8. 抽象化の真のコスト

  • Ted Newardは2008年にORMを「コンピュータサイエンスのベトナム戦争」と呼んだ
  • ORMはオブジェクト指向コードとリレーショナルデータベース間のインピーダンスミスマッチを排除すると約束
  • 実際には排除せず隠蔽するだけ
  • 抽象化がリークすると(常にリークする)、ORMの癖と生成されるSQLの両方を理解する必要があり、開始時より悪化

■ 9. 優れた代替案

  • Drizzle:
    • SQLを知っていればDrizzleを知っている
    • コード生成不要、バイナリ不要、魔法なし
    • パフォーマンスはPrismaより桁違いに高速
    • バンドルサイズ1.5MB対Prismaの6.5MB
  • 型安全性を持つ生SQL:
    • RustのSQLxはコンパイル時チェック付きSQLクエリをランタイムオーバーヘッドゼロで実現
    • TypeScriptではKyselyが同様の利点を提供
  • 生SQLの方が実際には優れているという動き:
    • より透明:どのクエリが実行されているか正確に分かる
    • より高パフォーマンス:抽象化オーバーヘッドがない
    • より移植性が高い:SQL知識がプロジェクト間で転用可能
    • より安全:インジェクションリスクを認識しているためより慎重になる

■ 10. セキュリティへの影響

  • ORMはSQLインジェクションを防ぐためより安全とされるが新しいセキュリティ問題を導入
  • マスアサインメント脆弱性:
    • リクエストボディに予期しないrole: 'admin'フィールドが含まれていると管理者ユーザーを作成してしまう
    • 生SQLではどのフィールドを更新するか明示的に指定するためこのパターンは起こりにくい
  • 過剰なデータベース権限:
    • ORMは機能するためにより広範なデータベース権限を必要とすることが多い
    • テーブルスキーマ情報のクエリ、メタデータへのアクセス、リフレクションの実行が必要
    • 最小権限の原則に違反

■ 11. バンドルサイズ問題

  • 2024年ではバンドルサイズが重要
  • AstroやSvelteKitのようなフレームワークは最小限のJavaScriptを強調
  • しかしバックエンドに6.5MBのORMを追加
  • エッジコンピューティングとサーバーレス関数では全てのキロバイトが重要
  • Cloudflareの無料プランは3MB制限でPrismaはビジネスロジックを1行も書く前にその半分以上を消費

■ 12. ORMが依然として意味を持つ場合

  • ラピッドプロトタイピング:何かを素早く動作させる必要がありパフォーマンスが重要でない場合
  • ジュニアチーム:チームに強力なSQLスキルがない場合、ORMはガードレールを提供
  • シンプルなCRUDアプリケーション:複雑な関係性のない基本的な作成・読取・更新・削除操作
  • エンタープライズ要件:一部の組織はコンプライアンスや標準化の理由でORMを義務付け

■ 13. 移行戦略

  • 新機能から開始:全てを一度に書き直さず、選択した代替案で新機能を構築
  • パフォーマンスボトルネックの特定:プロファイリングを使用して最も遅いデータベース操作を見つける
  • ドメインごとに移行:アプリケーション内の境界付けられたコンテキストを選び、そのデータベースアクセスを全て一緒に移行
  • ツールへの投資:適切なデータベースマイグレーションツール、クエリビルダー、型生成をセットアップ

■ 14. 結論

  • Prismaから離れることでサーバーレスコストを40%削減、パフォーマンスを3倍改善、コードベースの保守性向上
  • データベースをより深く理解することを強制された
  • データベースを単なるストレージレイヤーとして扱うのをやめ、その力を活用し始めた
  • クエリがより効率的になり、データモデルがよりクリーンになり、デバッグセッションが短縮
  • ORMは複雑さを隠すことで生産性を高めると約束したが、実際には複雑さは隠されておらず、手遅れになるまで見えない場所に移動していただけ
  • データベースから隠れるのをやめ、受け入れ始める時かもしれない

MEMO: