KoderKoder.ai
ราคาองค์กรการศึกษาสำหรับนักลงทุน
เข้าสู่ระบบเริ่มต้นใช้งาน

ผลิตภัณฑ์

ราคาองค์กรสำหรับนักลงทุน

ทรัพยากร

ติดต่อเราสนับสนุนการศึกษาบล็อก

กฎหมาย

นโยบายความเป็นส่วนตัวข้อกำหนดการใช้งานความปลอดภัยนโยบายการใช้งานที่ยอมรับได้แจ้งการละเมิด

โซเชียล

LinkedInTwitter
Koder.ai
ภาษา

© 2026 Koder.ai สงวนลิขสิทธิ์

หน้าแรก›บล็อก›การแบ่งหน้าแบบเคอร์เซอร์สำหรับรายการ API ที่เสถียรโดยไม่มีบั๊กลึกลับ
18 ธ.ค. 2568·1 นาที

การแบ่งหน้าแบบเคอร์เซอร์สำหรับรายการ API ที่เสถียรโดยไม่มีบั๊กลึกลับ

การแบ่งหน้าแบบเคอร์เซอร์ทำให้รายการคงที่เมื่อข้อมูลเปลี่ยน เรียนรู้ว่าทำไมการแบ่งหน้าด้วย offset ถึงพังเมื่อมีการแทรก/ลบ และวิธีการสร้างเคอร์เซอร์ที่สะอาด

การแบ่งหน้าแบบเคอร์เซอร์สำหรับรายการ API ที่เสถียรโดยไม่มีบั๊กลึกลับ

ปัญหา: หน้ารายการที่เปลี่ยนไปขณะใช้งาน

คุณเปิดฟีด เลื่อนลงไปบ้าง แล้วทุกอย่างก็ดูปกติจนกระทั่งไม่เป็นไปตามนั้น คุณเห็นรายการเดิมสองครั้ง สิ่งที่คุณมั่นใจว่าเคยอยู่กลับหายไป แถวที่คุณจะกดเลื่อนไปและคุณไปหน้ารายการผิด

นี่คือบั๊กที่ผู้ใช้จะเห็น แม้ว่าการตอบกลับจาก API แต่ละครั้งจะดู “ถูกต้อง” เมื่อแยกกัน อาการที่พบบ่อยมีดังนี้:

  • รายการซ้ำกันข้ามหน้า
  • รายการหายไปโดยที่ไม่เคยเห็นมาก่อน
  • รายการกระโดดตำแหน่งขณะเลื่อน
  • การเลื่อนแบบไม่รู้จบหยุดก่อนหรือโหลดหน้าซ้ำ

ปัญหานี้แย่ขึ้นบนมือถือ ผู้ใช้หยุดชั่วคราว สลับแอป หลุดการเชื่อมต่อ แล้วกลับมาต่อ ในช่วงเวลานั้น รายการใหม่ถูกสร้าง ลบรายการเก่า และบางรายการถูกแก้ไข ถ้าแอปของคุณยังคงขอ “หน้า 3” โดยใช้ offset ขอบเขตหน้าสามารถเลื่อนได้ขณะที่ผู้ใช้กำลังเลื่อน ผลคือฟีดที่รู้สึกไม่เสถียรและไม่น่าเชื่อถือ

เป้าหมายไม่ยาก: เมื่อลูกค้าเริ่มเลื่อนไปข้างหน้า รายการควรทำงานเหมือนสแนปชอต รายการใหม่สามารถมีได้ แต่ไม่ควรทำให้สิ่งที่ผู้ใช้กำลังดูเปลี่ยนตำแหน่ง ผู้ใช้ควรได้รับลำดับที่ราบรื่นและคาดเดาได้

ไม่มีวิธีแบ่งหน้าที่สมบูรณ์แบบ ระบบจริงมีการเขียนพร้อมกัน การแก้ไข และตัวเลือกการเรียงลำดับหลายแบบ แต่การแบ่งหน้าแบบเคอร์เซอร์ปลอดภัยกว่าการแบ่งหน้าแบบออฟเซ็ตในหลายกรณี เพราะมันเลื่อนจากตำแหน่งเฉพาะในลำดับที่เสถียร แทนที่จะอ้างจากจำนวนแถวที่เปลี่ยนไป

