Scalable System: Design & Pitfall Part 1

Scalable System: Design & Pitfall Part 1

Photo by Paul Dufour

บทความนี้เขียนขึ้นมาด้วยตั้งใจว่าจะตอบคำถามที่มักจะมาด้วยความเข้าใจแบบมึนๆ เกี่ยวกับการทำงานของ Web และ App Backend ที่เจอเรื่อยๆ

“ทำไมเพิ่ม CPU/Server แล้วระบบมันยังรองรับคนได้ไม่เยอะขึ้น เพิ่มไว้ไม่พอเหรอ”

“CPU ก็ยังว่าง ทำไมเว็บล่มไปแล้ว MySQL Setting ไว้ห่วยหรือเปล่า”

“ผมเปลี่ยนมาใช้ Nginx แล้วมันชอบขึ้น 502 bad gateway ผมว่า Nginx มันต้องห่วยแน่เลยครับ”

แท้จริงอาจจะเกิดจากความไม่เข้าใจการทำงานของระบบที่เป็นแบบ Scalable เลยคิดว่าจะเขียนในแบบที่ อ่านเข้าใจได้ง่ายที่สุดเท่าที่จะทำได้ดูครับ


มองการทำงานของ Web/App Backend ในยุคที่ทุกอย่าง Scale

ตอนที่ทุกคนเริ่มเขียน บริการที่เป็น Web หรือเป็น Backend ของ App ก็คงจะมีระบบเริ่มต้น แค่นี้ ซึ่งเมื่อคนดูเยอะขึ้นๆๆๆๆๆ ถ้าระบบเขียนออกแบบมาดีๆ ก็จะกลายเป็นแบบนี้

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

มันคือระบบการทำงานแบบ Counter Services (ไม่ใช่ไอ้ที่ไว้จ่ายเงินนะครับ) ของบริการอย่าง ศูนย์บริการมือถือ ธนาคาร หรือตัวที่ใกล้เคียงแบบเป๊ะมากๆ ก็คือ หน่วยงานราชการ หรือ โรงพยาบาลครับ โดยเฉพาะโรงพยาบาลรัฐ ที่มักจะประกอบไปด้วยหลากหลาย Counter ที่มี Label แปะไว้ “รับยา ช่อง 3 ค่ะ” “พบคุณหมอ ช่อง 80 รอหน้าห้อง 404 นะคะ” อะไรแบบนี้

เอ… มันเหมือนกันได้ยังไง เวลาเราเข้า Web หรือเปิด App มันทำงานวูบเดียวเราก็ได้ข้อมูลมาแล้ว คิดง่ายๆว่าในโลกของคอมพิวเตอร์ เจ้าหน้าที่ๆประจำทุกช่องนั้นเป็นลูกหลานของ The Flash กันหมด ทุกคนทำงานด้วยความเร็วระดับ Micro second (1 ใน 1,000,000 วินาที เอาจริงๆทำงานกันในระดับ Nano second ด้วยซ้ำไปแต่เอาง่ายๆแค่ Microsec ก่อนละกัน) เวลาเราไปรับบริการอะไรเลยรู้สึกว่ารวดเร็วปานฟ้าแลบ วูบเดียวก็ได้สิ่งที่อยากได้มา แท้ที่จริงแล้วมันมีการ รับบัตรคิว รอคิว รับเอกสาร ไปยื่นช่องถัดไป รอคิวอีกที พบหมอ วางบัตรรับยา รับยา ยื่นใบเสร็จจ่ายเงิน จ่ายเงิน กระบวนการทั้งหมดเกิดแบบรวดเร็วมาก โดยมีพนักงานประจำตัวเราคนหนึ่งคอยอำนวยความสะดวก(เหมือนอยู่โรงพยาบาลเอกชน) พนักงานคนนั้น ก็คือ Web Browser หรือว่า App นั่นเอง

อยู่ในโลกแห่ง 0.0000001 วินาทีกันหมด

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


