Scalable System: Design & Pitfall Part 2

Photo by Jannik Selz

ระบบหยุดไป 10 วินาที คนรอเป็นแสน? แล้วทำยังไงดี?

นึกภาพเทียบกับโรงพยาบาล สมมติฝ่ายการเงินของโรงพยาบาล คอมฯ เจ๊งหมด รับจ่ายเงินไม่ได้ต้องใช้เครื่องคิดเลขกดและรับเงินสด อันนี้แม้ว่าจะเป็นจุดสุดท้ายของระบบแล้วคือถึงจะจ่ายเงินไม่ได้ก็ยังตรวจโรคให้คนไข้ใหม่ได้ แต่คนก็จะมาออกันเต็มโรงพยาบาล จนถึงจุดหนึ่ง Capacity ของโรงพยาบาลก็จะล้น และพลอยทำให้รับคนไข้ใหม่ ไม่ได้ไปด้วย พอถึงจุดนั้น หมอก็ว่าง พยาบาลก็ว่างหมด แม้ว่าคนจะเดินกันเต็มโรงพยาบาลก็ตาม ก็ระบบมันเป็นแบบนั้น

แล้วทำยังไงดี?

  1. ทำให้มีคอขวดให้น้อยที่สุด

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

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

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

ถ้าเทียบกับระบบ IT อันนี้ก็คือ

  • เราเอาระบบพวก Reverse Proxy Caching มา Cache หน้าเว็บหรือ API Response ที่มาบ่อยๆ แต่ทุกคนเห็นเหมือนๆกัน นานๆเปลี่ยนที เอาไว้ให้ตอบ Request แบบเร็วๆ
  • เอา Memcache หรือ Redis มา Cache SQL Query บางประเภทที่รู้อยู่แล้วว่ามันซ้ำบ่อยมาก ไม่ค่อยเปลี่ยน หรือ Cache ผลการ Render หน้าเว็บเอาไว้บางส่วน เช่น Top menu หรือ Footer
  • Limit request บางรูปแบบ อาจจะในรูปของ CMS Backend หรืออะไรก็ตามที่ทำงานช้า เช่น การสร้าง Content ให้มีจำนวนไม่มากจนผิดปกติ เผื่อยามที่เกิดเหตุไม่คาดฝัน เราก็ปิดส่วนนี้ทิ้งไปก่อนได้แต่หน้าบ้านก็ยังทำงานไปตามปกติ

ถ้าเราทำทั้งหมดไปแล้ว ปริมาณภาระงานที่หลงเหลืออยู่ก็จะเป็นอะไรที่เป็นงานที่กินภาระจริงๆละ เราสามารถใช้วิธีขยาย (Scale) ระบบตรงส่วนนั้นให้มากพอที่จะรองรับคนที่จะมาใช้บริการ “ที่คาดการณ์ไว้” แต่ก็นั่นละครับ มันเป็นไปได้เหมือนกันที่จู่ๆจะมีคนมาใช้บริการเยอะมากๆจนทะลุขีดจำกัด ยังไงทุกส่วนของระบบก็มีขีดจำกัดครับ มันอาจจะเยอะมากจนเหมือนไม่มีขีดจำกัด ก็เท่านั้นเอง

อย่าลืมทำให้ส่วนที่จะเป็นคอขวดของระบบได้ Scale ได้และเผื่อ Capacity ไว้ให้เยอะๆ

2. อย่าให้มีการรอกัน

อันนี้เจอค่อนข้างจะบ่อยมาก คืออาจจะมาจาก Trend การเขียนโปรแกรมแบบ Microservices ด้วยมั้ง ที่จะมีการเรียก curl (curl คือ HTTP Client library ยอดนิยมที่คนใช้กันเยอะมากๆ) กันเองภายในเพื่อดึงข้อมูลกันภายใน ผ่านทาง HTTP REST API บางอย่างที่คิดขึ้นมาเอง