การแบ่งหน้าแบบออฟเซ็ตแบบย่อ

การแบ่งหน้าแบบออฟเซ็ตคือการทำ “ข้าม N แถว แล้วเอา M แถว” คุณบอก API ว่าจะข้ามกี่แถว (offset) และส่งกลับกี่แถว (limit) กับ limit=20 คุณจะได้ 20 รายการต่อหน้า

เชิงแนวคิด:

  • GET /items?limit=20&offset=0 (หน้าแรก)
  • GET /items?limit=20&offset=20 (หน้าที่สอง)
  • GET /items?limit=20&offset=40 (หน้าที่สาม)

การตอบกลับมักรวมรายการพร้อมข้อมูลพอให้ขอหน้าถัดไป

{
  "items": [
    {"id": 101, "title": "..."},
    {"id": 100, "title": "..."}
  ],
  "limit": 20,
  "offset": 20,
  "total": 523
}

มันเป็นที่นิยมเพราะแมปได้ตรงกับตาราง รายการแอดมิน ผลการค้นหา และฟีดง่ายๆ และง่ายต่อการทำใน SQL ด้วย LIMIT และ OFFSET

ข้อเสียคือสมมติฐานที่ซ่อนอยู่: ชุดข้อมูลคงที่ระหว่างการร้องขอ ในแอปจริง แถวใหม่ถูกแทรก แถวถูกลบ และคีย์การเรียงลำดับเปลี่ยน นั่นคือจุดที่บั๊กลึกลับเริ่มเกิด

ทำไม offset ถึงพังเมื่อมีการแทรกหรือลบแถว

การแบ่งหน้าแบบออฟเซ็ตสมมติว่ารายการนิ่งระหว่างคำขอ แต่รายการจริงไหล เมื่อรายการเลื่อน offset เช่น “ข้าม 20” จะไม่ชี้ไปยังรายการเดิมอีกต่อไป

ลองจินตนาการฟีดเรียงตาม created_at desc (ใหม่สุดก่อน) ขนาดหน้า 3

โหลดหน้า 1 ด้วย offset=0, limit=3 แล้วได้ [A, B, C]

ตอนนี้มีรายการใหม่ X ถูกสร้างและแทรกมาด้านบน รายการจะเป็น [X, A, B, C, D, E, F, ...] คุณโหลดหน้า 2 ด้วย offset=3, limit=3 เซิร์ฟเวอร์ข้าม [X, A, B] แล้วคืน [C, D, E]

คุณจึงเห็น C ซ้ำ และต่อมาคุณจะพลาดรายการบางอันเพราะทุกอย่างเลื่อนลง

การลบทำให้ล้มเหลวแบบตรงกันข้าม เริ่มจาก [A, B, C, D, E, F, ...] คุณโหลดหน้า 1 เห็น [A, B, C] ก่อนหน้า 2 B ถูกลบ รายการกลายเป็น [A, C, D, E, F, ...] หน้า 2 กับ offset=3 ข้าม [A, C, D] แล้วคืน [E, F, G] D จึงเป็นช่องว่างที่คุณไม่เคยดึงมา

ในฟีดแบบใหม่สุดก่อน การแทรกเกิดที่ด้านบน ซึ่งเป็นสาเหตุที่ทำให้ทุก offset ถัดไปเลื่อนไป

รายการเสถียรหมายถึงอะไรสำหรับเว็บและมือถือ

“รายการเสถียร” คือสิ่งที่ผู้ใช้คาดหวัง: เมื่อพวกเขาเลื่อนไปข้างหน้า รายการไม่กระโดด ซ้ำ หรือลบโดยไม่มีเหตุผลชัดเจน มันไม่ใช่การแช่เวลาเท่านั้น แต่คือการทำให้การแบ่งหน้าคาดเดาได้

