Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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