top of page
ค้นหา
รูปภาพนักเขียนSathit Jittanupat

MongoDB, How much "Sorting" costs?



เมื่อวานนี้น้องในทีมพัฒนาแจ้งปัญหารายงานที่จัดทำให้ผู้ใช้รายหนึ่ง แสดงผลลัพธ์ไม่ถูกต้องมีข้อมูลขาดหายและเบิ้ลซ้ำ พร้อมทั้งระบุสาเหตุว่าเกิดจากคำสั่ง "sort=null" ซึ่งใช้เพื่ออ่านข้อมูลจากด้าต้าเบสโดยไม่ต้องเรียงลำดับ


เพื่อให้เข้าใจสภาวะความสับสนของทีมพัฒนา ปัจจุบันโปรแกรมมีดาต้าเบสอยู่นับร้อยตามจำนวนผู้ใช้ มีดาต้าเบสทั้งขนาดเล็ก กลาง ใหญ่ และธรรมชาติของการใช้งานแตกต่างกัน เปรียบเสมือนต้นไม้ในป่าที่มีขนาดแตกต่างกัน และที่ยากจะจินตนาการกว่านั้น คือ อัตราการเติบโตที่ต่างกัน กิจการสองแห่งเริ่มต้นพร้อมกัน เมื่อเวลาผ่านไป 3 ปี กิจการที่เปิดบิลเดือนไม่เกินพันใบ กับเดือนละหมื่นใบขนาดของดาต้าเบสย่อมแตกต่างกัน รายงานที่เคยออกแบบตอนเริ่มต้นเหมือน ๆ กัน และเทคนิค query ที่เคยใช้ได้กับดาต้าเบสที่มีข้อมูลหลักหมื่น อาจใช้ไม่ได้กับดาต้าเบสที่มีข้อมูลหลักแสน


นานมาแล้วผมเคยแนะนำเทคนิคเพื่อช่วยให้สามารถ query ข้อมูลได้เร็วขึ้น และการใช้ "sort=null" ก็เป็นหนึ่งในเทคนิคนั้น ปกติแล้วงานของนักพัฒนาที่ต้องจัดรายงานตามที่ผู้ร้องขอ ทำให้ต้องใช้ query แตกต่างกันไป ขณะเดียวกันก็ไม่สามารถแน่ใจได้ว่า ดาต้าเบสของผู้ใช้รายนั้น ๆ จะมี index อะไรบ้าง เพราะขึ้นอยู่กับผู้ดูแลดาต้าเบสเห็นสมควรว่าจะทำ Performance Tuning ตามสถิติการใช้งานจริงว่า ช่วงนี้มี query จากรายงานไหนที่ใช้ประจำควรเพิ่ม index ให้ บางครั้งรายงานเดิมอาจเลิกใช้ไปแล้ว ทำให้ index ที่มีอยู่เดิมไม่จำเป็นอีกต่อไป เหล่านี้เป็นงานที่ไม่มีสิ้นสุดของผุ้ดูแลดาต้าเบส


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


กลไกการทำงานตามคำสั่ง query ของ MongoDB เพื่อให้ได้ผลลัพธ์ โดยทั่วไปประกอบด้วยการทำงานสองส่วน คือ เรียงกับกรอง (sort and filter) ผมละเว้นกรณี aggregation ($group, $unwind) ที่สามารถแปลงร่างผลลัพธ์ได้ เพราะจะทำให้ซับซ้อนเกินไป


เมื่อใดที่ query มีคำสั่ง sort กำหนดให้คำตอบเรียงลำดับมาให้ด้วย MongoDB พยายามเลือกใช้ index ที่ใช้ได้ก่อน เป็นโหมด "เรียงก่อนกรอง" ทำให้สามารถอ่านข้อมูลตามลำดับมากรองผลลัพธ์แล้วตอบได้ทันที จะเห็นว่าไม่จำเป็นต้องอ่านครบทั้งหมดก่อน