สองแนวคิดที่มักสับสนกัน:

  • เส้นการเรียงลำดับที่เสถียร: มีกฎการเรียงชัดเจน (เช่น created_at พร้อมตัวเบรกอย่าง id) ดังนั้นคำขอสองครั้งด้วยอินพุตเดียวกันจะคืนลำดับเดียวกัน
  • การแบ่งหน้าแบบเสถียร: เมื่อลูกค้าเริ่มเลื่อนไปข้างหน้า หน้าถัดไปจะต่อจากรายการสุดท้ายที่พวกเขาเห็น แม้ว่าจะมีการเพิ่มหรือลบรายการใหม่ก็ตาม

การรีเฟรชและการเลื่อนไปข้างหน้าคือการกระทำคนละแบบ รีเฟรชหมายถึง “แสดงสิ่งใหม่เดี๋ยวนี้” ด้านบนจึงเปลี่ยนได้ การเลื่อนไปข้างหน้าหมายถึง “ไปต่อจากที่ฉันอยู่” ดังนั้นคุณไม่ควรเห็นการซ้ำหรือช่องว่างที่เกิดจากขอบเขตหน้าที่เปลี่ยนไป

กฎง่ายๆ ที่ป้องกันบั๊กส่วนใหญ่: การเลื่อนไปข้างหน้าไม่ควรแสดงรายการซ้ำ

การแบ่งหน้าแบบเคอร์เซอร์: แนวคิดง่ายๆ

การแบ่งหน้าแบบเคอร์เซอร์เลื่อนผ่านรายการโดยใช้ที่คั่นแทนหมายเลขหน้า แทนที่จะบอก “ให้หน้าที่ 3” ไคลเอนต์จะบอกว่า “ต่อจากตรงนี้”

สัญญา (contract) ง่าย:

  • API คืนชุดรายการพร้อมเคอร์เซอร์ที่แทนตำแหน่งหลังรายการสุดท้าย
  • ไคลเอนต์ส่งเคอร์เซอร์นั้นกลับเพื่อขอชุดถัดไป

วิธีนี้ทนต่อการแทรกและการลบได้ดีกว่าเพราะเคอร์เซอร์ฝังอยู่กับตำแหน่งในลำดับที่เรียง ไม่ใช่จำนวนแถวที่ไหล

ข้อกำหนดสำคัญคือการเรียงลำดับต้องกำหนดได้แบบ deterministic คุณต้องมีกฎการเรียงที่เสถียรและตัวเบรกของการเสมอกัน มิฉะนั้นเคอร์เซอร์จะไม่เป็นที่คั่นตำแหน่งที่น่าเชื่อถือ

การเลือกเคอร์เซอร์และการเรียงลำดับ

ปรับใช้ฟีดของคุณอย่างรวดเร็ว
สร้าง service แล้วปรับใช้และโฮสต์จาก workspace ของคุณบน Koder.ai
ปรับใช้แอป

เริ่มจากเลือกการเรียงลำดับที่สอดคล้องกับวิธีที่คนอ่านรายการ ฟีด ข้อความ และบันทึกกิจกรรมมักเป็น newest first ประวัติอย่างใบแจ้งหนี้หรือตรวจสอบมักง่ายกว่า oldest first

เคอร์เซอร์ต้องระบุตำแหน่งในลำดับนั้นอย่างไม่ซ้ำกัน หากสองรายการมีค่าคีย์เดียวกัน คุณจะได้ duplicate หรือ gap

ตัวเลือกที่ใช้บ่อยและสิ่งที่ต้องระวัง:

  • created_at อย่างเดียว: ง่าย แต่ไม่ปลอดภัยถ้ามีหลายแถวที่มี timestamp เท่ากัน
  • id อย่างเดียว: ปลอดภัยถ้า id เป็น monotonic แต่บางครั้งอาจไม่ตรงกับการเรียงที่ต้องการของผลิตภัณฑ์
  • created_at + id: มักเป็นการผสมที่ดีที่สุด (timestamp สำหรับการเรียงที่เข้าใจได้, id เป็นตัวเบรก)
  • updated_at เป็นการเรียงหลัก: เสี่ยงสำหรับการเลื่อนไม่รู้จบเพราะการแก้ไขอาจย้ายรายการข้ามหน้า

