Pagination
Pagination คือการคืน collection ออกมาเป็นชิ้น (slice) มีกลยุทธ์ที่นิยมใช้กันสองแบบ และการเลือกที่ถูกต้องขึ้นอยู่กับว่าข้อมูลของคุณใหญ่และเปลี่ยนแปลงบ่อยแค่ไหน
Offset pagination
หัวข้อที่มีชื่อว่า “Offset pagination”client ขอหน้าตามตำแหน่ง: ?page=3&limit=20 (หรือ ?offset=40&limit=20) เรียบง่าย กระโดดไปหน้าไหนก็ได้ และแสดงจำนวนรวมได้ง่าย
GET /articles?page=2&limit=20จุดอ่อนมีสองข้อ: offset ที่ลึกจะช้า (database ต้องข้ามทุกแถวที่อยู่ก่อนหน้า) และ การ insert จะทำให้ window เลื่อน — ถ้ามีแถวเพิ่มเข้ามาระหว่างที่คุณกำลังเปิดหน้าอยู่ คุณอาจเห็นข้อมูลซ้ำหรือข้ามรายการไป
Cursor pagination
หัวข้อที่มีชื่อว่า “Cursor pagination”client ส่ง cursor แบบ opaque ที่บอกตำแหน่งที่หน้าก่อนหน้าจบลง: ?limit=20&cursor=eyJpZCI6MTQwfQ server จะคืน slice ถัดไปพร้อม cursor สำหรับหน้าหลังจากนั้น
flowchart LR A[GET /articles?limit=20] --> B[20 items + nextCursor] B --> C[GET /articles?limit=20&cursor=...] C --> D[next 20 + nextCursor]
cursor มีความเสถียรเมื่อมีการ insert และเร็วไม่ว่าจะอยู่ลึกแค่ไหน แต่คุณจะกระโดดไปหน้าไหนก็ได้ตามใจไม่ได้ และการนับจำนวนรวมให้แม่นยำก็ทำได้ยากกว่า ถึงอย่างนั้นก็ยังเป็นค่าเริ่มต้นที่ดีกว่าสำหรับ collection ขนาดใหญ่หรือเปลี่ยนแปลงบ่อย (feed, log, search)
การ slice และ envelope
หัวข้อที่มีชื่อว่า “การ slice และ envelope”หนึ่งหน้าควรถูกห่อไว้ใน envelope ที่มี data พร้อมกับ metadata ของการแบ่งหน้าและ links:
| Pagination Style | ข้อดี | ข้อเสีย | เหมาะกับ |
|---|---|---|---|
Offset (?page=2&limit=20) | ง่าย, random access | unstable เมื่อ data เปลี่ยน | Admin table, fixed dataset |
Cursor (?after=cursor123) | stable, performance ดีกับ large dataset | ไม่รู้ total, ข้ามหน้าไม่ได้ | Feed, infinite scroll |
Keyset (?after_id=42) | เร็ว, stable | ต้อง unique sortable key | Large sorted collection |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”Offset Pagination สำหรับ Real-time Feed อาการ:
- feed ที่ข้อมูลเพิ่มบ่อย — หน้า 2 เลื่อนเป็นหน้า 3 เพราะข้อมูลใหม่เข้า
- user เห็น item ซ้ำหรือ item หายระหว่าง paginate
- ใช้ cursor pagination สำหรับ real-time data
ไม่มี Pagination Default อาการ:
GET /usersreturn ทุก record โดยไม่จำกัด- database และ memory ระเบิดเมื่อ user มีล้านคน
- ตั้ง default limit (
limit=20) และ max limit (limit=100) เสมอ
💡 ตัวอย่างจากของจริง
GitHub API:
- cursor pagination ผ่าน
Linkheader:<https://api.github.com/repos?page=2>; rel="next"- default 30, max 100 items per page
Stripe API:
- cursor pagination:
GET /charges?starting_after=ch_xxx&limit=20has_more: trueบอก client ว่ามีข้อมูลเพิ่ม- cursor-based เพราะ charge เพิ่มตลอดเวลา