ページング性能を落とさないDB実装のポイント
データベースのページング実装は性能に直結する設計判断です。Offset方式とCursor方式では大規模データでの性能差が数十倍になることもあります。しかし、適切な方式を選べば問題は解決できます。そこで今回は、ページング性能を落とさないDB実装の考え方を解説します。
Offset方式の仕組みと性能問題
Offset方式は最も一般的なページング手法です。具体的には、SQLのOFFSETとLIMITで実現します。つまり、指定した位置から指定件数を取得する方式です。しかし、大きなOFFSET値では深刻な性能問題が発生します。
なぜなら、データベースはOFFSET分の行をすべて読み飛ばすからです。たとえば、OFFSET 10000で100件取得する場合です。実際には10100行を読み取ります。さらに、ページが深くなるほど遅くなります。そのため、100万件のデータで後半ページは極端に遅くなります。
Cursor方式の仕組みとメリット
Cursor方式は「最後に取得した行」を基準にする手法です。つまり、WHERE句で前ページの最後のIDより大きい行を取得します。しかも、インデックスを効率的に活用できます。そのため、ページの深さに関係なく一定の速度を維持します。
具体的には、WHERE id > last_id ORDER BY id LIMIT 100という形式です。また、複合ソートにも対応できます。さらに、クエリプランがインデックススキャンになるため高速です。実際、100万件のデータでも最初のページと同じ速度で取得できます。特に、無限スクロールのUIと相性が良いです。
Offset方式を使うべき場面
しかし、Offset方式にも適した場面があります。たとえば、データ量が少ない場合です。また、任意のページにジャンプする必要がある場合です。つまり、「5ページ目に直接アクセス」したい場合です。
さらに、管理画面のように利用者が限られる場面でも問題ありません。そのため、全ページ数を表示するUIにはOffset方式が適しています。なお、データ量が1万件以下なら性能差はほぼ感じません。このように、要件に応じて使い分けることが重要です。
実装時の注意点
Cursor方式の実装にはいくつか注意点があります。特に、カーソル値のエンコーディングが重要です。また、ソート順が変わるとカーソルが無効になります。さらに、データの追加や削除でページがずれる問題もあります。
具体的には、Base64でカーソル値をエンコードする方法が一般的です。しかし、不正なカーソル値のバリデーションも必要です。つまり、セキュリティの観点も考慮します。そのため、GraphQLのConnection仕様を参考にするのがベストプラクティスです。
まとめ
ページング性能はOffset方式とCursor方式の選択で大きく変わります。大規模データではCursor方式が圧倒的に有利です。しかし、ページジャンプが必要な場面ではOffset方式も有効です。特に、要件に応じた適切な使い分けがDB設計の鍵です。