n+1-logo

พี่ @roofimon ส่งบทความเรื่อง Web API and N+1 problem มาให้อ่าน
เป็นเรื่องที่น่าสนใจมาก จึงทำการสรุปหน่อยดีกว่า
ดังนั้นมาทำความรู้จักและเข้าใจกับ ปัญหา N+1 กัน
นั่นใจว่าเป็นปัญหาที่ทุกๆ คนเห็นหรือทำอยู่เป็นประจำ

ทำความรู้จักกันหน่อย

ปัญหา N+1 นั้น เป็นปัญหาพื้นฐานที่ทุกๆ คนต้องเคยพบเจอแน่นอน
คำอธิบายง่ายๆ ของปัญหานี้คือ

เมื่อทำการส่ง request ไปยังระบบอะไรก็ตามเพียง 1 ครั้ง
แล้วระบบนั้น จะต้องทำงานอย่างต่ำ N+1 ครั้งเสมอ
ซึ่ง N คือจำนวนสิ่งที่เกี่ยวข้องหรือส่วนทำงานตาม request นั้นๆ

ตัวอย่างเช่น การดึงข้อมูลด้วย ORM ที่ดึงข้อมูลแบบ Lazy
นั่นคือ เมื่อมีการดึงข้อมูลเพียง 1 ครั้งแล้วภายในระบบของ ORM
จะทำการดึงข้อมูลจากส่วนอื่นๆ ที่เกี่ยวข้องกับตารางนั้นๆ ทั้งหมด
ส่งผลให้ประสิทธิภาพการทำงานแย่มาก
อาจจะเกิดการ lock ตารางในฐานข้อมูล
และ timeout ขึ้น เมื่อใช้เวลาการทำงานสูงๆ

แต่ปัญหา N+1 นั้นไม่ได้เป็นปัญหาเฉพาะ ORM เท่านั้นนะครับ
ระบบการทำงานอื่นๆ ก็มีปัญหานี้ได้เช่นกัน
ตัวอย่างเช่น

  • NoSQL
  • Web API ใน API แต่ละตัวอาจจะทำการดึงข้อมูลมาจาก API หลายๆ ตัว เพื่อนำมารวมกัน
  • Distributed caching

เริ่มต้นด้วยปัญหา N+1 กับ NoSQL

ตัวอย่างที่เห็นได้ชัดเจนก็คือ  MongoDB
ทำการเก็บข้อมูลในรูปแบบของเอกสาร สามารถอ้างอิงเอกสารอื่นๆ ผ่าน object id
ซึ่งวิธีการอ้างอิงแบบนี้นำไปสู่ปัญหา N+1 นั่นเอง
ในการดึงข้อมูลจาก MongoDB แต่ละครั้ง
จะต้องทำการดึงข้อมูลหรือ query ข้อมูลจำนวน N+1 ครั้ง

Batching API

คือ API ที่ให้ผู้ใช้เรียกใช้งานเพียงครั้งเดียว โดยภายในจะทำการหลากหลายขั้นตอน
เป้าหมายเพื่อลดจำนวนการเรียกใช้งานของฝั่งผู้ใช้ หรือ client
แต่หารู้ไม่ว่า มันกลับเป็นการสร้างปัญหา N+1 ให้กับฝั่งผู้ให้บริการหรือ server
เป็นการการส่งภาระงานไปให้

  • query ในฐานข้อมูล
  • ผ่าน batching api ของพวก NoSQL

API composition

ในปัจจุบันมีการนำแนวคิดของ SOA มาใช้ออกแบบระบบงานกันเยอะ
ซึ่งออกแบบกันไปจนถึงขนาดเล็กมากๆ ที่เรียกว่า Microservice

การสร้าง API เพื่อรองรับการใช้งานของผู้ใช้งาน
โดยทางผู้ใช้งานนั้น ทำการส่ง request มายัง API เพียงครั้งเดียว
ก็จะได้ข้อมูลตามที่ต้องการ
เนื่องจากข้างใน API จะทำการเรียก API อื่นๆ
และรวบรวมข้อมูลที่ต้องการไว้ด้วยกัน
ต่อจากนั้นทำการส่งข้อมูลกลับไปให้ผู้ที่ร้องขอ
การทำงานลักษณะนี้ของ API จึงเป็นปัญหา N+1 นั่นเอง