ถ้าคุณให้ตัวเลือกการเรียงหลายแบบ ให้ถือว่าแต่ละโหมดเป็นรายการต่างหากพร้อมกฎเคอร์เซอร์ของตัวเอง เคอร์เซอร์มีความหมายเฉพาะสำหรับการเรียงลำดับเดียวเท่านั้น

ทีละขั้นตอน: รูปร่าง API เคอร์เซอร์ที่ชัดเจน

คุณสามารถเก็บผิวสัมผัส API ให้เล็ก: อินพุตสองอย่าง ผลลัพธ์สองอย่าง

1) คำขอ: limit + cursor

ส่ง limit (จำนวนรายการที่ต้องการ) และ cursor ทางเลือก (ตำแหน่งที่จะต่อจาก) ถ้าไม่มีเคอร์เซอร์ เซิร์ฟเวอร์คืนหน้าต้น

Example request:

GET /api/messages?limit=30&cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wMS0xNlQxMDowMDowMFoiLCJpZCI6Ijk4NzYifQ==

2) การตอบกลับ: items + next_cursor

คืนรายการและ next_cursor ถ้าไม่มีหน้าถัดไป ให้คืน next_cursor: null ไคลเอนต์ควรปฏิบัติต่อเคอร์เซอร์เป็นโทเค็น ไม่ใช่สิ่งที่จะแก้ไข

{
  "items": [ {"id":"9876","created_at":"2026-01-16T10:00:00Z","subject":"..."} ],
  "next_cursor": "...",
  "has_more": true
}

ตรรกะฝั่งเซิร์ฟเวอร์โดยสรุป: เรียงลำดับในกฎที่เสถียร กรองโดยใช้เคอร์เซอร์ แล้วใช้ limit

ถ้าคุณเรียงใหม่สุดก่อนด้วย (created_at DESC, id DESC) ให้ถอดรหัสเคอร์เซอร์เป็น (created_at, id) แล้วดึงแถวที่มี (created_at, id) น้อยกว่าแบบเข้มงวดกรณีคู่ (strictly less than) ใช้การเรียงแบบเดียวกัน แล้วเอา limit แถว

3) การเข้ารหัสเคอร์เซอร์: แบบทึบปลอดภัยกว่า

คุณสามารถเข้ารหัสเคอร์เซอร์เป็น base64 JSON blob (ง่าย) หรือเป็นโทเค็นที่ลงลายเซ็น/เข้ารหัส (งานมากกว่า) แบบทึบปลอดภัยกว่าเพราะให้คุณเปลี่ยนภายในภายหลังโดยไม่ทำให้ไคลเอนต์เสีย

นอกจากนี้ตั้งค่า default ที่สมเหตุสมผล: ค่า default บนมือถือ (มัก 20-30), default เว็บ (มัก 50), และ max บนเซิร์ฟเวอร์ห้ามให้ไคลเอนต์ขอ 10,000 แถวเพียงเพราะบั๊ก

การจัดการการแทรก การลบ และการแก้ไข

เพิ่มการย้อนกลับสำหรับการรีแฟคเตอร์
ใช้ snapshots และ rollback เมื่อการปรับเปลี่ยน pagination ทำให้การเลื่อนพังในการทดสอบ
เปิดใช้งาน Snapshots

ฟีดเสถียรส่วนใหญ่เกี่ยวกับคำสัญญาหนึ่งข้อ: เมื่อผู้ใช้เริ่มเลื่อนไปข้างหน้า รายการที่พวกเขายังไม่ได้เห็นไม่ควรกระโดดเพราะผู้อื่นสร้าง ลบ หรือแก้ไขเรคคอร์ด

การแทรกเป็นเรื่องง่ายที่สุด รายการใหม่ควรแสดงเมื่อรีเฟรช ไม่ใช่อยู่กลางหน้าที่โหลดแล้ว หากคุณเรียงตาม created_at DESC, id DESC รายการใหม่จะอยู่ก่อนหน้าเพจแรก เคอร์เซอร์ที่มีอยู่จะชี้ไปที่รายการเก่าต่อไป

การลบไม่ควรทำให้รายการสับเปลี่ยน ถ้ารายการถูกลบ มันเพียงไม่ถูกส่งกลับเมื่อคุณถึงมัน ถ้าคุณต้องการรักษาขนาดหน้าคงที่ ให้เรียกต่อจนกว่าจะเก็บ limit รายการที่มองเห็นได้

