W E B D E V E L O P M E N T

GraphQL ใช้ผิดวิธีเว็บช้าลง! 7 ข้อผิดพลาดที่พบบ่อยและวิธีแก้

GraphQL ใช้ผิดวิธีเว็บช้าลง! 7 ข้อผิดพลาดที่พบบ่อยและวิธีแก้

GraphQL ถูกพูดถึงอย่างกว้างขวางนับตั้งแต่ Facebook เปิดโค้ดต้นแบบสู่สาธารณะเมื่อปี 2015 และกลายเป็นตัวเลือกแรก ๆ ของใครหลายคนเมื่อต้องออกแบบ API ใหม่ จุดขายอันโด่งดังคือ client เป็นฝ่ายเลือกได้เองว่าจะดึง field ไหน หมดปัญหา over-fetch แบบ REST ทว่าประสบการณ์จริงในโปรเจกต์ซอฟต์แวร์กลับไม่สวยงามเช่นนั้น บทความนี้รวบรวมข้อผิดพลาดที่พบบ่อยที่สุดตอนใช้ GraphQL ซึ่งหลายครั้งทำให้เว็บที่ตั้งใจจะเร็วกว่าเดิมกลับช้าลง โค้ดพังง่าย และค่าบำรุงรักษาพุ่งสูง

ข้อผิดพลาดที่ 1 เลือกใช้ GraphQL ทั้งที่ REST พอจะรับมือได้

ข้อผิดพลาดที่ใหญ่ที่สุดไม่ใช่ตอนเขียนโค้ด แต่อยู่ที่ตอนตัดสินใจเลือกเทคโนโลยีเสียมากกว่า งานเว็บแอปส่วนใหญ่มีหน้าจอไม่กี่แบบ workflow ค่อนข้างตรงไปตรงมา REST หรือแม้แต่การแสดงหน้า HTML ธรรมดาจาก server ก็จัดการได้ดีและเข้าใจง่ายกว่า GraphQL ที่มาพร้อมความซับซ้อนของ schema, resolver และ tooling รอบด้าน

ลองมองภาพธุรกิจ E-commerce ซึ่งเป็นอีกประเด็นที่หลายคนสงสัยว่าธุรกิจขนาดไหนควรขายของออนไลน์ดี ร้านค้าขนาดเล็กที่เพิ่งเริ่มต้นไม่จำเป็นต้องลุยสร้างระบบ backend ของตัวเองเลยแม้แต่น้อย การเริ่มจากแพลตฟอร์มสำเร็จรูปอย่าง Shopee, Lazada, LINE OA หรือระบบร้านค้าสำเร็จรูปที่จัดการสต็อกและการชำระเงินได้ในตัว คือทางเลือกที่คุ้มค่าที่สุด GraphQL กลายเป็นเรื่องจำเป็นก็ต่อเมื่อธุรกิจมีขนาดกลางขึ้นไป มีหน้าจอหลายแบบ ทั้งเว็บ ทั้งแอป ทั้งหน้าร้านสาขา และต้องการอ่านข้อมูลสินค้า สต็อก และราคาจากชุดข้อมูลเดียวกันทุกช่องทาง ในจุดนั้น schema กลางรูปแบบเดียวของ GraphQL จึงคืนทุนให้เห็นจริง

บทเรียน: ตอบโจทย์ให้ชัดก่อนเลือกเครื่องมือ

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

ข้อผิดพลาดที่ 2 ลืมปัญหา N+1 Query ตัวร้ายเงียบที่กินทรัพยากรทุกวัน

ข้อผิดพลาดที่นักพัฒนาเจอเป็นประจำที่สุดคือ N+1 query ภาพตัวอย่างคือระบบร้านค้าออนไลน์ query รายการสั่งซื้อ 200 ออเดอร์ และ resolver บอกให้เพิ่มข้อมูลลูกค้าของแต่ละออเดอร์ด้วย ผลที่ได้คือการ query database 201 ครั้ง แทนที่จะเป็น 2 ครั้ง ยิ่งออเดอร์มาก ยิ่งช้าเป็นเท่าตัวและยิ่งกดดันฐานข้อมูลหนักขึ้น

วิธีแก้อันดับหนึ่งคือ DataLoader ซึ่งเป็นไลบรารีที่บัฟเฟอร์คำขอไว้เป็นชุดแล้วยิง query ครั้งเดียวครอบทุกข้อมูล ในฝั่ง Django ที่ใช้ graphene-django ยังต้องใช้ select_related และ prefetch_related ช่วยให้ ORM ไม่ดึงข้อมูลซ้ำกัน ที่สำคัญคือตรวจหา N+1 ตั้งแต่ช่วงพัฒนาด้วยเครื่องมืออย่าง nplusone หรือ django-silk ก่อนขึ้น production