Distributed caching

ในการใช้งานพวก caching เพื่อเก็บข้อมูลต่างๆ ไว้ในหน่วยความจำ
เพื่อลดเวลาการดึงข้อมูลโดยตรงจากระบบเก็บข้อมูล
ทำให้ระบบมีประสิทธิภาพที่ดีขึ้น

ตัวอย่างเช่น
การใช้งาน Redis
เมื่อการดึงข้อมูลจาก redis ในแต่ละครั้งต้องส่งข้อมูลจำนวนมากๆ กลับไป
การดึงข้อมูลหรือการทำงานภายใน มีการดึงข้อมูลหลายครั้ง
หรือต้องการใช้เวลาการทำงานสูงขึ้น
หรือบางครั้งอาจจะทำงานแบบขนาน เพื่อให้ดึงข้อมูลเร็วขึ้น
ซึ่งล้วนก่อให้เกิดปัญหา N+1 ทั้งสิ้น

ปัญหามีจำนวนมากขึ้น เมื่อมีการใช้ caching server หลากหลายที่

เหมือนว่าจะทำอะไร ก็เจอปัญหา N+1 ทั้งนั้นเลย แล้วเรามีแนวทางอย่างไรบ้าง ?

ต้องเข้าใจก่อนว่าปัญหา N+1 มันไม่ใช่ปัญหาเสมอไปนะ
แต่เพียงเราต้องรู้จักเตรียมตัว เพื่อรับเมื่อกับปัญหาเท่านั้นเอง
โดยแนวทางการรับมือมีดังต่อไปนี้

1. ปัญหาแรกคือ เรื่องความสัมพันธ์ของข้อมูล แก้ด้วยการ Denomalize

เช่นข้อมูลการซื้อขาย จะต้องดึงข้อมูลส่วนที่เกี่ยวข้องมาด้วย เช่น

  • ข้อมูลลูกค้า
  • ข้อมูลสินค้า
  • ข้อมูลร้านค้า

ดังนั้นในการดึงข้อมูลการซื้อขายเพียงครั้งเดียว
ก่อให้เกิดการดึงข้อมูลหรือ query จำนวนมากกว่า 1 ครั้ง
หรือใครจะบอกว่าเรา join ก็ได้นะ
แต่นั่นก็คือการเปลี่ยนที่อยู่ของปัญหา N+1 เท่านั้นเอง

Screen Shot 2557-06-05 at 4.52.46 PM

แต่เนื่องจากปัญหานี้มันเป็นของคู่กันกับ RDBMS อยู่แล้ว
ดังนั้น สามารถแก้ไขด้วยการเก็บข้อมูลตามแต่ละหน้าจอต้องการเท่านั้น
ซึ่งนั่นคือวิธีการ denormalize ข้อมูลนั่นเอง

ดูเหมือนว่าจะลดปัญหาการเก็บข้อมูลไปได้
แต่ปัญหาจะเกิดขึ้นเมื่อมีจำนวนข้อมูลสูงขึ้น
หรือในส่วนอื่นๆ ต้องการข้อมูลไปใช้งาน

เช่นถ้านำข้อมูลการซื้อขาย ลูกค้า สินค้า ร้านค้า มารวมกัน
จะทำให้เก็บข้อมูลและดึงข้อมูลเร็วมากๆ
แต่ถ้าต้องการดึงข้อมูลเฉพาะข้อมูลลูกค้าล่ะ ?
หรือต้องการแก้ไขข้อมูลลูกค้าล่ะ จะทำอย่างไร ?
ซึ่งทำให้ปัญหา N+1 กลับมาอีกแล้ว