หุบเหว (Pitfall) ของการออกแบบระบบ

กลับมาที่คำถาม ว่าทำไม services ถึงตาย ทำไมเราเจอ 502 bad gateway หรือ 503 services unavailable ได้แต่ทำไม CPU ยังว่างๆ

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

  1. Database ไม่ว่างจะมา services เพราะมี query ขนาดใหญ่ หรือซับซ้อน หรือเป็นการเขียน (insert/update) มาเป็นจำนวนมาก จนไม่ว่างจะให้บริการงานง่ายๆ
  2. Web server หรือ FastCGI Backend ไม่ว่างจะมา Process Request เพราะรอคำตอบกลับจากการไป curl เรียก Twitter/Facebook/Youtube ขอข้อมูลอยู่

ถ้าใครอ่านดูแล้วงง ถ้าแปลงเป็นแบบนี้ละ

  1. เภสัชฯในห้องยาไม่ว่างมาจ่ายยา เพราะมียาตัวใหม่เข้ามาต้องแบ่งคนมารับยาและจัดการเอายาเข้าระบบ ทำให้ไม่เหลือเภสัชให้บริการเลย
  2. หมอไม่ว่างกำลังตรวจคนไข้เต็มทั้งหมด เพราะเน็ตโรงพยาบาลล่ม เอาประวัติคนไข้ได้ช้ามากต้องใช้วิธีวิ่งเอกสารที่เป็นกระดาษเอา

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

จากประสบการณ์น้อยๆของผู้เขียน อันนี้คือประมาณ 80% ของปัญหาความช้าส่วนใหญ่ที่เกิดขึ้น และหลายครั้งเกิดจาก ความรู้เท่าไม่ถึงการณ์ คือคิดว่าเออ ตอนลองบน Dev environment ก็ใช้งานได้นี่ แน่นอนครับถ้าลองด้วยปริมาณภาระงานน้อยๆ อาการแบบนี้ก็คงไม่เกิด หรือบางกรณีที่โชคร้าย ลอง Load Test ดูก็ไม่เจอปัญหา เพราะไม่ได้ไปแตะโดนจุดทีเป็นปัญหา หรือบางครั้งก็แตะโดนในปริมาณที่น้อย เพราะไม่คิดว่า ภาระงานแบบนี้จะเกิดบ่อย (แต่พอเกิดขึ้นมาทีก็พาล่มไปเลย)

เมื่อคอขวดเกิด Client ก็ไปค้างที่บริเวณคอขวด ระบบโดยรวมก็เหมือนล่มไปหมด