การแก้ไขคือจุดที่ทีมมักทำให้บั๊กกลับมา คำถามสำคัญคือ: การแก้ไขจะเปลี่ยนตำแหน่งการเรียงไหม?

เลือกพฤติกรรมแบบสแนปชอตหรือแบบสด

พฤติกรรมแบบสแนปชอตมักดีที่สุดสำหรับรายการเลื่อน: ใช้คีย์ที่ไม่เปลี่ยนแปลงอย่าง created_at การแก้ไขอาจเปลี่ยนเนื้อหา แต่ไอเท็มจะไม่กระโดดไปตำแหน่งใหม่

พฤติกรรมแบบสดจะเรียงตามอย่าง edited_at ซึ่งอาจทำให้กระโดด (รายการเก่าถูกแก้ไขและย้ายขึ้นมา) ถ้าเลือกแบบนี้ ให้ถือว่ารายการเปลี่ยนตลอดเวลาและออกแบบ UX ให้รองรับการรีเฟรช

เมื่อรายการเคอร์เซอร์ไม่อยู่แล้ว

อย่าให้เคอร์เซอร์พึ่งพาการ “หาบรรทัดนี้แถวเดียว” ให้เข้ารหัสตำแหน่งแทน เช่น {created_at, id} ของรายการสุดท้ายที่คืนมา แล้วคำขอถัดไปจะอ้างจากค่ามากกว่าการมีแถวจริง:

  • สำหรับการเรียงลดลง: WHERE (created_at, id) < (:created_at, :id)
  • เสมอใส่ตัวเบรก (id) เพื่อหลีกเลี่ยงการซ้ำ
  • ถ้ารายการสุดท้ายถูกลบ ค่ายังคงใช้ได้
  • ถ้ารายการสุดท้ายถูกแก้ไข มันยังคงใช้ได้ตราบใดที่คีย์เรียงเป็น immutable

การย้อนกลับ รีเฟรช และการกระโดดไปรอบๆ

การเลื่อนไปข้างหน้าเป็นส่วนที่ง่าย ส่วน UX ที่ยากกว่าได้แก่การย้อนกลับ รีเฟรช และการเข้าถึงแบบสุ่ม

สำหรับการย้อนกลับ สองแนวทางที่ได้ผล:

  • คืนทั้งสองทิศทาง (next_cursor สำหรับรายการเก่า และ prev_cursor สำหรับรายการใหม่) พร้อมการเรียงบนหน้าจอแบบเดียวกัน
  • เก็บเคอร์เซอร์เดียว แต่ขอการเรียงลำดับกลับเมื่อผู้ใช้เลื่อนขึ้นด้านบน

การกระโดดแบบสุ่มยากกว่าเพราะ “หน้า 20” ไม่มีความหมายคงที่เมื่อรายการเปลี่ยน หากคุณต้องกระโดดจริงๆ ให้กระโดดไปที่แองเคอร์ เช่น “รอบๆ timestamp นี้” หรือ “เริ่มจาก message id นี้” ไม่ใช่ดัชนีหน้า

บนมือถือ การแคชมีความสำคัญ เก็บเคอร์เซอร์แยกตามสถานะรายการ (query + filters + sort) และถือแต่ละแท็บ/วิวเป็นรายการของตัวเอง นั่นจะป้องกันพฤติกรรม “สลับแท็บแล้วทุกอย่างเละ”

ความผิดพลาดทั่วไปที่สร้างบั๊กลึกลับ

ปัญหาส่วนใหญ่ของการแบ่งหน้าแบบเคอร์เซอร์ไม่ใช่เรื่องฐานข้อมูล แต่เป็นความไม่สอดคล้องกันเล็กๆ น้อยๆ ระหว่างคำขอ ซึ่งจะปรากฏภายใต้ทราฟฟิกจริง