ข้อผิดพลาดที่ 3 เปิดให้ใครก็ได้ query ฝังลึกแบบไม่จำกัด

GraphQL เปิดทางให้ client ส่ง query ที่ซ้อนกันลึก ๆ ได้ นักพัฒนาบางคนมองว่าเป็นฟีเจอร์เจ๋ง ๆ จนลืมไปว่ามันคือประตูหลังให้ผู้ไม่หวังดีส่ง query วนซ้ำ เช่น รายการสินค้า -> รายละเอียด -> ผู้ขาย -> ร้านค้า -> รายการสินค้า ซ้ำไปหลายสิบชั้น เซิร์ฟเวอร์จะถูกบังคับให้ทำงานหนักจนไม่ตอบสนอง หรือเรียกว่าระเบิดการทำงานแบบ DoS

แนวทางป้องกันที่นิยมใช้คือกำหนดความลึกของ query สูงสุด เช่น 10 ชั้น, ให้คะแนนความซับซ้อนของแต่ละ query แล้วจำกัดงบประมาณ และใช้ persisted query ให้ client ส่งเฉพาะ identifier ของ query ที่อนุมัติไว้แล้วเท่านั้น ทำให้ผู้โจมตีไม่มีทางยิง query อะไรก็ได้เข้ามา

ข้อผิดพลาดที่ 4 ไม่มีระบบแบ่งหน้า และปล่อยให้ดึงข้อมูลทุกอย่าง

ต่อให้คุมความลึกได้แล้ว อีกด่านที่มักหลุดคือการไม่มี pagination ผลลัพธ์ต่อ query เดียวอาจเป็นสินค้า 50,000 รายการหรือ log ยาวเป็นหมื่นบรรทัด ใครก็ตามที่เปิดหน้าจอหนึ่งจะถล่ม bandwidth และหน่วยความจำเสียเปล่า

วิธีที่แนะนำคือ cursor-based pagination ตามสเปก Relay โดยเฉพาะเมื่อข้อมูลมีการเพิ่ม ลบ หรือเรียงลำดับเปลี่ยนตลอดเวลา ส่วนระบบที่ข้อมูลค่อนข้างนิ่ง offset pagination ก็ประหยัดกว่าและเข้าใจง่ายกว่า หลักการร่วมกันคือ ทุก list ใน schema ต้องมีขนาดหน้าสูงสุดและเครื่องมือสำหรับขอดูหน้าถัดไปเสมอ

ข้อผิดพลาดที่ 5 คิดว่า GraphQL แคชไม่ได้ แล้วปล่อยวางมือ

REST ใช้ HTTP cache ร่วมกับ CDN ได้ง่าย เพราะทุก endpoint คือ URL ที่แคชได้ในตัว แต่ GraphQL ส่วนใหญ่รับ query ผ่าน POST ตัวเดียวกัน ทำให้การแคชแบบเดิมใช้ไม่ได้ผล นักพัฒนาหลายทีมจึงบอกว่าดูแลไม่ได้ แล้วปล่อยให้โหลดซ้ำทุกครั้ง ค่าบริการ server และฐานข้อมูลจึงบานปลาย

วิธีที่ใช้จริงคือแคชที่ชั้น resolver โดยเก็บผลลัพธ์ของ field ที่ถูกเรียกบ่อย ๆ ไว้ใน Redis หรือใช้ persisted query ร่วมกับ CDN สำหรับ query ที่เหมือนกันจากทุก client การแคชแบบ field-level นี้ทำได้ไม่ยาก แต่เปลี่ยนต้นทุนการดูแลระบบไปอย่างมีนัยสำคัญ แน่นอนว่าต้องออกแบบ key ให้ถูกต้อง และมีวิธีล้างแคชเมื่อข้อมูลเปลี่ยน เช่น ราคาสินค้าหรือสถานะสต็อกมีการอัปเดต

ข้อผิดพลาดที่ 6 จัดการ Error แบบหลวม ๆ ทั้งที่ GraphQL ทำให้เรื่องนี้ยากกว่าเดิม

จุดเด่นที่หลายคนลืมใช้ให้เป็นประโยชน์คือ GraphQL ตอบข้อมูลบางส่วนได้ แม้ field หนึ่งล้มเหลว field อื่นยังคืนค่าได้พร้อมเหตุผลใน errors แยกต่างหาก หมายความว่าแอปที่จัดการไม่ดีจะแสดงข้อมูลกึ่งสมบูรณ์โดยที่ผู้ใช้ไม่รู้ตัว เช่น หน้าตะกร้าสินค้าแสดงรายการครบ แต่ราคารวมขาดไปหนึ่งบรรทัดเพราะ resolver ตัวหนึ่งล้มเหลว