คิวของ Services ต่างๆมันมีจำกัด

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

  1. Apache Web Server — ปกติก็จำกัดด้วยพวก MaxClient MaxThread ซึ่งสัมพันธ์กับขนาดของ Memory บนเครื่อง เพราะยิ่งเยอะยิ่งกิน Memory โดยทั่วๆไปก็ตั้งอยู่ไม่เกินหลักพัน (แต่ถ้าใช้ Event MPM ก็เยอะหน่อย)
  2. พวก FastCGI Backend ต่างๆ เช่น PHP-FPM หรือ UWSGI — ปกติ ก็มักจะหลักร้อยครับ เพราะตรงจุดนี้มักจะกินแรมพอสมควร สมมติ 64MB ต่อ process 500 ตัวก็ 32G แล้วนะครับ
  3. MySQL/MariaDB — ปกติก็ตันแถวๆจำนวน Connection สูงสุดที่ซอฟต์แวร์รองรับได้ (Max connection) ครับ (ไม่จำเป็นต้องเท่ากับจำนวน Client นะ แต่ถ้าไม่แย่จนเกินไปก็น่าจะเท่าๆกัน) Default คือ 150 เอง แต่ที่เห็นปกติก็จะตั้งไปหลัก หลายร้อย จนถึงหลักพัน ขึ้นกับปริมาณ Memory ที่มีพอ
  4. Nginx — คิดว่าน่าจะตันตรง worker_connections แต่อันนี้เยอะมากหน่อยน่าจะตั้งกันได้ถึงหลักหลายหมื่น ปกติสูตรของผมคือ 16,384 ครับ
  5. Network — เอ๊ะ Network เกี่ยวอะไรด้วย? อันนี้เกี่ยวเต็มๆถ้า services ของเรา มีการดึง Resource บางอย่างจากภายนอก เช่น Twitter, Facebook, LINE, หรือ Youtube เป็นต้น “อ้าวปกติเว็บพวกนี้ก็ไม่ล่มนี่ครับ” ครับบริการพวกนี้มี Uptime ที่สูงมาก (เว้น Twitter?) แต่ ถนนที่เชื่อมระหว่าง services ของเรา ไปยัง services ข้างนอกเหล่านี้ มันอาจจะไม่ได้เสถียรแบบนั้นนะครับ ยิ่งถ้าเป็นบริการภายนอกประเทศ Limit ของการดึง Resource พวกนี้มันก็ขึ้นกับขนาด International Bandwidth Quota ของเรานั่นละครับ (ซึ่งโดยมาก มักจะตัวเลขน้อยอยู่นะ 100Mbps นี่ก็ถือว่าเยอะแล้วครับ ถ้าสมมติคนหนึ่งต้องใช้ 1Mbps ก็ได้ 100 คนเองนะ อันนี้เป็นข้อได้เปรียบของพวก Cloud services ต่างประเทศอยู่เหมือนกัน)
  6. คอขวดอื่นๆจากระบบ — สารพัด Limit ครับ ไม่ว่าจะเป็น จำนวนพอร์ตต่อ 1 IP ที่ Limit (สูงสุดแค่ 65,535 แต่ใช้ได้จริงๆก็ไม่ทุกเบอร์) ขนาด Connection Tracking Table หรือจำนวน File descriptor per process หรือ ฯลฯ เยอะแยะไปหมดครับ พวกนี้บางทีก็อยู่หลักหมื่นบ้าง บางอันก็เป็นแสน
ถ้าต้องรอการตอบกลับจาก Services ที่ต้องข้าม Network ไปไกลๆโดยเฉพาะต่างประเทศ ก็เหมือนพนักงานของเราส่ง Messenger ขี่จักรยานไปรับของ

จากตัวอย่าง จะเห็นว่า โดยส่วนใหญ่ ตัวเลขมันน้อยมาก ถ้าเทียบกับวลีที่เรามักจะใช้ในการ Marketing “เราจะรองรับคนเป็นล้านๆๆคน” สิ่งที่ทำให้ระบบยังทำงานอยู่ได้ เพราะทุกส่วนของคิวเป็นลูกหลาน The Flash ถ้ามาพร้อมกันระบบอาจจะรับงานได้พร้อมๆกันหลักพันคน แต่ทุกคนทำงานเสร็จเร็วมากในระดับ 1 ในล้าน วินาที ทำให้มองภาพรวม ในหลักวินาที นาที ชั่วโมง หรือวัน ตัวเลขมันเลยเยอะมาก สมมติวินาทีหนึ่งรองรับได้สัก 10,000 คน นาทีหนึ่งก็ล่อไป 600,000 คนแล้วนะ

แต่นั่นคือกรณีที่ทุกคนทำงานได้อย่างไหลลื่น ไม่สะดุดติดขัดใดๆ สมมติถ้าเกิดเหตุไม่คาดคิดบางอย่าง เช่น หน่วยคัดกรองผู้ป่วย ต้องหยุดทำงานไป 10 วินาที นั่นแปลว่าถ้ามีคนมาใช้บริการเราวินาทีละ 10,000 คน ในช่วง 10 วินาทีนั้นก็จะมีคนมารอไปแล้ว 100,000 คน


Leave a comment