ตอนแรกมันก็อาจจะดูสวยงามดีครับ แต่ในระดับ FastCGI เลยคือพวก PHP หรือ UWSGI พวกนี้มีคิวงานที่สั้น หากใน 1 request เราเขียนให้มันไปเรียก HTTP Request อีกเครื่องและรอ Response ก็เหมือนเราเอาเจ้าหน้าที่ ที่ให้บริการเรา ไปวิ่งรอต่อคิวเป็นเหมือนลูกค้าของอีก Counter หนึ่ง ถ้าเกิดแบบนี้มากๆ เจ้าหน้าที่ ที่จะรอให้บริการลูกค้าก็หายไปหมด แล้วใครจะกลับมาให้บริการลูกค้า?

หรือแม้แต่ถ้าใช้ภาษาสมัยใหม่ เช่น Node.js หรือ Go แต่หากไม่เขียนโปรแกรมไว้เป็นแบบ Asynchronous ไว้ มันก็ยังคงรอกันอยู่นะ

ซ้ำร้ายหนัก หลายๆ เคสที่เคยเจอคือ ปลายทางของการ curl ที่ว่านั้นคือ “เครื่องตัวเอง” แปลว่าเรากำลังเอาเจ้าหน้าที่ ที่ให้บริการเราอยู่ วิ่งกลับมาเป็นลูกค้าของ Counter บริการที่ตัวเองเป็นเจ้าหน้าที่

แน่นอนถ้าลูกค้าน้อยๆ มันก็อาจจะเหมือนไม่เกิดอะไร ระบบยังทำงานกันทัน แต่ถ้าลูกค้าเยอะๆ ละ หรือว่าถ้าปลายทางที่เราไปขอรับบริการนั้นเกิดคิวเต็มละ เราก็จะเกิดอาการ “คิวเต็มกันมาเป็นทอดๆ” ขึ้น

ถ้าเขียนโค้ดไปมาแล้วแต่ละส่วนเกิดเหตุการณ์ “รอกันเองเป็นวงกลม” แบบนี้คือหายนะ

นอกจากการ “รอกันเอง” เราต้องพยายามลดการไป “รอคนอื่น” ด้วยเช่นกัน การพยายามไปดึงเนื้อหาจากภายนอกเป็นสิ่งที่ต้องพึงระวัง เพราะจากที่บอกไว้แล้วข้างต้น Network เองก็เป็น Limit ของระบบอยู่เหมือนกัน

ถ้าเราต้องไปรอระบบภายนอก ที่ไม่ใช่ระบบที่ Scale ได้ดีๆอีก ก็เป็นหายนะเช่นกัน

ทำยังไงที่จะไม่ให้ระบบต้องรอกันเอง (โดยไม่จำเป็น)

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

ลดการใช้งานแบบนี้โดยไม่จำเป็น

อาจจะเป็นคำตอบกำปั้นทุบดินสุดๆ แต่ถ้าเราสามารถเลี่ยงได้แต่เนิ่นๆ ทำไมไม่ทำล่ะ ตรงนี้อาจฟังดูน่าตลก แต่บ่อยครั้งที่ผมเจอทีม Dev ของระบบ เลือกใช้วิธี curl library หรือ ฟังก์ชันอื่นๆที่เทียบเท่า จาก “URL ภายในเครื่องตัวเอง”แทนที่จะดึงข้อมูลตรงผ่าน Database ด้วยเหตุผลทำนองว่า “พอดีคนเขียนโค้ดตรงนั้นลาออกไปแล้ว ไม่อยากแก้ครับทำแบบนี้สะดวกกว่า” “ดึงมาเป็น JSON เลย Parse ง่ายดี โค้ดเดิมก็มีไว้แล้วพอดีเคยเปิดไว้ให้ภายนอกใช้ เราก็ดึงเอามาเองซะ” “ไม่อยากคุยกับ Dev คนที่ทำตรงนั้นครับ พอดีคนละทีมกัน”

ไม่ได้หมายถึงว่าเป็นเหตุผลที่ใช้ไม่ได้นะครับ (โดยเฉพาะอันสุดท้าย ปัญหา “คน” เรื่องใหญ่) แต่จงตระหนักว่ามันสามารถเกิดผลร้ายได้ และพยายามเลี่ยงเอาไว้

