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

GraphQLにおける​ページネーションベストプラクティス

 GraphQLにおける​ページネーションベストプラクティス

2024年9月13日の「テックリードの悩みを解決するGraphQLの話」というイベントで登壇した資料です。
https://estie.connpass.com/event/328999/

estie | エスティ

September 13, 2024
Tweet

More Decks by estie | エスティ

Other Decks in Programming

Transcript

  1. © 2024 estie, inc. 野村 大樹 (@h_noson) estie Software Engineer

    2018年に株式会社リクルートホールディングスに新卒 入社しIndeedへ出向。1年後にIndeedに転籍。 2022年よりestieに参画。テックリードとしてestie マーケット調査の開発をリード。 自己紹介 2
  2. © 2024 estie, inc. Agenda • Paginationとは • Offset based

    と Cursor based の概要と利点・欠点 • どちらを使うべきなの? • Offset basedの欠点を補う方法 3
  3. © 2024 estie, inc. • よく使われるシンプルなPaginationの方式 • offset または page

    と limit を指定する • 例:https://estie.jp/buildings?page=2&limit=10 o 10棟毎に区切ったときの2ページ目 • 2ページ目から5ページ目など、ページ移動が自由 6 Offset based Pagination
  4. © 2024 estie, inc. Offset based Pagination 7 type Query

    { buildings(page: Int! = 1, limit: Int! = 50): BuildingPaginationPayload } type BuildingPaginationPayload { nodes: [Building!]! pageInfo: OffsetPageInfo! } type OffsetPageInfo { """ 1-indexed """ page: Int! totalPages: Int! totalCount: Int! } GraphQL Schema定義例
  5. © 2024 estie, inc. • GraphQLにおけるベストプラクティス (https://graphql.org/learn/pagination/) • Relay connectionsが標準仕様

    (https://relay.dev/graphql/connections.htm) o "cursor” (after, before) と取得する要素数 (first, last) を指定する o 例: https://estie.jp/buildings?after=MTIzCg&first=10 • レスポンスに始点と終点の “cursor” が含まれており、前後のページへ のみ遷移できる “cursor” とは • IDなどレコードを特定する何かしらの値 • Base64などでencodeして中身を曖昧するべき (“MTIzCg==“ = Base64(“123”)) o クライアントがその値に依存せず仕様変更できるようにする目的 Cursor based Pagination 8
  6. © 2024 estie, inc. GraphQL Schema定義例 Cursor based Pagination 9

    type Query { buildings( after: String, before: String, first: Int, last: Int ): BuildingConnection } type BuildingConnection { edges: [BuildingEdge!]! pageInfo: PageInfo! } type BuildingEdge { node: Building! cursor: String! } type PageInfo { startCursor: String! endCursor: String! hasNextPage: Boolean! hasPreviousPage: Boolean! }
  7. © 2024 estie, inc. • 一定量以上の情報を表示する際にページを分割する方法 • 大きく2種類 o 任意のページに移動できる(例:Google)これ↑

    o スクロールなどで前後のページを取得する(例:X, Googleモバイル版) Paginationとは 10 前者がOffset based、後者がCursor based でよいのでは?
  8. © 2024 estie, inc. • 一定量以上の情報を表示する際にページを分割する方法 • 大きく2種類 o 任意のページに移動できる(例:Google)これ↑

    o スクロールなどで前後のページを取得する(例:X, Googleモバイル版) Paginationとは 11 前者がOffset based、後者がCursor based でよいのでは? 大体そう だがそれぞれの利点を理解した上で選択しましょう
  9. © 2024 estie, inc. それぞれの利点・欠点 13 Offset based Cursor based

    利点 • 仕様や実装がシンプル • 連続していないページにアクセスできる • 条件を満たしたときクエリ実行が高速 • 更新に強い • GraphQLのライブラリに標準実装されているこ とが多い • 例:async-graphql (Rust), graphql-ruby 欠点 • テーブルが大きい場合、パフォーマンスに 悪影響がある • 更新に弱い • 連続したページにのみアクセスできる • offset basedに比べて仕様や実装が複雑
  10. © 2024 estie, inc. それぞれの利点・欠点 14 Offset based Cursor based

    利点 • 仕様や実装がシンプル • 連続していないページにアクセスできる • 条件を満たしたときクエリ実行が高速 • 更新に強い • GraphQLのライブラリに標準実装されているこ とが多い • 例:async-graphql (Rust), graphql-ruby 欠点 • テーブルが大きい場合、パフォーマンスに 悪影響がある • 更新に弱い • 連続したページにのみアクセスできる • offset basedに比べて仕様や実装が複雑
  11. © 2024 estie, inc. Offset basedで発行されるSQLクエリ SELECT * FROM buildings

    WHERE ... LIMIT 10 OFFSET 100; Cursor basedで発行されるSQLクエリ idがcursorのとき(高速) SELECT * FROM buildings WHERE ... AND id > 123 LIMIT 10; offsetがcursorのとき(低速) SELECT * FROM buildings WHERE ... LIMIT 10 OFFSET 100; 複数キーからcursorが生成されるとき(indexを張ると高速) SELECT * FROM buildings WHERE updated_at > '2024-01-01' OR (updated_at = '2024-01-01' AND id > 123) ORDER BY updated_at, id LIMIT 10; パフォーマンス 15 OFFSETが大きいと遅くなる O(N)
  12. © 2024 estie, inc. それぞれの利点・欠点 16 Offset based Cursor based

    利点 • 仕様や実装がシンプル • 連続していないページにアクセスできる • 条件を満たしたときクエリ実行が高速 • 更新に強い • GraphQLのライブラリに標準実装されているこ とが多い • 例:async-graphql (Rust), graphql-ruby 欠点 • テーブルが大きい場合、パフォーマンスに 悪影響がある • 更新に弱い • 連続したページにのみアクセスできる • offset basedに比べて仕様や実装が複雑
  13. © 2024 estie, inc. Offset based データが更新されるとどうなるか 17 ID=1 2

    3 4 5 page=1&limit=3 page=2&limit=3 ID=3を削除 ID=1 2 4 5 ID=1 2 4 5 スキップされる
  14. © 2024 estie, inc. Cursor based データが更新されるとどうなるか 18 ID=1 2

    3 4 5 first=3 after=“3”&first=3 ID=3を削除 ID=1 2 4 5 ID=1 2 4 5 スキップされない
  15. © 2024 estie, inc. Cursor based • データ量が膨大で絞り込みをあまりせずに取得する(OFFSETが大きくなり得る) • 追加や削除が起こる

    • 例:X, Facebook Offset based • ページを跨いで遷移したい(2ページ目→5ページ目) • Cursor basedの恩恵をあまり受けられない • OFFSETが大きくならない(Googleでは1000件以上表示できない) • 追加や削除がない(or 検索インデックスのバージョンを固定してページ遷移する) • 様々な項目でソートしたい(estieではこのケースが多い) • ソートキーをcursorにしてindexを張ればCursor basedでも実装可能 • 例:Google, Amazon, LinkedIn 20 方式の選択基準
  16. © 2024 estie, inc. 22 追加や削除に対応した Offset based Pagination •

    ページを跨いで遷移したいかつ追加・削除が起こるケースは存在する • ページを跨いで遷移したい → Offset based • 追加・削除が起こる → Subscriptionで変更を監視して調整する Subscription • WebSocketまたはHTTPを使って同期的にデータを取得できる subscription UserUpdated { userUpdated { id } }
  17. © 2024 estie, inc. 23 追加や削除に対応した Offset based Pagination(例:順位表) type

    User { id: ID! score: Int! } type Query { rankedUsers(page: Int! = 1, limit: Int! = 50): UserPaginationPayload } type Mutation { updateScore(userId: ID!, score: Int!): UpdateScorePayload } type Subscription { userUpdated: User }
  18. © 2024 estie, inc. 24 まとめ • GraphQLではCursor based Paginationが公式に推奨されているが、欠点

    もありOffset basedが適しているケースもある • Cursor basedは高速で更新に強い • Offset basedは仕様がシンプルで自由にページ遷移できる
  19. © 2024 estie, inc. • GraphQL Foundation • https://graphql.org/learn/pagination/ •

    Relay - GraphQL Cursor Connections Specification • https://relay.dev/graphql/connections.htm • Production Ready GraphQL • https://book.productionreadygraphql.com/ • Cursor based pagination with arbitrary ordering • https://medium.com/@george_16060/cursor-based-pagination-with-arbitrary- ordering-b4af6d5e22db 参考 25