HN 日本語サマリー

← 一覧へ戻る
プログラミング

ORMから学んだこと:SQLを学ぶべきだ (2014年)

What ORMs have taught me: just learn SQL (2014) (wozniak.ca)

140 pointsby ciconia182 コメント

要約

この記事は、著者がORM(Object-Relational Mapper)の使用経験から、SQLを直接学ぶことの重要性を説いています。ORMはSQLを補完するツールとしては有用ですが、SQLに取って代わるべきではないと主張しています。特に、属性の増加(attribute creep)や過剰な外部キー結合によるパフォーマンス低下、スキーマ定義の二重管理、エンティティIDの扱いの複雑さなど、ORMが引き起こす問題点を具体的に解説しています。

全文翻訳

ORMから学んだこと:SQLを学ぶべきだ 私は、ORMは利益よりも害の方が大きいという結論に至りました。要するに、プログラムでSQLを扱う作業をうまく補強するためにORMを使用することはできますが、ORMにSQLを置き換えさせるべきではありません。 いくつかの背景:過去30ヶ月間、私はPostgres、そしてある程度SQLiteと連携する必要のあるコードに取り組んできました。そのほとんどはSQLAlchemy(これはかなり気に入っています)とHibernate(これは気に入っていません)を使用しました。既存のコードとデータモデルで作業したこともあれば、独自のモデルを設計したこともあります。データの大部分はイベントベースのストレージ(「タイムライン」)であり、レポート作成に重点が置かれています。 オブジェクト/リレーショナルインピーダンスミスマッチについては多くのことが書かれています。それを実際に経験するまで、その困難さを十分に理解することは難しいです。Newardは、彼の有名なエッセイで、ORMがどのように泥沼にはまるかについての多くの説得力のある理由を挙げています。私の経験では、エンティティIDの問題、二重スキーマ問題、データ取得メカニズムの懸念、そして部分オブジェクト問題など、かなりの数の問題に対処しなければなりませんでした。これらの問題に関する私の経験について簡単に触れ、さらに私自身の問題も一つ加えたいと思います。 部分オブジェクト、属性の増加、外部キー 私がORMで経験した最も巧妙な問題の一つは、「属性の増加」または「ワイドテーブル」、つまり属性がどんどん増え続けるテーブルです。それを避けたいと思っても、時には必要になります(Postgresのhstoreのようなものが役立つこともありますが)。例えば、クライアントがレポートに基づいて様々なビジネスロジックに添付したい大量のデータを提供してくれることがあります。さらに、そのデータについてあまり洞察がなく、単にそれを運び回しているだけです。これ自体はデータベースにおいてはそれほどひどいことではありません。ORMでは、これは本当に厄介な問題になります。 具体的には、エンティティを直接クエリに使用する際に問題が現れ始めます。プロジェクトの初期段階では、以下のようなHibernateクエリを使用しているかもしれません。 query(Foo.class).add(Restriction.eq("x", value)) Fooが5つの属性しか持たない場合はこれで良いかもしれませんが、100個の属性を持つと、データが洪水のように流れ込んできます。これはSELECT *を使用するのと同等であり、通常は意図以上のものを取得しています。しかし、ORMはこのような使用を奨励しており、SQLで正確な射影を作成するのと同じくらい、面倒なものにすることがよくあります。(私は、適切な射影を追加してそのようなクエリを最適化し、実行時間を数分から数秒に短縮しました。すべての時間は、データベースの行をJavaオブジェクトに変換するのに費やされました。) これがもう一つの悪い経験につながります:外部キーの有害な使用です。私が使用したORMでは、クラス間のリンクはデータモデルで外部キーとして表現されますが、注意深く設定しないと、オブジェクトを取得する際に多数のJOINが発生します。(私の仕事で最近そのようなテーブルを数えたところ、単一のオブジェクトにアクセスするために600以上の属性と14のJOINが必要でした。これは推奨されるクエリ方法を使用した場合です。) 属性の増加と外部キーの過剰な使用は、ORMを効果的に使用するには、やはりSQLを知る必要があることを示しています。ORMに対する私の主張は、SQLを知る必要があるなら、SQLを知っているなら、SQLを使用すべきだということです。なぜなら、SQL以外のものがSQLにどのように変換されるかを知る必要がなくなるからです。 データ取得 ORMを使用して実際にクエリを作成しようとすると、SQLの書き方を知っていることがさらに重要になります。特に効率が懸念される場合は重要です。私の見たところでは、本当に単純なデータモデル(つまり、JOINを全く行わない場合)でない限り、ORMに効率的なSQLを生成させる方法を見つけるために、非常に苦労することになります。ほとんどの場合、それは実際のSQLよりも不明瞭です。そして、クエリを単純に保つことを選択すると、データベースでより速く行える作業の多くをコード内で行うことになります。 ウィンドウ関数は比較的高度なSQLであり、ORMで書くのは困難です。クエリにそれらを書き込まない場合、データベースからアプリケーションに大量の追加データを転送することになる可能性が高いです。これらのケースでは、テンプレートシステムを使用してクエリを書き、ORMでテーブルを記述することを選択しました。アプリケーションレベルでのテーブル記述の利便性を得つつ、SQLを直接使用できます。これまでのどの方法よりも手間がかかりません。 二重スキーマの危険性 これは避けられない冗長性の一つであるように思われます。それをなくそうとすると、さらに問題が増えたり、過剰な複雑さが加わったりするだけです。問題は、データベースとアプリケーションの2つの場所にデータ定義が存在することになることです。定義をアプリケーション全体に保持すると、ORMコードと共にSQLデータ定義言語(DDL)を書くことになり、ORMで高度なクエリを書くのと同じ複雑さになります。データベースに保持する場合、利便性のためにアプリケーション内に表現を用意し、「文字列タイピング」を過度に行わないようにしたいと思うでしょう。私はデータ定義をデータベースに保持し、それをアプリケーションに読み込むことを好みます。それは問題全体を解決するわけではありませんが、より管理しやすくします。 データ定義を取得するためのリフレクション技術は価値がないと判断し、2つの場所にデータ定義の冗長性を管理することに屈しました。しかし、面倒なマイグレーションの問題は本当に痛手です:アプリケーションでのモデルの変更はたいしたことではありませんが、データベースでは本当に面倒です。結局のところ、データベースは永続的ですが、アプリケーションデータはそうではありません。ORMはデータマイグレーションを全く支援しないため、ここでは単に邪魔になります。 私は、データベースのデータ定義はアプリケーションで操作すべきものではないという原則で作業しています。代わりに、クエリの結果を操作します。つまり、クエリがデータベースへのAPIとなります。そのため、オブジェクトについて考えるのではなく、戻り値の型を持つ関数について考えます。したがって、利便性のためにクエリを作成する以外にORMを使用すべきかどうかを問わざるを得ません。 ID エンティティIDの扱いは、ORMを使用する際に常に念頭に置かなければならないことの一つであり、1つの表現力しか持たないシステムのために、2つのシステムのために書くことを強制されます。外部キーがある場合、識別子で関連するIDを参照します。アプリケーションでは、「識別子」は様々な意味を持ちますが、通常はメモリ位置(ポインタ)です。データベースでは、それはオブジェクト自体の状態です。 これら2つのものは実際にはうまく調和しません。なぜなら、データベース(あなたが作業しているデータの最終的な宛先)では、データベース識別子しか使用できないからです。これがもたらすのは、キャッシュを手動でフラッシュしたり、部分的なコミットを行ったりして実際のデータベース識別子を取得するために、ORMを操作しなければならないということです。これは、内容のわずかな「漏れ」がソースに対して相対的であることを意味するため、リーキー抽象化とさえ呼べません。 トランザクション Newardが示唆しているのは、開発者がトランザクションを処理する必要があるということです。トランザクションは動的にスコープされるもので、強力ですが、プログラム言語では過剰に使用すると混乱を招くため、ほとんど無視されている概念です。これは、例外ハンドラを伴う多くのボイラープレートコードと、トランザクションの境界がどこに発生すべきかについての慎重な検討を必要とします。 また、データベースと通信する可能性のある任意の関数/メソッドにセッションオブジェクトを渡す必要も生じます。トランザクションの概念は、時間に基づいたコンテキストへの依存のため、アプリケーションにはうまく翻訳されません。前述のように、動的スコープはこのプログラムで使用する1つの方法ですが、主流のパラダイムであるレキシカルスコープとは対立します。 したがって、データベースと連携するコードを書く際には、トランザクションの「いつ」について十分に注意する必要があります。これはモジュール性を難しくする可能性があります(「これは特定のコンテキストでのみ機能する便利な関数です」)。 私はどこへ向かっているのか? この時点で、私はストアドプロシージャの完全な拒否の賢明さについて疑問を抱き始めています。それは冒涜的に聞こえるかもしれませんが、私のユースケースには有効かもしれません。(そして、「DevOps」の登場により、開発と運用