ผู้ร้ายหลัก:

  • ใช้เคอร์เซอร์ที่ไม่ไม่ซ้ำ (เช่น created_at อย่างเดียว) ทำให้กรณีเสมอเกิด duplicate หรือ missing
  • คืน next_cursor ที่ไม่ตรงกับรายการสุดท้ายที่คืนจริง
  • เปลี่ยนฟิลเตอร์หรือการเรียงลำดับระหว่างคำขอหน้า
  • ผสม offset และ cursor ใน endpoint เดียวกัน

ถ้าคุณสร้างแอปบนแพลตฟอร์มอย่าง Koder.ai edge cases เหล่านี้จะโผล่เร็วเพราะไคลเอนต์เว็บและมือถือมักแชร์ endpoint เดียว การมีสัญญาเคอร์เซอร์ชัดเจนและกฎการเรียงลำดับ deterministic ช่วยให้ทั้งสองไคลเอนต์สอดคล้องกัน

เช็คลิสต์ด่วนก่อนปล่อยของ

ออกแบบการรีเฟรชอย่างถูกต้อง
ออกแบบ flow การรีเฟรชที่ชัดเจนเพื่อแสดงรายการใหม่โดยไม่ทำให้หน้าที่เลื่อนแล้วสับสน
ลองเลย

ก่อนเรียกว่าการแบ่งหน้าพร้อม ให้ตรวจพฤติกรรมภายใต้การแทรก การลบ และการลองใหม่

  • การเรียงชัดเจน กำหนดได้ และมีตัวเบรก
  • ทุกคำขอทำซ้ำฟิลเตอร์และฟิลด์การเรียงเหมือนกัน
  • next_cursor มาจากแถวสุดท้ายที่คืนจริง
  • limit มี max ปลอดภัยและมี default ระบุไว้
  • กำหนดพฤติกรรมการรีเฟรช (รายการใหม่จะปรากฏอย่างไร)

สำหรับการรีเฟรช เลือกกฎเดียวที่ชัด: ผู้ใช้ดึงเพื่อรีเฟรชเพื่อดึงรายการใหม่ที่ด้านบน หรือคุณตรวจเป็นระยะว่า “มีอะไรใหม่กว่าแถวแรกของฉันไหม?” แล้วแสดงปุ่ม “รายการใหม่” ความสม่ำเสมอคือสิ่งที่ทำให้รายการรู้สึกเสถียรแทนจะเหมือนมีผี

ตัวอย่างสมจริง: กล่องจดหมายที่คงที่ข้ามอุปกรณ์

นึกถึงกล่องจดหมายสนับสนุนที่เอเจนต์ใช้บนเว็บ ขณะที่ผู้จัดการเช็กบนมือถือ รายการเรียงจากใหม่สุดก่อน ผู้ใช้คาดหวังอย่างเดียว: เมื่อพวกเขาเลื่อนไปข้างหน้า รายการไม่ควรกระโดด ซ้ำ หรือหายไป

ด้วยการแบ่งหน้าแบบออฟเซ็ต เอเจนต์โหลดหน้า 1 (รายการ 1-20) แล้วเลื่อนไปหน้า 2 (offset=20) ขณะที่เขาอ่าน ขณะเดียวกันมีข้อความใหม่สองข้อความมาถึงด้านบน ตอนนี้ offset=20 ชี้ไปยังตำแหน่งต่างจากเดิม ผู้ใช้จะเห็นรายการซ้ำหรือพลาดข้อความ

ด้วยการแบ่งหน้าแบบเคอร์เซอร์ แอปจะขอ “20 รายการถัดไปหลังเคอร์เซอร์นี้” โดยที่เคอร์เซอร์มาจากรายการสุดท้ายที่ผู้ใช้เห็นจริง (โดยทั่วไปเป็น (created_at, id)) ข้อความใหม่สามารถมาถึงได้ทั้งวัน แต่หน้าถัดไปยังเริ่มหลังข้อความสุดท้ายที่ผู้ใช้เห็น

วิธีทดสอบง่ายๆ ก่อนปล่อย:

  • เริ่มดึงหน้าขณะที่สคริปต์แทรกข้อความใหม่ทุกวินาที
  • ลบข้อความบางส่วนระหว่างการเลื่อน
  • แก้ไขข้อความ (โดยไม่เปลี่ยนฟิลด์การเรียง)
  • ยืนยันว่าไม่เคยได้รายการซ้ำ ช่องว่าง หรือเรียงผิด
  • ยืนยันว่าแอปมือถือและเว็บแสดงขอบเขตหน้าที่สอดคล้องกัน

