ページネーション
#アーキテクチャ大全
設計ポイント
ページネーションクエリには以下の3種類が存在する。
オフセットベース
タイムベース
カーソルベース
基本はオフセットベースで実装することになるが、カーソルベースの方が性能的には有利なので、カーソルベースページネーションにできないかは常に検討する。
オフセットベース
offset/limit または page/limit をクエリパラメータとして渡す。まんまSQLや検索エンジンのoffset/limitとなる。
table:パラメータ
offset 何件目から取得するか?
page 何ページ目から取得するか? (offsetと排他的)
limit 何件取得するか?
検索エンジンでも、大きな結果セットに対して大きなオフセットを指定することは、かなりリソースを消費するクエリとなる。したがって、「最後のページへ」のようなページネーションは避けた方が良い。
タイムベース
イベントエンティティのように日付属性が含まれているものが検索対象の場合に使うことができる。
table:パラメータ
since 検索対象日時From
until 検索対象日時To
limit 何件取得するか?
limitでページネーションする場合は、カーソルを併用する。
カーソルベース
時系列など、一定の軸でソート可能なIDを発行したデータについて、そのIDをキーとして検索する。
table:パラメータ
cursor ソート可能なID
limit 何件取得するか?
IDはソート可能なものを使う。 →参照: https://qiita.com/kawasima/items/6b0f47a60c9cb5ffb5c4
code:テーブル設計例
tweets
+------------+
|cursor | <- PKでなくてもよい。(FlakeやULIDで発番すると良い)
+------------+
|description |
|posted_by |
|posted_at | <- 日時は一意にならないので、カーソルとしては
| | 使わない
+------------+
code:sql
SELECT description, posted_by, posted_at
FROM tweets
WHERE cursor < ?
LIMIT 10
ORDER BY cursor DESC
TwitterやFacebookのような更新データの多いタイムライン表示で、オフセットベースだとページ切り替えのときに検索結果が変わってしまうものに関しては、カーソルベースのページネーションがよく使われる。無限スクロール(infinite scroll)との相性もよい。
ただし、以下の制限がある
特定のページ番号へのダイレクトリンクはできない。「前へ」「次へ」のリンクのみ
カーソルの指し示すデータが削除されると、ページングはできなくなる(カーソルがWHERE句に入るため)。
参考文献
https://www.sitepoint.com/paginating-real-time-data-cursor-based-pagination/
https://www.novatec-gmbh.de/en/blog/art-pagination-offset-vs-value-based-paging/
https://use-the-index-luke.com/no-offset
https://www.citusdata.com/blog/2016/03/30/five-ways-to-paginate/