$30 off During Our Annual Pro Sale. View Details »

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

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

Yamaguchi Takahiro

June 16, 2021
Tweet

More Decks by Yamaguchi Takahiro

Other Decks in Technology

Transcript

  1. RestAPIのページネーション
    atma バックエンド勉強会 #3
    2021/06/16 nyk510

    View Slide

  2. 目次
    ● ページネーションとは
    ● 紹介するページネーション
    ○ オフセット: Offset
    ○ キーセット: Keyset
    ○ シーク: Seek
    ○ カーソル: Cursor
    ● まとめ

    View Slide

  3. ページネーションとは
    たくさんあるレコードを一気に取ってくるとコストが高い
    ユーザが必要なのは全体のうち一部(ふつうそんな同時に見れない)
    必要な部分だけを取ってくるような仕組み → ページネーション
    ページネーションの暗黙的仮定
    ● 順繰りにめくるという操作のため順番がある
    ● したがって必ず順番が設定されている必要がある
    ● 例えばPrimaryKeyかもしれないし作成日(created_at)かもしれない

    View Slide

  4. 目次
    ● ページネーションとは
    ● 紹介するページネーション
    ○ オフセット: Offset
    ○ キーセット: Keyset
    ○ シーク: Seek
    ○ カーソル: Cursor
    ● まとめ

    View Slide

  5. ページネーションの方式
    名前 クエリ例 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方式

    View Slide

  6. 目次
    ● ページネーションとは
    ● 紹介するページネーション
    ○ オフセット: Offset
    ○ キーセット: Keyset
    ○ シーク: Seek
    ○ カーソル: Cursor
    ● まとめ

    View Slide

  7. オフセット型 OffsetPagination (PageNumberPagination)
    はじめから数えて何番目から取ってくるかを明示的に与える方式
    実行の例
    1. はじめに /?limit=20 でリクエスト
    2. 次のページがほしい時は最初から数えていくつからか(Offset)を同時に渡す
    a. 20*1=20 をoffsetに指定して
    b. /?limit=20&offset=20 でリクエスト
    3. 次のページは 20*2=40 をoffsetに指定してリクエスト

    View Slide

  8. OffsetPagination Benefits & Downsides
    ● 利点
    ○ 実装が簡単
    ○ サーバー上でステートレス(前の状態が不要)

    View Slide

  9. 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件しか取得できない
    挿入回数が少ない・データの上限が小さいアプリケーションにむいてる

    View Slide

  10. 目次
    ● ページネーションとは
    ● 紹介するページネーション
    ○ オフセット: Offset
    ○ キーセット: Keyset
    ○ シーク: Seek
    ○ カーソル: Cursor
    ● まとめ

    View Slide

  11. KeysetPagination
    別のField(Key)の値をどこまでめくったかの情報として次回のリクエストで使う方式
    実行の例
    1. はじめに /?limit=20 でリクエスト
    2. クライアントは受け取ったデータの中で最も小さい作成時間 A を見つけてくる次のリクエストの
    時には最小時間に上記の時間 A を指定
    a. /?limit=20&created_at__lte=A
    3. 次のリクエストの時も最小の時間を使ってリクエスト(2に戻る)

    View Slide

  12. KeysetPagination
    利点
    ● 特に追加実装が不要 (keyのlte/gte実装があれば limit だけつければ良い)
    ● 新しいデータが挿入されても一貫性がある
    ● 大きなオフセットでパフォーマンス優
    欠点
    ● ページネーションとフィルター・ソートが密結合
    ○ 今ソートされている要素の最小値を見つけてリクエストに追加する必要がある
    ○ たとえば複数のkeyでソートする場合などを想像すると大変さがわかるかも
    ● ソートするkeyがカーディナリティが低い場合機能しない
    ○ ユニーク数1のカラムをkeyにはできないという話

    View Slide

  13. 目次
    ● ページネーションとは
    ● 紹介するページネーション
    ○ オフセット: Offset
    ○ キーセット: Keyset
    ○ シーク: Seek
    ○ カーソル: Cursor
    ● まとめ

    View Slide

  14. 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. 次の時も同様に

    View Slide

  15. SeekPagination
    一見 id でのソートにしか対応できないように見えるがそうでもない
    例えば email でソートしたい時
    先の例の[2]でリクエストが行われた時、APIサーバー内部で以下の処理をする
    1. id=20をもつレコードのemailを取得. これを A とする
    2. email >= A の絞り込みの中で email,id でソートして limit 20 を取る
    → idがユニークなのでIDに対応するemailでsortし, ページめくりを行える

    View Slide

  16. SeekPagination / 利点と欠点
    利点
    ● ページネーションとフィルタを同時に考えなくて済む / クライアントで最小の値などを考える必
    要がない
    ● データが新しくインサートされても一貫性を失わない
    ● 大きなオフセットでパフォーマンス優
    欠点
    ● バックエンドの実装が若干煩雑
    ● after_idのレコードが削除された時に動かない (削除させない・論理削除等の工作が必要)
    ● ページをスキップできない / 直前のページにデータが追加されると欠損が起こる可能性がある

    View Slide

  17. SeekPagination
    Summary
    ● サーバー側で難しいことを担当するので、クライアントでは簡単に使えることができパフォーマ
    ンスも良い
    ● ページをスキップするような用途には不向き

    View Slide

  18. 目次
    ● ページネーションとは
    ● 紹介するページネーション
    ○ オフセット: Offset
    ○ キーセット: Keyset
    ○ シーク: Seek
    ○ カーソル: Cursor
    ● まとめ

    View Slide

  19. CursorPagination
    「今見てるレコードの場所情報が入ったなにか(cursor)」だけ渡すパターン。
    カーソルは難読化されていてクライアントからは内部状態はわからない。
    (逆に言うとカーソルだけ知っていれば他のことを気にしなくても良い)

    View Slide

  20. CursorPagination
    利点
    ● cursor以外隠匿されているので他のAPIに変えやすい
    ● レコードが削除された時にも対応可能
    欠点
    ● ページをスキップできない
    ● バックエンドの実装が若干煩雑
    ● 直前のページにデータが追加されると欠損が起こる可能性がある
    DRF実装を見ているとソート順番はユーザに選ばせないのがデフォルト
    制約が強いがそれで十分な場合には便利と言える?
    https://github.com/encode/django-rest-framework/blob/master/rest_framework/pagination.py#L577

    View Slide

  21. 実サービスではどれがつかわれてるの?
    千差万別・Cursorはパフォーマンスにも優れている/timelineの実装に向いているため採用率高め
    https://medium.com/@ignaciochiazzo/paginating-requests-in-apis-d4883d4c1c4c

    View Slide

  22. まとめ
    ● Offset:
    ○ 簡単でページを選べるのはO. 一方でパフォーマンス気になる・小さいデータ
    ● Keyset:
    ○ 一貫性とパフォーマンスは良いがクライアントはだるい/スキップできない
    ● Seed:
    ○ Keysetよりクライアントの煩雑さが減るのでKeysetを選ぶ理由はあまりない?
    ● Cursor:
    ○ ページは選べないがクライアントの利便性は一番。Timeline型ならこれ一択か。実装は大
    変そうだが大体のライブラリは用意してくれてると思う。

    View Slide

  23. 参考
    ● 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

    View Slide