ถ้าคุณกำลังทำโปรโตไทป์เร็วๆ Koder.ai ช่วย scaffold endpoint และ flow ของไคลเอนต์จาก prompt แชท แล้ววนปรับโดยใช้ Planning Mode พร้อมสแนปชอตและการ rollback เมื่อการเปลี่ยน pagination ทำให้การทดสอบพัง

คำถามที่พบบ่อย

ทำไมฉันถึงเจอรายการซ้ำหรือหายไปเมื่อตั้งค่า pagination แบบ offset?

การแบ่งหน้าแบบออฟเซ็ตชี้ไปที่ “ข้าม N แถว” ดังนั้นเมื่อแถวใหม่ถูกแทรกหรือแถวเก่าถูกลบ จำนวนแถวจะเลื่อนไป ค่า offset เดิมอาจชี้ไปยังรายการคนละรายการกับที่เคยชี้ก่อนหน้า ทำให้เกิดรายการซ้ำหรือช่องว่างขณะผู้ใช้กำลังเลื่อน

การแบ่งหน้าแบบเคอร์เซอร์ช่วยป้องกันปัญหา “รายการเปลี่ยนไปขณะใช้งาน” อย่างไร?

การแบ่งหน้าแบบเคอร์เซอร์ใช้ที่คั่นตำแหน่งที่หมายถึง “ตำแหน่งหลังรายการสุดท้ายที่ฉันเห็น” คำขอต่อไปจะเริ่มจากตำแหน่งนั้นในลำดับที่กำหนดไว้ ทำให้การแทรกแถวที่ด้านบนและการลบแถวในกลางไม่ทำให้ขอบเขตหน้าของคุณเปลี่ยนเหมือนกับ offset

ฉันควรใช้เป็นเคอร์เซอร์อะไร: created_at, id, หรือทั้งสอง?

ใช้การเรียงลำดับที่กำหนดได้และมีตัวเบรกสำหรับกรณีเสมอกัน โดยทั่วไปมักใช้ (created_at, id) พร้อมทิศทางเดียวกัน created_at ให้การจัดเรียงที่เข้าใจได้สำหรับผู้ใช้ ส่วน id ทำให้แต่ละตำแหน่งไม่ซ้ำกัน จึงไม่เกิดการซ้ำหรือข้ามเมื่อ timestamp ตรงกัน

ฉันสามารถแบ่งหน้าตาม updated_at สำหรับฟีดแบบสดได้ไหม?

การเรียงตาม updated_at อาจทำให้รายการกระโดดข้ามหน้ากันเมื่อมีการแก้ไข ซึ่งจะทำลายความคาดหวังว่า "เลื่อนต่อไปแล้วไม่เปลี่ยนตำแหน่ง" หากคุณต้องการมุมมองแบบสดที่แสดงรายการที่อัปเดตล่าสุด ให้ออกแบบ UI ให้รีเฟรชและยอมรับการเรียงใหม่ แทนที่จะสัญญาการเลื่อนแบบคงที่

API ควรคืนค่าอะไรสำหรับการแบ่งหน้าแบบเคอร์เซอร์?

คืนค่าโทเค็นทึบในฟิลด์ next_cursor แล้วให้ไคลเอนต์ส่งกลับโดยไม่แก้ไข วิธีง่ายๆ คือเข้ารหัส (created_at, id) ของรายการสุดท้ายเป็น base64 JSON blob แต่สิ่งสำคัญคือปฏิบัติต่อมันเป็นค่า opaque เพื่อให้คุณเปลี่ยนภายในในอนาคตได้โดยไม่ทำให้ไคลเอนต์พัง

จะเกิดอะไรขึ้นถ้ารายการที่เคอร์เซอร์อ้างถึงถูกลบ?

