เรียนรู้วิธีสร้างรายการแดชบอร์ดที่เร็วแม้มี 100k แถว โดยใช้การแบ่งหน้า การจำลองการแสดงผล การกรองอย่างชาญฉลาด และการปรับคำสั่งฐานข้อมูล เพื่อให้เครื่องมือภายในยังคงลื่นไหล

หน้ารายการมักจะใช้งานได้ลื่นจนกว่าจะไม่เป็นอย่างนั้น ผู้ใช้เริ่มสังเกตการหยุดชั่วคราวเล็ก ๆ ที่สะสม: การเลื่อนกระตุก หน้าเด้งติดเป็นช่วง ๆ หลังการอัปเดต ตัวกรองตอบสนองเป็นวินาที และมีสปินเนอร์หลังการคลิกบางครั้ง บางทีแท็บเบราว์เซอร์ดูเหมือนค้างเพราะ UI thread ทำงานหนัก
จุดเปลี่ยนที่พบบ่อยคือประมาณ 100k แถว เพราะมันกดทุกส่วนของระบบพร้อมกัน ชุดข้อมูลยังไม่ใหญ่เกินไปสำหรับฐานข้อมูล แต่ก็เพียงพอที่จะทำให้ความไม่ประสิทธิภาพเล็ก ๆ สะดุดตาบนเบราว์เซอร์และเครือข่าย หากพยายามโชว์ทุกอย่างพร้อมกัน หน้าจอเรียบง่ายจะกลายเป็นสายการประมวลผลหนัก
เป้าหมายไม่ใช่การเรนเดอร์ทุกแถว เป้าหมายคือช่วยให้ใครสักคนพบสิ่งที่ต้องการอย่างรวดเร็ว: 50 แถวที่ใช่ หน้าถัดไป หรือชิ้นข้อมูลแคบ ๆ ตามตัวกรอง
จะเป็นประโยชน์ถ้าจะแบ่งงานออกเป็นสี่ส่วน:
ถ้าส่วนใดส่วนหนึ่งแพง หน้าทั้งหมดจะรู้สึกช้า กล่องค้นหาง่าย ๆ สามารถกระตุ้นคำขอที่ไปเรียง 100k แถว คืนผลเป็นพันรายการ แล้วบังคับให้เบราว์เซอร์เรนเดอร์ทั้งหมด นั่นแหละสาเหตุที่การพิมพ์เกิดอาการหน่วง
เมื่อทีมสร้างเครื่องมือภายในอย่างรวดเร็ว (รวมถึงด้วยแพลตฟอร์มแบบโค้ดจากแชทเช่น Koder.ai) หน้ารายการมักเป็นที่แรกที่การเติบโตของข้อมูลจริงเปิดเผยช่องว่างระหว่าง “ใช้งานกับชุดข้อมูลเดโมได้” กับ “รู้สึกทันทีทุกวัน”
ก่อนจะปรับจูน ให้ตัดสินใจว่า "เร็ว" สำหรับหน้านี้หมายถึงอะไร หลายทีมไล่ตาม throughput (โหลดทุกอย่าง) ในขณะที่ผู้ใช้ส่วนใหญ่ต้องการ low latency (เห็นการอัปเดตเร็ว) รายการสามารถรู้สึกทันทีแม้ว่าจะไม่โหลดแถวทั้ง 100k ก็ต่อเมื่อมันตอบสนองเร็วต่อการเลื่อน การเรียง และตัวกรอง
เป้าหมายที่ใช้งานได้จริงคือเวลาเพื่อแถวแรก ไม่ใช่เวลาในการโหลดทั้งหมด ผู้ใช้เชื่อถือเพจเมื่อเห็นแถวแรก 20–50 แถวอย่างรวดเร็วและการโต้ตอบยังคงลื่น
เลือกชุดตัวเลขเล็ก ๆ ที่ติดตามได้ทุกครั้งที่คุณเปลี่ยนอะไรบางอย่าง:
COUNT(*) และ SELECT กว้าง ๆ)ตัวชี้เหล่านี้ตรงกับอาการที่พบบ่อย ถ้า CPU ของเบราว์เซอร์พุ่งเมื่อเลื่อน แสดงว่าฝั่ง frontend ทำงานหนักเกินต่อแถว หากสปินเนอร์รอก่อนแต่การเลื่อนหลังจากนั้นเป็นปกติ ปัญหามักอยู่ที่ backend หรือเครือข่าย ถ้าคำขอเร็วแต่หน้าเด้งค้าง มักจะเป็นการเรนเดอร์หรือการประมวลผลบนไคลเอนต์หนักเกินไป
ลองทดลองง่าย ๆ: เก็บ UI ไว้เหมือนเดิม แต่จำกัด backend ชั่วคราวให้คืนแค่ 20 แถวด้วยตัวกรองเดียวกัน หากเร็วขึ้น คอขวดคือขนาดการส่งหรือเวลา query หากยังช้า ให้ดูที่การเรนเดอร์ การจัดรูปแบบ และคอมโพเนนต์ต่อแถว
ตัวอย่าง: หน้าคำสั่ง (Orders) ภายในช้าขณะพิมพ์ค้นหา ถ้า API คืน 5,000 แถวแล้วเบราว์เซอร์กรองบนทุก keypress การพิมพ์จะหน่วง หาก API ใช้ 2 วินาทีเพราะ COUNT บนตัวกรองที่ไม่มีดัชนี คุณจะรอก่อนที่แถวใด ๆ จะเปลี่ยน ทั้งสองกรณีผู้ใช้ร้องเรียนเหมือนกัน แต่การแก้ต่างกัน
เบราว์เซอร์มักเป็นคอขวดแรก หน้ารายการสามารถรู้สึกช้าได้แม้ API จะเร็ว เพราะหน้าพยายามวาดมากเกินไป กฎข้อแรกง่าย ๆ: อย่าเรนเดอร์หลายพันแถวใน DOM พร้อมกัน
แม้ก่อนจะเพิ่ม virtualization เต็มรูปแบบ ให้ทำให้แต่ละแถวเบาไว้ แถวที่มี wrapper ซ้อนกัน ไอคอน tooltip และสไตล์เชิงเงื่อนไขซับซ้อนในแต่ละเซลล์ จะมีค่าใช้จ่ายทุกครั้งที่เลื่อนและอัปเดต ให้ใช้ข้อความธรรมดา ป้ายเล็ก ๆ สองสามชิ้น และองค์ประกอบเชิงโต้ตอบไม่เกินหนึ่งหรือสองชิ้นต่อแถว
ความสูงแถวคงที่ช่วยได้มากกว่าที่คิด เมื่อทุกแถวมีความสูงเท่ากัน เบราว์เซอร์คาดเดาเลย์เอาต์ได้และการเลื่อนจะลื่น ความสูงแถวไม่คงที่จะทำให้ต้องวัดซ้ำและ reflow หากต้องการรายละเอียดเพิ่มเติม ให้พิจารณาแผงด้านข้างหรือพื้นที่ขยายเดียว ไม่ใช่แถวหลายบรรทัดทั้งหมด
การจัดรูปแบบก็กินทรัพยากร เช่น วันที่ สกุลเงิน และการจัดการสตริงหนัก ๆ จะสะสมเมื่อทำซ้ำหลายเซลล์
ถ้าค่าหนึ่งมองไม่เห็น อย่าคำนวณมันตอนนี้ แคชผลการจัดรูปแบบที่แพงและคำนวณเมื่อจำเป็น เช่น เมื่อแถวปรากฏหรือเมื่อผู้ใช้เปิดแถว
รายการปรับปรุงด่วนที่มักได้ผลชัดเจน:
ตัวอย่าง: ตาราง Invoices ภายในที่จัดรูปแบบคอลัมน์สกุลเงินและวันที่ 12 คอลัมน์จะกระตุกขณะเลื่อน การแคชค่าที่จัดรูปแบบต่อ invoice และหน่วงงานสำหรับแถวที่อยู่นอกหน้าจอสามารถทำให้รู้สึกทันที แม้ก่อนจะทำงานเชิงลึกฝั่ง backend
Virtualization หมายถึงตารางจะวาดเฉพาะแถวที่มองเห็นได้จริง (บวก buffer เล็กน้อยด้านบนและล่าง) เมื่อเลื่อนมันจะนำ DOM element เดิมมาใช้ซ้ำแล้วสลับข้อมูลภายใน นั่นช่วยให้เบราว์เซอร์ไม่พยายามวาดคอมโพเนนต์แถวจำนวนหมื่น ๆ พร้อมกัน
Virtualization เหมาะกับรายการยาว ตารางกว้าง หรือแถวหนัก (avatar ชิปสถานะ เมนูการกระทำ tooltip) และมีประโยชน์เมื่อผู้ใช้เลื่อนเยอะและคาดหวังว่าจะเห็นวิวต่อเนื่องลื่น ๆ แทนการกระโดดเป็นหน้าทีละหน้า
มันไม่ใช่เวทย์มนตร์ มีบางอย่างที่มักสร้างความประหลาดใจ:
วิธีที่เรียบง่ายที่สุดคือวิธีน่าเบื่อ: ความสูงแถวคงที่ คอลัมน์คาดเดาได้ และไม่ใส่วิดเจ็ตเชิงโต้ตอบมากเกินไปภายในแต่ละแถว
คุณสามารถผสมทั้งสอง: ใช้การแบ่งหน้า (หรือโหลดแบบ cursor) เพื่อจำกัดสิ่งที่ดึงจากเซิร์ฟเวอร์ และใช้ virtualization เพื่อรักษาการเรนเดอร์ให้ถูกภายในสไลซ์ที่ดึงแล้ว
รูปแบบปฏิบัติคือดึงขนาดเพจปกติ (มัก 100–500 แถว), ทำ virtualization ภายในเพจนั้น, และให้ตัวควบคุมชัดเจนสำหรับย้ายระหว่างหน้า หากใช้ infinite scroll ให้แสดงตัวบ่งชี้ "Loaded X of Y" เพื่อให้ผู้ใช้เข้าใจว่าพวกเขายังไม่เห็นทั้งหมด
ถ้าต้องการหน้ารายการที่ยังใช้งานได้เมื่อข้อมูลเพิ่ม การแบ่งหน้ามักเป็นค่าเริ่มต้นที่ปลอดภัย มันคาดเดาได้ ทำงานได้ดีกับเวิร์กโฟลว์แอดมิน (รีวิว แก้ไข อนุมัติ) และรองรับความต้องการทั่วไปเช่นการส่งออก "หน้า 3 พร้อมตัวกรองเหล่านี้" โดยไม่มีความประหลาดใจ หลายทีมกลับมาใช้การแบ่งหน้าหลังจากลองวิธีการเลื่อนอื่น ๆ
Infinite scroll ให้ความรู้สึกดีสำหรับการเรียกดูแบบไม่ตั้งใจ แต่มีต้นทุนแฝง ผู้ใช้จะเสียความรู้สึกว่าตัวเองอยู่ตรงไหน ปุ่มย้อนกลับมักไม่กลับไปตำแหน่งเดิม และเซสชันยาวอาจสะสมหน่วยความจำเมื่อโหลดแถวมากขึ้น ทางสายกลางคือปุ่ม Load more ที่ยังใช้เพจ ทำให้ผู้ใช้คงทิศทาง
Offset pagination เป็นแบบคลาสสิก page=10&size=50 มันง่าย แต่เมื่อโตขึ้นอาจช้าบนตารางใหญ่เพราะฐานข้อมูลอาจต้องข้ามแถวจำนวนมากไปยังหน้าหลัง ๆ และมันทำให้แถวเปลี่ยนหน้าถ้ามีแถวใหม่เข้ามา
Keyset pagination (เรียกอีกอย่างว่า cursor pagination) ขอ "50 แถวถัดไปหลังจากรายการที่เห็นล่าสุด" โดยใช้ id หรือ created_at มันมักจะเร็วเพราะไม่ต้อง count และ skip มาก
กฎปฏิบัติ:
ผู้ใช้มักชอบเห็นจำนวนรวม แต่การทำ "count ทุกแถวที่ตรงเงื่อนไข" อาจแพงกับตัวกรองหนัก ๆ ตัวเลือกได้แก่การแคชจำนวนสำหรับตัวกรองยอดนิยม, อัปเดตจำนวนในพื้นหลังหลังหน้าโหลด, หรือแสดงจำนวนโดยประมาณ (เช่น "10,000+")
ตัวอย่าง: หน้าสั่งซื้อภายในสามารถแสดงผลทันทีด้วย keyset pagination แล้วเติมจำนวนรวมที่แม่นยำเมื่อผู้ใช้หยุดเปลี่ยนตัวกรองสักครู่
ถ้าคุณสร้างด้วย Koder.ai ให้จัดการพฤติกรรมการแบ่งหน้าและการนับเป็นส่วนของสเปคหน้าตั้งแต่เริ่ม เพื่อที่ API และสถานะ UI ที่สร้างจะไม่ขัดกันทีหลัง
หน้ารายการส่วนใหญ่รู้สึกช้าเพราะเริ่มแบบเปิดกว้าง: โหลดทุกอย่าง แล้วค่อยให้ผู้ใช้แคบลง พลิกมุมมองนั้น เริ่มด้วยค่าพื้นฐานที่สมเหตุสมผลซึ่งคืนผลชุดเล็กที่มีประโยชน์ (เช่น 7 วันที่ผ่านมา, ไอเท็มของฉัน, สถานะ: Open) และให้ตัวเลือก All time เป็นการเลือกชัดเจน
การค้นหาด้วยข้อความเป็นกับดักทั่วไปอีกอย่าง ถ้ารัน query ทุกครั้งที่พิมพ์ คุณจะสร้างคิวคำขอและ UI กระพริบ ให้ debounce ช่องค้นหาเพื่อเรียกเมื่อผู้ใช้หยุดพิมพ์เล็กน้อย และยกเลิกคำขอเก่าพอมีคำขอใหม่ กฎง่าย ๆ: ถ้าผู้ใช้ยังพิมพ์ อย่าทุบเซิร์ฟเวอร์
การกรองจะรู้สึกเร็วเมื่อชัดเจนด้วย แสดงชิปตัวกรองด้านบนตารางเพื่อให้ผู้ใช้เห็นว่าสิ่งใดกำลังถูกใช้และสามารถลบได้ด้วยคลิกเดียว ให้ป้ายชิปอ่านง่ายเป็นภาษามนุษย์ ไม่ใช่ชื่อฟิลด์ดิบ (เช่น Owner: Sam แทน owner_id=42) เมื่อคนบอกว่า "ผลหายไป" มักเป็นเพราะมีตัวกรองที่มองไม่เห็น
รูปแบบที่ช่วยให้รายการใหญ่ตอบสนองโดยไม่ซับซ้อน UI:
Saved views เป็นฮีโร่ที่เงียบ ๆ แทนที่จะสอนผู้ใช้ให้สร้างคอมโบตัวกรองครั้งเดียว ให้พวกเขามีพรีเซ็ตไม่กี่แบบที่ตรงงานจริง ทีม ops อาจสลับระหว่าง Failed payments วันนี้ กับ High-value customers ได้ด้วยคลิกเดียว ชุดงานเหล่านี้เข้าใจง่ายและรักษาความเร็วฝั่ง backend ได้ง่ายกว่าการกรอง ad-hoc
ถ้าคุณสร้างเครื่องมือภายในด้วยบิลเดอร์แบบขับเคลื่อนด้วยแชทอย่าง Koder.ai ให้ถือว่าตัวกรองเป็นส่วนหนึ่งของโฟลว์ผลิตภัณฑ์ ไม่ใช่ของเสริม เริ่มจากคำถามที่พบบ่อยที่สุด แล้วออกแบบมุมมองเริ่มต้นและ saved views รอบ ๆ คำถามเหล่านั้น
หน้ารายการไม่ค่อยต้องการข้อมูลเท่าหน้ารายละเอียด ถ้า API คืนทุกอย่างเกี่ยวกับทุกอย่าง คุณจ่ายสองครั้ง: ฐานข้อมูลทำงานมากขึ้น และเบราว์เซอร์ได้รับและเรนเดอร์มากกว่าที่ใช้จริง Query shaping คือการขอเฉพาะสิ่งที่หน้าต้องการตอนนี้
เริ่มจากคืนเฉพาะคอลัมน์ที่จำเป็นในการเรนเดอร์แต่ละแถว สำหรับแดชบอร์ดส่วนใหญ่ นั่นคือ id, ป้ายสองสามอัน, สถานะ, เจ้าของ, และ timestamps ข้อความยาว JSON blobs และฟิลด์คำนวณสามารถรอจนกว่าผู้ใช้จะเปิดแถว
หลีกเลี่ยง join หนักสำหรับการแสดงผลครั้งแรก Join ใช้ได้เมื่อชนกับดัชนีและคืนผลเล็ก แต่จะแพงเมื่อคุณ join หลายตารางแล้วเรียงหรือกรองบนข้อมูลที่ join มา รูปแบบง่าย ๆ คือ: ดึงรายการจากตารางเดียวให้เร็ว แล้วโหลดรายละเอียดที่เกี่ยวข้องตามต้องการ (หรือโหลดเป็นกลุ่มสำหรับแถวที่มองเห็นเท่านั้น)
จำกัดตัวเลือกการเรียงและเรียงด้วยคอลัมน์ที่มีดัชนี "เรียงได้ทุกอย่าง" ฟังดูมีประโยชน์ แต่บ่อยครั้งบังคับให้ต้องเรียงช้าในชุดข้อมูลใหญ่ ให้เลือกไม่กี่ตัวเลือกที่คาดเดาได้ เช่น created_at, updated_at, หรือ status และตรวจสอบให้คอลัมน์เหล่านั้นมีดัชนี
ระวังการรวมผลทางฝั่งเซิร์ฟเวอร์ COUNT(*) บนชุดกรองใหญ่, DISTINCT บนคอลัมน์กว้าง, หรือการคำนวณหน้าทั้งหมดอาจกินเวลาตอบกลับมาก
แนวทางปฏิบัติ:
COUNT และ DISTINCT เป็นทางเลือก และแคชหรือประมาณเมื่อเป็นไปได้ถ้าคุณสร้างเครื่องมือภายในบน Koder.ai ให้กำหนด query รายการแบบเบาแยกจาก query รายละเอียดในขั้นตอนวางแผน เพื่อ UI จะยังคงลื่นเมื่อข้อมูลโต
ถ้าต้องการหน้ารายการที่ยังเร็วที่ 100k แถว ฐานข้อมูลต้องทำงานน้อยลงต่อคำขอ รายการช้าที่สุดมักไม่ใช่เพราะข้อมูลมากเกินไป แต่เป็นเพราะรูปแบบการเข้าถึงข้อมูลผิด
เริ่มจากดัชนีที่สอดคล้องกับการใช้งานจริงของผู้ใช้ หากรายการมักถูกกรองด้วย status และเรียงด้วย created_at คุณต้องการดัชนีที่รองรับทั้งสองในลำดับนั้น ไม่เช่นนั้นฐานข้อมูลอาจสแกนแถวมากกว่าที่คิดแล้วค่อยเรียง ซึ่งแพงเร็ว
การแก้ที่มักให้ผลใหญ่:
tenant_id, status, created_at)OFFSET ลึก ๆ OFFSET ทำให้ฐานข้อมูลต้องก้าวผ่านแถวจำนวนมากเพื่อข้ามตัวอย่างง่าย ๆ: ตาราง Orders ภายในที่แสดงชื่อผู้ใช้ สถานะ จำนวน และวันที่ อย่า join ทุกตารางที่เกี่ยวข้องและดึงบันทึกคำสั่งเต็มสำหรับมุมมองรายการ ให้คืนเฉพาะคอลัมน์ที่ใช้ในตาราง แล้วโหลดที่เหลือในคำขอแยกเมื่อลูกค้าเปิดดูคำสั่ง
ถ้าคุณสร้างด้วยแพลตฟอร์มอย่าง Koder.ai ให้ยึดแนวคิดนี้แม้ UI จะถูกสร้างจากแชท ตรวจสอบให้ endpoint ที่สร้างรองรับ cursor pagination และฟิลด์ที่เลือกได้ เพื่อให้งานฐานข้อมูลคาดเดาได้เมื่อโตขึ้น
ถ้าหน้ารายการรู้สึกช้าที่สุดตอนนี้ อย่าเริ่มด้วยการเขียนใหม่ทั้งหมด เริ่มจากล็อกลงว่าใช้งานปกติเป็นอย่างไร แล้วเพิ่มประสิทธิภาพเส้นทางนั้น
กำหนดมุมมองเริ่มต้น. เลือกตัวกรองเริ่มต้น การเรียง และคอลัมน์ที่มองเห็น หน้าจะช้าเมื่อพยายามโชว์ทุกอย่างเป็นค่าพื้นฐาน
เลือกสไตล์การแบ่งหน้าที่ตรงกับการใช้งาน. ถ้าผู้ใช้สแกนแค่หน้าแรกสองสามหน้า การแบ่งหน้าแบบคลาสสิกพอเพียง ถ้าผู้ใช้ไปลึกมาก (หน้า 200+) หรือคุณต้องการประสิทธิภาพคงที่ ให้ใช้ keyset pagination (อิงการเรียงเสถียรเช่น created_at บวก id)
เพิ่ม virtualization สำหรับส่วนของตาราง. แม้ backend จะเร็ว เบราว์เซอร์อาจล่มเมื่อเรนเดอร์แถวเยอะเกินไป
ทำให้การค้นหาและตัวกรองรู้สึกทันที. ใส่ debounce สำหรับการพิมพ์เพื่อไม่เรียกคำขอทุก keypress เก็บสถานะตัวกรองใน URL หรือ state กลางเดียวเพื่อให้รีเฟรช ปุ่มย้อนกลับ และการแชร์มุมมองทำงานได้อย่างเชื่อถือได้ แคชผลสำเร็จล่าสุดเพื่อให้ตารางไม่กระพริบเป็นค่าว่าง
วัดผล แล้วปรับคำสั่งและดัชนี. บันทึกเวลาเซิร์ฟเวอร์ เวลาในฐานข้อมูล ขนาด payload และเวลาเรนเดอร์ จากนั้นตัดคำสั่ง: เลือกเฉพาะคอลัมน์ที่แสดง ใช้ตัวกรองตั้งแต่ต้น และเพิ่มดัชนีที่สอดคล้องกับตัวกรองเริ่มต้น + การเรียง
ตัวอย่าง: แดชบอร์ดซัพพอร์ตภายในที่มี 100k ตั๋ว ตั้งเป็นค่าเริ่มต้นเป็น Open มอบหมายให้ทีมของฉัน เรียงตามใหม่สุด แสดงหกคอลัมน์ และดึงแค่ ticket id, subject, assignee, status, และ timestamps ด้วย keyset pagination และ virtualization คุณจะรักษาความคาดเดาได้ทั้งฝั่งฐานข้อมูลและ UI
ถ้าคุณสร้างเครื่องมือภายในใน Koder.ai แผนนี้จับคู่ได้ดีกับเวิร์กโฟลว์ iterate-and-check: ปรับมุมมอง ทดสอบการเลื่อนและการค้นหา แล้วจูนคำสั่งจนหน้าไม่ช้า
วิธีที่เร็วที่สุดทำให้หน้ารายการพังคือถือว่า 100k แถวเป็นหน้าข้อมูลปกติ กับดักที่พบบ่อยมีไม่กี่แบบ
ข้อใหญ่ข้อหนึ่งคือเรนเดอร์ทุกอย่างแล้วซ่อนด้วย CSS แม้มันจะดูเหมือนแค่ 50 แถวที่มองเห็น เบราว์เซอร์ยังจ่ายค่าการสร้าง 100k โหนด DOM วัดพวกมัน และ repaint ตอนเลื่อน ถ้าต้องการรายการยาว ควรเรนเดอร์เฉพาะที่ผู้ใช้เห็น (virtualization) และทำให้คอมโพเนนต์แถวเรียบง่าย
การค้นหาก็สามารถทำลายประสิทธิภาพเงียบ ๆ เมื่อตัวพิมพ์ทุกตัวทริกเกอร์การสแกนตารางเต็ม นี่เกิดขึ้นเมื่อตัวกรองไม่มีดัชนี เมื่อคุณค้นหาหลายคอลัมน์ หรือเมื่อรัน contains queries บนฟิลด์ข้อความใหญ่โดยไม่มีแผน กฎที่ดีคือตัวกรองแรกที่ผู้ใช้จะใช้ควรถูกออกแบบให้ถูกในฐานข้อมูล ไม่ใช่แค่สะดวกใน UI
ปัญหาทั่วไปอีกอย่างคือดึงระเบียนเต็มเมื่อหน้าแค่ต้องการสรุป แถวรายหนึ่งมักต้องการ 5–12 ฟิลด์ ไม่ใช่อ็อบเจ็กต์ทั้งหมดหรือคำอธิบายยาว ๆ การดึงข้อมูลเกินจำเป็นเพิ่มงานฐานข้อมูล เวลาเครือข่าย และการแยกวิเคราะห์ฝั่ง frontend
การส่งออกและการคำนวณจำนวนรวมสามารถทำให้ UI ค้างถ้าคำนวณบน main thread หรือรอคำขอหนักก่อนตอบ ให้เก็บ UI โต้ตอบได้: เริ่มการส่งออกในพื้นหลัง แสดงความคืบหน้า และหลีกเลี่ยงการคำนวณจำนวนซ้ำทุกครั้งที่ตัวกรองเปลี่ยน
สุดท้าย ตัวเลือกการเรียงที่มากเกินไปกลับตลก ถ้าผู้ใช้เรียงได้ทุกคอลัมน์ คุณจะต้องเรียงชุดข้อมูลใหญ่ในหน่วยความจำหรือบังคับให้ฐานข้อมูลใช้แผนช้า ให้จำกัดการเรียงในชุดคอลัมน์ที่มีดัชนี และตั้งค่าเริ่มต้นให้ตรงกับดัชนีจริง
เช็ครวดเร็วด้วยสัญชาตญาณ:
ถือว่าประสิทธิภาพหน้ารายการเป็นฟีเจ็ตผลิตภัณฑ์ ไม่ใช่การปรับแต่งครั้งเดียว หน้ารายการจะเร็วเมื่อมันรู้สึกเร็วขณะที่คนจริงเลื่อน กรอง และเรียงในข้อมูลจริง
ใช้เช็คลิสต์นี้เพื่อตรวจสอบว่าคุณแก้ถูกจุด:
การตรวจสอบง่าย: เปิดหน้ารายการ เลื่อน 10 วินาที แล้วใช้ตัวกรองที่ใช้บ่อย (เช่น Status: Open) ถ้า UI ค้าง ปัญหามักเป็นการเรนเดอร์ (เรนเดอร์ DOM เกิน) หรือการแปลงหนักฝั่งไคลเอนต์ (เรียง กลุ่ม จัดรูปแบบ) ที่เกิดขึ้นทุกอัปเดต
ขั้นตอนต่อไปตามลำดับเพื่อไม่เด้งระหว่างการแก้ไข:
ถ้าคุณสร้างกับ Koder.ai (koder.ai) ให้เริ่มที่ Planning Mode: กำหนดคอลัมน์รายการ ตัวฟิลเตอร์ และรูปแบบการตอบกลับก่อน แล้วทำซ้ำโดยใช้ snapshots และ rollback เมื่อการทดลองทำให้หน้าช้าลง
เปลี่ยนเป้าหมายจาก “โหลดทุกอย่าง” เป็น “แสดงแถวที่เป็นประโยชน์แรกให้เร็ว” ให้เพิ่มประสิทธิภาพที่เวลาเพื่อแถวแรกและการตอบสนองที่ลื่นไหลเมื่อกรอง เรียง และเลื่อน ถึงแม้จะไม่ได้โหลดข้อมูลทั้งชุดพร้อมกันก็ตาม
วัดเวลาเพื่อแถวแรกหลังการโหลดหรือเปลี่ยนตัวกรอง, เวลาที่ตัวกรอง/การเรียงใช้ผลใหม่, ขนาด payload ของการตอบกลับ, คำสั่งฐานข้อมูลช้า (โดยเฉพาะ SELECT กว้าง ๆ และ COUNT(*)), และการกระโดดของ main-thread ในเบราว์เซอร์ ตัวเลขเหล่านี้จะสื่อถึงสิ่งที่ผู้ใช้รับรู้ว่าเป็น “หน่วง” ได้ตรงจุด
จำกัด API ชั่วคราวให้คืนแค่ 20 แถวโดยใช้ตัวกรองและการเรียงแบบเดิม หากหน้ารวดเร็วขึ้น แสดงว่าคอขวดมาจากขนาดการตอบกลับหรือเวลา query ถ้ายังช้าอยู่ ปัญหามักอยู่ที่การเรนเดอร์ การจัดรูปแบบ หรืองานฝั่งไคลเอนต์ต่อแถว
อย่าเรนเดอร์หลายพันแถวใน DOM พร้อมกัน, ทำให้คอมโพเนนต์แถวเรียบง่าย และควรใช้ความสูงแถวคงที่ นอกจากนี้หลีกเลี่ยงการประมวลผลหนัก ๆ สำหรับแถวที่อยู่นอกหน้าจอ; คำนวณและแคชการจัดรูปแบบเมื่อแถวปรากฏหรือเมื่อผู้ใช้เปิดดู
Virtualization จะติดตั้งเฉพาะแถวที่มองเห็นได้ (และสำรองเล็กน้อย) แล้วนำ DOM element เดิมมาใช้ซ้ำเมื่อเลื่อน มันคุ้มเมื่อต้องเลื่อนเยอะหรือแถวหนัก แต่จะทำงานได้ดีที่สุดถ้าความสูงแถวสม่ำเสมอและเลย์เอาต์คาดเดาได้
การแบ่งหน้าเป็นค่าพื้นฐานที่ปลอดภัยสำหรับงานแอดมินและงานภายใน เพราะช่วยให้ผู้ใช้คงทิศทางและจำกัดงานฝั่งเซิร์ฟเวอร์ การเลื่อนไม่รู้จบเหมาะสำหรับการท่องแบบสบาย ๆ แต่จะทำให้การนำทางและการใช้หน่วยความจำแย่ลงถ้าไม่มีการจัดการสถานะที่ชัดเจน
การแบ่งหน้าแบบ offset ง่ายแต่จะช้าลงเมื่อไปลึกเพราะฐานข้อมูลต้องข้ามแถวจำนวนมาก page=10&size=50 แบบคลาสสิกเหมาะกับรายการเล็กหรือเมื่อจำเป็นต้องไปยังหน้าที่ระบุ คีย์เซ็ต (cursor) pagination มักเร็วกว่าเพราะไม่ต้องข้ามแถวจำนวนมาก แต่ไม่เหมาะกับการกระโดดไปหน้าที่ระบุเสมอไป
อย่าเรียกคำขอทุกครั้งที่พิมพ์ ใส่ debounce ให้ช่องค้นหา ยกเลิกคำขอที่ยังรันเมื่อมีคำขอใหม่ และตั้งค่าเริ่มต้นเป็นตัวกรองที่แคบ (เช่น รายการ 7 วันที่ผ่านมา หรือ My items) เพื่อให้คำขอแรกมีขนาดเล็กและมีประโยชน์
API ควรคืนเฉพาะฟิลด์ที่หน้ารายการต้องแสดง เช่น id, label, status, owner และ timestamps ข้อมูลยาว ๆ, JSON blobs และฟิลด์คำนวณสามารถโหลดเมื่อเปิดแถวเพื่อให้การแสดงผลครั้งแรกเบาและคาดเดาได้
ทำให้ตัวกรองและการเรียงสอดคล้องกับพฤติกรรมจริงของผู้ใช้ แล้วสร้างดัชนี (index) ที่รองรับรูปแบบนั้น เช่น ดัชนีผสมที่รวม tenant_id, status, created_at การนับแบบแม่นยำอาจช้า ให้เก็บแคช หรือนำเสนอเป็นค่าประมาณเมื่อไม่จำเป็นต้องแม่นยำทันที