เช่นปัญหาสุด classic คือ
การเก็บข้อมูลของ content และ comment
ถ้าการดึงข้อมูล content แล้วมีข้อมูลของ comment กลับมาด้วยเลย
จะทำให้ผู้ใช้งานไม่ต้องเรียก API เพื่อดึงข้อมูลหลายครั้ง
และในฝั่งผู้ใช้บริการก็เก็บข้อมูล content และ comment ไว้ด้วยกัน
ดังนั้นจึงไม่เกิดปัญหา N+1 มากนัก ( แต่ก็ยังเกิดนะ )

แต่ลองคิดดูว่า ถ้าจำนวน comment ของ content นั้น
มีจำนวนมากๆ ล่ะ จะเกิดปัญหาอะไรขึ้น ?
การดึงข้อมูลมาจำนวนมากๆ ในครั้งเดียว
ไม่น่าจะเป็นวิธีการที่ดีมากนัก

ดังนั้น การแก้ไขปัญหา N+1 ในกรณีนี้ จะต้องดูตามความเหมาะสม
ไปในแต่ละระบบกันว่า มีพฤติกรรมการใช้งานอย่างไร

2.  Parallelising call

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

แน่นอนว่าวิธีการนี้ ทำให้ระบบงานเราซับซ้อนมากขึ้น
และเสียงต่อการทำงานที่ผิดพลาดสูงขึ้น
แถมยังต้องการ การดักจับความผิดพลาด และรูปแบบการ retry การทำงาน เสมอ

3. Layer  caching

จากที่อธิบายไปแล้วว่า caching สามารถนำไปสู่ปัญหา N+1 ได้เช่นกัน
แต่ว่า caching ยังคงมีประโยชน์มาก เนื่องจากมีความเร็วในการทำงานสูง

การแก้ไขปัญหาเรื่อง distributed caching สามารถสร้าง local caching server ขึ้นมา
เพื่อให้อยู่ใกล้กับผู้ใช้งานมากที่สุด เหมือนว่าเป็นตัวแทนของ caching ที่อยู่อย่างกระจัดกระจาย

โดย local caching server ต้องมีการกำหนดอายุของข้อมูลด้วยเสมอ
เพื่อต้องการข้อมูลที่ใหม่ล่าสุดอยู่เสมอ จะมากหรือน้อยขึ้นอยู่กับระบบงาน
และต้องคอยตรวจสอบดูด้วยว่า อัตราการเจอและไม่เจอ caching data เป็นอย่างไร
ถ้ามีอัตราการไม่เจอที่สูงมาก แสดงว่าสิ่งที่คุณกำลังทำมีปัญหาแล้วนะ

Screen Shot 2557-06-05 at 4.56.15 PM

โดยสรุปแล้ว

จะพบว่าปัญหา N+1 ไม่ได้เกิดจากการรวบรวมข้อมูลจากหลายๆ ที่เข้าด้วยกันเท่านั้น
ยังพบในส่วนของการพัฒนา Web APIs ด้วย เนื่องจากต้องการสร้าง API ให้ใช้งานง่ายๆ
ดังนั้นจึงก่อให้เกิดปัญหา N+1 ขึ้นเช่นกัน
แต่ถ้าระบบยังสามารถรองรับได้ และทำงานได้อย่างถูกต้อง
แสดงว่าปัญหา N+1 ยังไม่กระทบอะไรมากนัก
เนื่องจากมีการเตรียมวิธีการรองรับไว้แล้ว

การทำงานแบบขนานและ caching data นั้น
มีความสำคัญอย่างมากในการพัฒนาระบบงานในปัจจุบัน
แต่ก็อย่าลืมในเรื่องการจัดการข้อผิดพลาด การ retry เมื่อมีปัญหาขึ้นมาด้วย
รวมทั้งระบบ monitoring ของระบบ ที่ขาดไปไม่ได้

จะพบว่า มันไม่มีอะไรตายตัวในเรื่องของการออกแบบระบบเลย

Reference Website
http://www.infoq.com/articles/N-Plus-1
http://www.sitepoint.com/silver-bullet-n1-problem/

Tags:,