ให้สร้างคำขอถัดไปจากค่าที่เก็บไว้ในเคอร์เซอร์ ไม่ใช่จากการหาแถวเฉพาะ หากรายการสุดท้ายถูกลบ (created_at, id) ที่เก็บไว้ยังคงกำหนดตำแหน่งได้ ดังนั้นคุณสามารถดำเนินการต่อด้วยตัวกรองแบบ “strictly less than” (หรือ “greater than”) ตามทิศทางเรียงลำดับได้อย่างปลอดภัย

ฉันจะหลีกเลี่ยงการแสดงซ้ำเมื่อต้องดึงหน้าถัดไปได้อย่างไร?

ใช้การเปรียบเทียบแบบเข้มงวดและตัวเบรกที่ไม่ซ้ำกัน และเสมอนำเคอร์เซอร์จากรายการสุดท้ายที่คุณคืนจริงๆ บั๊กซ้ำส่วนใหญ่เกิดจากการใช้ <= แทน <, ละเลยตัวเบรก, หรือสร้าง next_cursor จากแถวที่ผิดพลาด

การรีเฟรชควรทำงานอย่างไรกับการแบ่งหน้าแบบเคอร์เซอร์?

เลือกกฎที่ชัดเจน: รีเฟรชดึงรายการใหม่ที่ด้านบน ขณะที่การเลื่อนไปข้างหน้าต่อเนื่องไปยังรายการเก่าจากเคอร์เซอร์ที่มีอยู่ อย่านำ semantics ของการรีเฟรชมาปะปนกับ flow ของเคอร์เซอร์เดียวกัน มิฉะนั้นผู้ใช้จะเห็นการเรียงใหม่และคิดว่ารายการไม่น่าเชื่อถือ

ฉันสามารถใช้เคอร์เซอร์เดิมอีกครั้งถ้าผู้ใช้เปลี่ยนฟิลเตอร์หรือการเรียงลำดับได้ไหม?

เคอร์เซอร์ใช้ได้เฉพาะกับการเรียงลำดับและชุดตัวกรองเดียวกันเท่านั้น ถ้าไคลเอนต์เปลี่ยนโหมดการเรียง คำค้นหา หรือฟิลเตอร์ ต้องเริ่ม session การแบ่งหน้าใหม่โดยไม่ใช้เคอร์เซอร์เดิม และเก็บเคอร์เซอร์แยกตามสถานะรายการ

ฉันจะรองรับการกระโดดไปหน้าที่ต้องการหรือการเข้าถึงแบบสุ่มด้วยเคอร์เซอร์ได้อย่างไร?

การแบ่งหน้าแบบเคอร์เซอร์เหมาะกับการเรียกดูต่อเนื่อง แต่ไม่เหมาะกับการกระโดดไปยัง “หน้า 20” แบบคงที่เพราะชุดข้อมูลเปลี่ยนได้ หากต้องการกระโดด ให้กระโดดไปยังแองเคอร์เช่น “รอบๆ timestamp นี้” หรือ “เริ่มจาก id นี้” แล้วค่อยแบ่งหน้าต่อด้วยเคอร์เซอร์จากจุดนั้น

สารบัญ
ปัญหา: หน้ารายการที่เปลี่ยนไปขณะใช้งานการแบ่งหน้าแบบออฟเซ็ตแบบย่อทำไม offset ถึงพังเมื่อมีการแทรกหรือลบแถวรายการเสถียรหมายถึงอะไรสำหรับเว็บและมือถือการแบ่งหน้าแบบเคอร์เซอร์: แนวคิดง่ายๆการเลือกเคอร์เซอร์และการเรียงลำดับทีละขั้นตอน: รูปร่าง API เคอร์เซอร์ที่ชัดเจนการจัดการการแทรก การลบ และการแก้ไขการย้อนกลับ รีเฟรช และการกระโดดไปรอบๆความผิดพลาดทั่วไปที่สร้างบั๊กลึกลับเช็คลิสต์ด่วนก่อนปล่อยของตัวอย่างสมจริง: กล่องจดหมายที่คงที่ข้ามอุปกรณ์คำถามที่พบบ่อย
แชร์
Koder.ai
Build your own app with Koder today!

The best way to understand the power of Koder is to see it for yourself.

Start FreeBook a Demo