ควรแบ่งข้อผิดพลาดให้ชัดเจนระหว่าง user error ที่ผู้ใช้แก้ไขได้เอง กับ system error ที่ต้องแจ้งทีมพัฒนา โดยใช้ error codes หรือ extensions มาตรฐาน รวมถึงเขียนเทสต์ครอบคลุมกรณีที่ field บางตัวเป็น null และทำให้โค้ดฝั่ง client แยก error ออกจากข้อมูลปกติได้เสมอ

ข้อผิดพลาดที่ 7 ออกแบบ Schema โดยไม่มีวินัย ไม่มีเวอร์ชัน ไม่มีแผนรองรับอนาคต

schema ใน GraphQL เปรียบเสมือนสัญญาระหว่างฝั่ง server กับ client ทุกฝ่าย เมื่อเปลี่ยนชื่อ field แก้ type หรือลบ field ทิ้ง client ที่ยังไม่รู้ก็พังทันที ยิ่งมีแอปหรือพันธมิตรภายนอกเชื่อมต่อมากเท่าไหร่ก็ยิ่งเจ็บตัวมากเท่านั้น

หลักปฏิบัติคือห้ามลบ field ทันที ให้ประกาศ @deprecated พร้อมคำอธิบายเส้นทางใหม่ไว้เป็นเวลานานพอ แล้วค่อยเก็บสถิติการใช้งานเพื่อตัดสินใจลบทีหลัง และในโปรเจกต์ที่ทีมใหญ่ ควรกำหนด schema ให้ชัดเจนก่อนเขียน resolver เพื่อให้ทุกฝ่ายเห็นภาพเดียวกันตั้งแต่แรก

แล้ว E-commerce เหมาะกับธุรกิจขนาดไหน สรุปสั้น ๆ ในมุมสถาปัตยกรรม

กลับมาที่คำถามที่ทุกธุรกิจต้องเจอคือ ควรขายของออนไลน์เมื่อไหร่ ภาพที่ชัดเจนคือธุรกิจขนาดเล็ก 1-5 คน ควรเน้นแพลตฟอร์มสำเร็จรูปประหยัดต้นทุน เอาเงินไปลงกับการตลาดและสินค้าดีกว่า ระดับกลางที่มีรายได้ประจำและต้องควบคุมประสบการณ์ลูกค้าเอง ถึงเวลาลงทุนร้านค้าแบบ headless commerce โดยใช้ GraphQL เป็นตัวกลางเชื่อมเว็บ แอป และหน้าร้านสาขาเข้าด้วยกัน ส่วนธุรกิจใหญ่ที่มีหลายแบรนด์หลายภาษา GraphQL ไม่ใช่คำถามว่าจะใช้หรือไม่ แต่อยู่ที่ว่าจะวางระบบแคชและ pagination ให้ทำงานไร้ที่ติเหมือนประเด็นทั้ง 7 ข้างต้นได้แค่ไหน

บทสรุป: ปัญหาที่แท้จริงไม่ใช่ GraphQL แต่เป็นวิธีใช้ของเรา

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

  • เลือกใช้เท่าที่จำเป็น เริ่มจาก REST หรือแพลตฟอร์มสำเร็จรูปก่อนเสมอ
  • จัดการ N+1 ด้วย DataLoader และ prefetch_related
  • จำกัดความลึกและความซับซ้อนของ query ทุกตัว
  • มี pagination ครบทุกรายการข้อมูลใน schema
  • วางแผนแคชตั้งแต่ต้น ทั้ง field-level และ CDN
  • นิยาม error ให้ชัดเจน และเขียนเทสต์ครอบคลุมกรณีข้อมูลไม่ครบ
  • ออกแบบ schema ด้วยวินัย ใช้ @deprecated แทนการลบ field ทันที

สำหรับใครที่อยู่ระหว่างตัดสินใจว่าจะย้ายระบบไปใช้ GraphQL หรือกำลังเจ็บกับการดูแล API เดิมอยู่ ทีมงาน pythonthailand.com (Para-Studio เชียงใหม่) มีประสบการณ์จริงทั้งการออกแบบ schema และแก้ปัญหา GraphQL บน Django ไล่ตั้งแต่การแก้ N+1 query วางระบบแคช ไปจนถึงการประเมินความคุ้มค่าก่อนลงทุนระบบ E-commerce ของคุณ ไม่ว่าเซิร์ฟเวอร์จะเริ่มช้าจนรับไม่ไหว หรืออยากวางสถาปัตยกรรมให้เติบโตในระยะยาว ทักมาพูดคุยกันได้ที่หน้า /contact ครับ


หากชอบบทความดีๆ โปรดติดตามพวกเราด้วยนะคะ 🙏