เลี่ยงมาใช้ปลายทางที่ทำงานเร็วกว่า

ตัวอย่างง่ายๆ เลย เช่นใช้ Caching หรือเอาง่ายๆ เลยเก็บลง Filesystem แทนที่จะไปดึง Resource โดยผ่านทาง HTTP REST จากปลายทางมาโดยตรง ก็เอามาเก็บลง Cache หรืออย่างแย่เลยคือลงไฟล์ไว้ก่อน อย่างน้อยหาก Cache ยังอยู่ ถึงแม้จะเกิดเหตุอะไรที่ทำให้ดึงข้อมูลจากปลายทางไม่ได้ ระบบก็ยังไม่ล่มโดยทันที

กรณีที่เป็นการดึงข้อมูลอะไรที่ไม่ต้อง Update ถี่ และมี Endpoint ที่จะดึงข้อมูลไม่กี่แบบ (ยกตัวอย่าง ดึงราคาหุ้น ราคาทอง ราคาน้ำมัน Tweet ต่างๆ) กระบวนการตรงนี้แก้ไขได้ง่ายมาก โดยการเปลี่ยนมาใช้การทำงานตามเวลา เช่น การใช้ Crontab ดึงข้อมูลมาเก็บไว้เป็นไฟล์ แล้วเปลี่ยน URL ที่ดึงข้อมูลมาเป็น Local File (file://) แทน แบบนี้แก้โค้ดน้อยมากแล้วยังสะดวกด้วย ถ้าเป็นระบบที่มี Web server หลายเครื่องก็ใช้ Crontab ดึงข้อมูลบนเครื่องๆหนึ่งแล้วกระจายไปทุกเครื่องผ่าน rsync หรือเครื่องมืออะไรทำนองนั้นก็ได้

การใช้เครื่องมือสมัยใหม่ เช่นการ Cache ภายในตัว Library (ซึ่งอาจจะใช้ Cache server ภายนอก เช่น Memcache) ก็พอโอเค แต่พึงระวังไว้ว่า Cache ก็มีวัน Expire และถ้ามัน Expire จังหวะที่ Services ปลายทางตาย ก็อาจจะเกิดหายนะขึ้นได้เสมอ (แต่ก็ยังดีกว่าไม่ทำอะไรเลยนะ)

แอบสำเนาเอกสารของ Client (Cache) ไว้บางส่วน เพื่อว่าจะได้ไม่ต้องไปดึงข้อมูลจากหลังบ้านทุกรอบไป

หมายเหตุ: Timeout ไม่ได้ช่วยอะไรซะทีเดียวนะ (แต่มีไว้ก็ดีกว่าไม่มี)

ถึงตรงนี้บางคนอาจจะสงสัยว่า เอ๊ะถ้าเรากลัวว่าปลายทางจะล่ม เราก็ Handle Exception เอาไว้สิ แล้วตั้ง Timeout ให้ Request ทั้งหมดซะ แบบนี้ถ้าปลายทางตายระบบเราก็จะทำงานเป็นปกติ ไม่เห็นต้องทำอะไรให้ยุ่งยาก

อย่าลืมว่าระบบทั้งหมดของเราทำงานอยู่ในโลกของ The Flash คือวิ่งกันด้วยความเร็ว 1 ใน ล้านวินาที แต่เราไม่สามารถการันตีได้เสมอว่าการตอบสนองของข้อมูลทาง Network นั้นจะกลับมาในระดับ Microsec หรือบางครั้งก็แม้แต่ระดับ Millisec ก็บอกไม่ได้ อีกทั้งโดยมาก Library ทั้งหลายจะอนุญาตให้เราตั้ง Timeout ต่างๆ ในระดับ Millisec เท่านั้นเอง

ยิ่งไปกว่านั้น กรณีที่ “ปลายทางตาย ก็ส่ง Exception” บ่อยครั้งที่เราจะเจอ การตายแบบ แน่นิ่ง อึ้ง กิมกี่ ไม่ตอบสนองอะไร แต่ไม่ได้มีอะไรตอบมานะว่าตายแล้ว คือด้วยระบบ Firewall WAF IDS อะไรทั้งหลายที่ตั้งกันขึ้นมาเพื่อป้องกันการโจมตีทางเครือข่าย บ่อยครั้งมันจะใช้วิธีการ “ทำตัวเงียบๆไว้ถ้าไม่รู้จะตอบอะไร” (แปลแบบทางเทคนิค: เลิกตอบ ICMP ทุกรูปแบบ) ฝั่ง Client แทนที่จะได้รับการแจ้งว่า “ตายแล้วนะ” ก็จะได้รับมาแต่ความเงียบ เหมือนจะทำงานได้ แต่ก็เงียบ จนกว่าจะไปสุด Network Timeout นั่นแหละ หรือถ้าเป็นกรณีที่ปลายทางนั้นคิวเต็มจริงๆ มันก็ไม่ว่างจะมาตอบเราเหมือนกันว่า “เฮ้ย ไม่ว่างแล้วนะ” เราก็จะต้องรอไปยัน Timeout สมมติเราตั้ง Timeout ไว้แค่ 1 วินาที ก็อาจจะแปลว่ามีคนมารอเราแล้ว 10,000 คนนะ

แต่ก็ดีกว่าไม่ตั้งอะไรเลยนะครับ รอ 10,000 (Timeout 1 วินาที) ยังดีกว่ารอ 300,000 คน (Timeout 30 วินาที ซึ่งเป็นช่วง default ของหลายๆอย่าง) นะ

ผลักภาระไปที่ Client

เนื่องจากทรัพยากรของ Server นั้นมีจำกัด เรายังต้องรองรับคนมาใช้บริการอีก 999,999 คน ไม่ควรเสียเวลามาให้บริการลูกค้าคนแรกแค่คนเดียว แต่ทรัพยากรของ Client นั้นมีเหลือเฟือ การที่แอพบนมือถือ ต้องมาคอย(แอบ)เช็คข้อมูล Update แล้ว แสดงข้อมูลใหม่นั้นมันเป็นเรื่องนิดเดียวของมือถือเครื่องหนึ่ง แต่ถ้าภาระนี้มาตกอยู่บน Server แปลว่าถ้ามีคนใช้ระบบ 1,000,000 คนเราก็ต้องคอยเช็คให้คนทั้ง 1,000,000 คนนั้นมันคือหายนะของ Server เลยทีเดียว

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

โชคดีที่ในโลกไอที เรามี Web browser และ App เป็นข้ารับใช้ ทำให้เราไม่รู้สึกถึงความยุ่งยากตรงนี้

ไม่มีจุดไหนที่เจ้าหน้าที่ต้องวิ่งไปทำหน้าที่เป็นคนไข้เลย ทุกอย่างคนไข้ทำหมด ดูเหมือนจะแย่แต่แบบนี้แหละลดภาระเจ้าหน้าที่ได้แบบสุดๆ

ถ้าเทียบกับระบบ IT มันหมายถึงว่าแทนที่เราจะเขียนหน้าเว็บด้วย PHP ให้ไปดึงข้อมูลจากระบบ API หลังบ้านซึ่งก็เป็น PHP อีกชุดหนึ่งบน Server เครื่องเดียวกัน แต่ให้ข้อมูลออกมาเป็น JSON (ซึ่งเราก็เป็นคนเขียนเองนั่นแหละ) แล้วเอามาแสดงเป็น HTML ให้ผู้ใช้ได้เห็นอีกที อาจจะเลี่ยงโดยการเขียนหน้าเว็บทั้งหมดด้วยเฟรมเวิร์กอย่างพวก Angular หรือ React เพื่อให้การทำงานเกือบทั้งหมดถูกผลักไปอยู่บน Web browser และเชื่อมต่อกับ Backend ของเราโดยตรงเพื่อเอาข้อมูลซึ่งอยู่ในรูปของ JSON ไปแสดงผลแทน แบบนี้ก็จะลดภาระของ “การรอกันโดยไม่จำเป็น” ลงไปได้อีก (แถม Reuese API ไปใช้กับ App ได้ง่ายด้วย) จริงๆแล้วอันนี้เป็น Trend การเขียนเว็บในยุคปัจจุบันเลย ถ้าใครยังไม่ได้ลองก็ควรหาเวลาลอง

ปรับให้โปรแกรมทำงานแบบ Asnychronous

หากการรอกันเป็นสิ่งที่เลี่ยงไม่ได้จริงๆ วิธีที่ดีที่สุดในการรองรับการทำงานแบบนี้ คือเขียนโค้ดให้ทำงานแบบ Asnychronous

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

  1. ผู้ใช้ไม่ต้องยืนรอคิว ไปรอในที่ๆจัดไว้ให้ และสามารถทำอย่างอื่น ไม่ว่าจะเป็น โทรคุยกับเพื่อน เล่นมือถือ ไปได้อย่างสะดวก ถ้าเทียบกับโปรแกรม นั่นก็แปลว่าจังหวะนั้นโปรแกรมของเราสามารถไปทำอย่างอื่นที่มีประโยชน์ (เช่น รับผู้ใช้คนถัดไป มากดบัตรคิว หรือไปทำอย่างอื่นที่ไม่ต้องเข้าคิวเลย) ได้ ถ้าระบบของเราเพียงต้องรอการตอบสนองจากระบบปลายทางเท่านั้น (เช่น เราไปดึงข้อมูลจาก Facebook) รู้วิธีเขียนเพียงเท่านี้ก็เพียงพอ
  2. มีการแยกแยะงาน ทุกคนไม่ต้องรออย่างเดียวกัน ถ้าคนไหนอยากเปิดบัญชี ก็รอเฉพาะ Counter เปิดบัญชีว่าง บางทีอาจจะว่างพร้อมๆกัน 2 ช่อง Counter เลยก็เป็นได้ ผู้ใช้คนอื่นที่ต้องการงานที่ง่ายกว่า เช่น ฝาก ถอน โอน จ่าย ก็ไปรอรับบริการส่วนนั้นไป แบบนี้ฝั่ง Server ที่รับ Request ก็จะไม่เจออาการ “คนมาออกันรอหน้า Counter จนล้น”

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

กรณีที่ Server ต้องทำตัวเป็น Client — ใช้ระบบพวก Message Queue

เช่น RabbitMQ โดยเขียน Worker มารองรับการทำงานต่างๆที่อาจจะนาน เมื่อมี Request ที่ต้องทำงานกลุ่มนี้เข้ามาถึง Server ฝั่ง Server ก็ Connect เข้าหา Message Queue server ภายในเพื่อ Notify ให้ Worker ที่รันไว้แล้วให้มารับงานไปทำ โค้ดฝั่ง Server เองก็กลับไปทำงานอื่นๆต่อไป (เช่น รับ Request อื่นๆ) แบบนี้ฝั่ง Server เอง ก็ไม่สูญเสียจำนวนคิวที่จะต้องไปรอการตอบกลับของ Request จากปลายทาง ขณะเดียวกันถ้าเราพบว่าทำงานไม่ทันก็สามารถเพิ่มปริมาณ Worker ให้มากขึ้นเพื่อรอรับการตอบกลับจาก Server ภายนอกได้ หรือถ้าพบว่า Worker มากเกินไปจนกิน Resource อื่นๆ เราก็สามารถลดจำนวน Worker ลงได้เช่นกันโดยไม่กระทบกับ Response ฝั่งหน้าบ้าน

Server submit job ไปที่ Worker แล้วกลับมาให้บริการต่ออย่างรวดเร็ว ส่วน Worker ก็รองานเสร็จค่อยแจ้งกลับไปยัง Server

กรณีที่ฝั่ง Server ต้องรองรับ Request จาก Client ค้างไว้นานๆ เพื่อตอบกลับ

ตัวอย่างของกรณีแบบนี้ก็เช่น การทำระบบพวก Push Notification ทั้งหลายแหล่ (ที่ไม่อยากไปวิ่งผ่านพวก Cloud Messaging ด้วยเหตุผลอะไรก็แล้วแต่) พวกนี้สามารถเขียนโค้ดให้ทำงานแบบ Publisher/Subscriber (PubSub) แล้วอาศัยโมดูลช่วยที่มีใน Web server (เช่น กรณี Nginx ก็มี nginx-push-stream-module)โดยไอเดียคือ Web server เองจะถือ Connection จาก Client ค้างไว้ (Subscribe) และมีช่องทางให้ฝั่ง Server ส่งข้อมูลไปยัง Client (Publish) ซึ่งปกติมักจะรองรับการส่งข้อมูลไปทีละหลายๆ Client พร้อมๆ กันได้ด้วย แบบนี้ก็จะไม่เปลืองจำนวนคิวที่ฝั่งโปรแกรมเลย

ตัวอย่าง Publisher Scriber Model โดยใช้ Nginx push-stream module ช่วยลดภาระแก่ส่วนของ Fast CGI

สรุป

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

ระบบ Web หรือ App ยุคปัจจุบัน มันทำงานแบบเชื่อมต่อกันผ่านทางเครือข่าย จะภายในหรือภายนอกก็แล้วแต่ แล้วมันก็ทำงานขึ้นต่อกันเป็นทอดๆ อยู่เสมอ

ทุกๆ ส่วนของระบบนั้นจะมี Limit ของมันเอง และมีการทำงานในลักษณะที่ใกล้เคียงกับระบบการเข้าคิว

เวลาที่ระบบโดยภาพรวมล่ม ต้องหาดูก่อนว่า อะไรคือคอขวด มันไม่จำเป็นว่าจะต้องเป็นเพราะ CPU ไม่แรงพอ Memory ไม่มากพอ หรือ Disk ไม่เร็วพอ บ่อยครั้งมันเป็นเพราะ เราไปเรียกใช้ทรัพยากรบางอย่างจากภายนอกบ้าง หรือบางทีไม่มีอะไร แค่ตั้งขนาดของคิว (Limit ต่างๆ) ไว้ต่ำไปในส่วนที่สามารถรองรับสูงกว่านั้นได้แต่ลืมนึกไป

ตรงนี้ถ้าเราไม่เข้าใจ อันนี้แย่ละแปลว่าเราต้องทำความเข้าใจระบบเราเองไม่ดีพอ แต่ถ้ามองไม่เห็น คือไม่รู้จะวัดขีดจำกัดยังไง แปลว่าเรายังวาง Probe สำหรับการ Monitor ระบบไว้ไม่ละเอียดพอ หา Monitoring Tool มาช่วยเยอะๆ แทบทุกส่วนของระบบสามารถ Monitor ได้เสมอ (โดยเฉพาะถ้าเป็น Open-source Software) และส่วนมากคือสามารถ Monitor ในระดับที่เห็นเป็นตัวเลข จำนวนคิวปัจจุบัน ขนาดคิวทั้งหมด แบบดูง่ายๆไม่ต้องใช้ความรู้ลึกซึ้งมาแปลความอะไรเลย

และพอเราเข้าใจระบบเราดี รู้ขีดจำกัดของระบบ ถัดมาก็ต้องหาทางแก้ไข

ซึ่งจริงๆมันจะดีกว่ามากถ้าสามารถคาดการณ์ขีดจำกัดล่วงหน้าได้ แต่ก็นั่นละครับ บางทีถ้าไม่เจ็บซะก่อนเราก็จะไม่รู้จักจำ

หมายเหตุ:

  • 502 Bad Gateway จะแสดงออกมาทันทีเมื่อ Backend เช่น FastCGI ไม่ตอบสนอง โดยมากที่เจอคือ FastCGI เต็มหมด เจอบ่อยกับ NGINX
  • 503 Services Unavailable คล้ายๆ กรณีของ 502 Bad Gateway แต่เป็น Default error ที่ Varnish ใช้กรณีที่ Backend ไม่ตอบสนอง