ในทางกลับกันหากคำสั่ง sort นั้นไม่สามารถใช้ index ได้ก็จะต้องทำงานในโหมด "กรองก่อนเรียง" (blocking sort) พยายามหาวิธีกรองข้อมูลให้ได้เร็วที่สุด แล้วเอาผลลัพธ์ที่ได้ทั้งหมดมาเรียงลำดับตามที่กำหนดก่อน ถ้าดาต้าเบสขนาดใหญ่ การกรองข้อมูลให้ได้ผลลัพธ์ทั้งหมดย่อมใช้เวลานาน หากผลลัพธ์ที่ได้มีจำนวนมากการเรียงลำดับย่อมใช้เวลานาน หรือทำไม่ได้ (อ้างอิง ข้อจำกัด blocking sort ขนาดของผลลัพธ์ต้องไม่เกิน 100MB)[https://www.mongodb.com/docs/manual/reference/method/cursor.sort/#sort-and-index-use]


เมื่อไม่สามารถใช้ index เพื่อ sort ก็จะพยายาม optimize ในขั้นตอน filter แทน พยายามหา index ที่กรองข้อมูลได้เร็วที่สุด เช่น ใช้ index ที่มีฟิลด์ตรงกับเงื่อนไขที่ต้องการกรอง เพื่อไม่ต้องทำ document scan อ่านข้อมูลเต็มจากดิสก์เข้ามากรอง [https://www.mongodb.com/docs/manual/core/query-optimization/#performance]


จะเห็นว่าการใช้ query ที่ไม่กำหนดเงื่อนไข sort ทำให้ไม่ต้องเสียเวลาเรียงลำดับภายหลัง เหลือเพียงขั้นตอนกรองข้อมูล ทำให้สามารถตอบผลลัพธ์ได้ทันที ไม่จำเป็นต้องรอให้กรองข้อมูลทั้งหมดเสร็จก่อน รับประกันได้ว่า query แบบไม่กำหนด sort เร็วกว่าแน่นอน


ข้อจำกัดที่ผู้ออกแบบรายงานควรเข้าใจ คือ ความแม่นยำของข้อมูล ไม่ควรใช้กับกรณีการอ่านข้อมูลแบบ pagination query (limit & skip) ทีละ 100 ข้อมูล แล้วเอามาต่อกัน มีโอกาสที่ผลลัพธ์ที่ได้แต่ละครั้ง ซ้ำซ้อนกับครั้งก่อน หรือกระโดดหายไป หากช่วงรอยต่อของข้อมูลนั้น เป็น duplicate value เช่น ข้อมูลลำดับ 100 (อ่านรอบแรก) กับ 101 (อ่านรอบที่สอง)​ มีค่าวันที่เป็นวันเดียวกัน (อ้างอิง Sort Consistency)[https://www.mongodb.com/docs/manual/reference/method/cursor.sort/#sort-consistency]


สรุปคำตอบ "sort=null" ไม่สามารถใช้ได้การทุกกรณี เหมาะสำหรับการออกแบบที่แสดงข้อมูล preview หรือไม่สนใจความแม่นยำ เช่น แคตตาล็อกสินค้า เพราะสามารถอ่านข้อมูลได้เร็วกว่า หรือรายงานที่ผู้ออกแบบรู้ขอบเขตจำนวนคำตอบ เช่น รายงานภาษีขายต่อเดือน มีบิลไม่เกิน 1000 รายการ แล้วใช้ควบคู่กับคำสั่ง "limit=1000" เพื่อให้อ่านข้อมูลทั้งหมดมาในคราวเดียว หลบเลี่ยงความผิดพลาดของ pagination query


โจทย์ท้าทายที่จะฝากไว้ให้คิด สมมติว่าเจอเคสรายงานภาษีขายของกิจการที่มีรายการหลักหมื่นต่อเดือน ไม่สามารถอ่านทั้งหมดมาในคราวเดียว จะต้องออกแบบ query อย่างไร เมื่อนั้นจะเข้าใจคำที่เคยบอก ตอนที่ดาต้าเบสยังเล็ก อาจจะใช้ query อย่างไรก็ได้ ผลลัพธ์ที่ได้​ (เร็ว)ไม่แตกต่างกัน จนกระทั่งวันหนึ่งที่ดาต้าเบสมีขนาดใหญ่ query เดิมเริ่มใช้ไม่ได้ (ช้า) เมื่อนั้นเป็นสัญญาณบอกให้ต้องทบทวนรายละเอียด ทำความเข้าใจธรรมชาติของข้อมูล เข้าใจกลไกของดาต้าเบสอย่างจริงจัง


ดู 0 ครั้ง0 ความคิดเห็น

โพสต์ล่าสุด

ดูทั้งหมด

Comments


Post: Blog2_Post
bottom of page