Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RestAPIのページネーション atma バックエンド勉強会 #3

RestAPIのページネーション atma バックエンド勉強会 #3

Yamaguchi Takahiro

June 16, 2021
Tweet

More Decks by Yamaguchi Takahiro

Other Decks in Technology

Transcript

  1. ページネーションの方式 名前 クエリ例 Cost 簡単 途中から 一貫性 オフセット OffsetPagination /?limit=20&offset=100

    X O O X キーセット KeysetPagination /?limit=20&key__lte=hoge O △ (クライアントが大 変) X O シーク SeekPagination /?limit=20&after_id=hoge O △ X O カーソル CursorPagination /?cursor=hogehoge O X X O 今日紹介する4方式
  2. オフセット型 OffsetPagination (PageNumberPagination) はじめから数えて何番目から取ってくるかを明示的に与える方式 実行の例 1. はじめに /?limit=20 でリクエスト 2.

    次のページがほしい時は最初から数えていくつからか(Offset)を同時に渡す a. 20*1=20 をoffsetに指定して b. /?limit=20&offset=20 でリクエスト 3. 次のページは 20*2=40 をoffsetに指定してリクエスト
  3. OffsetPagination Benefits & Downsides 欠点 • 大きいオフセット値を使うとパフォーマンスが下がる ◦ SQLでOffsetNinを使う為 ◦

    The larger the offset, the slower the request is, up until the point that it times out. https://www.shopify.com/partners/blog/relative-pagination • 新しいデータが挿入されると一貫性を保てない(ページドリフト) ◦ 時間でソートされている時最初に20件取得したあと15件の新規レコードが挿入されたと仮 定する ◦ 2回めのリクエストでは新しく5件しか取得できない 挿入回数が少ない・データの上限が小さいアプリケーションにむいてる
  4. KeysetPagination 別のField(Key)の値をどこまでめくったかの情報として次回のリクエストで使う方式 実行の例 1. はじめに /?limit=20 でリクエスト 2. クライアントは受け取ったデータの中で最も小さい作成時間 A

    を見つけてくる次のリクエストの 時には最小時間に上記の時間 A を指定 a. /?limit=20&created_at__lte=A 3. 次のリクエストの時も最小の時間を使ってリクエスト(2に戻る)
  5. KeysetPagination 利点 • 特に追加実装が不要 (keyのlte/gte実装があれば limit だけつければ良い) • 新しいデータが挿入されても一貫性がある •

    大きなオフセットでパフォーマンス優 欠点 • ページネーションとフィルター・ソートが密結合 ◦ 今ソートされている要素の最小値を見つけてリクエストに追加する必要がある ◦ たとえば複数のkeyでソートする場合などを想像すると大変さがわかるかも • ソートするkeyがカーディナリティが低い場合機能しない ◦ ユニーク数1のカラムをkeyにはできないという話
  6. SeekPagination UniqueなID列を使いページの次(after_id) 最初(start_id) をパラメータに追加する方式 ページネーションとフィルター・ソートの密結合を解消する 例: unique列を id として idでソートしたページネーション

    1. /?limit=20 で取得。このときAPIから次のページのはじめのID(after_id)をもらう 2. 次のページを取得するときには after_id を利用してリクエスト a. 例えば0スタートなら after_id=20 になるので /?limit=20&after_id=20 3. 次の時も同様に
  7. SeekPagination 一見 id でのソートにしか対応できないように見えるがそうでもない 例えば email でソートしたい時 先の例の[2]でリクエストが行われた時、APIサーバー内部で以下の処理をする 1. id=20をもつレコードのemailを取得.

    これを A とする 2. email >= A の絞り込みの中で email,id でソートして limit 20 を取る → idがユニークなのでIDに対応するemailでsortし, ページめくりを行える
  8. SeekPagination / 利点と欠点 利点 • ページネーションとフィルタを同時に考えなくて済む / クライアントで最小の値などを考える必 要がない •

    データが新しくインサートされても一貫性を失わない • 大きなオフセットでパフォーマンス優 欠点 • バックエンドの実装が若干煩雑 • after_idのレコードが削除された時に動かない (削除させない・論理削除等の工作が必要) • ページをスキップできない / 直前のページにデータが追加されると欠損が起こる可能性がある
  9. CursorPagination 利点 • cursor以外隠匿されているので他のAPIに変えやすい • レコードが削除された時にも対応可能 欠点 • ページをスキップできない •

    バックエンドの実装が若干煩雑 • 直前のページにデータが追加されると欠損が起こる可能性がある DRF実装を見ているとソート順番はユーザに選ばせないのがデフォルト 制約が強いがそれで十分な場合には便利と言える? https://github.com/encode/django-rest-framework/blob/master/rest_framework/pagination.py#L577
  10. まとめ • Offset: ◦ 簡単でページを選べるのはO. 一方でパフォーマンス気になる・小さいデータ • Keyset: ◦ 一貫性とパフォーマンスは良いがクライアントはだるい/スキップできない

    • Seed: ◦ Keysetよりクライアントの煩雑さが減るのでKeysetを選ぶ理由はあまりない? • Cursor: ◦ ページは選べないがクライアントの利便性は一番。Timeline型ならこれ一択か。実装は大 変そうだが大体のライブラリは用意してくれてると思う。
  11. 参考 • REST API Design: Filtering, Sorting, and Pagination ◦

    https://www.moesif.com/blog/technical/api-design/REST-API-Design-Filtering-Sort ing-and-Pagination/ • DjangoRestFramework: Pagination ◦ https://www.django-rest-framework.org/api-guide/pagination/ • How to Use Relative Pagination In Your Application ◦ https://www.shopify.com/partners/blog/relative-pagination