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

นิยามของการออกแบบระบบและแนวทางโดยรวม

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

การแยกความต่างระหว่าง state และ stateless

  • ส่วนที่ยากที่สุดในงานออกแบบซอฟต์แวร์ก็คือ การจัดการสถานะ
  • บริการที่ไม่เก็บข้อมูลและคืนผลลัพธ์ทันที เช่น การเรนเดอร์ PDF ของ GitHub ถือเป็นแบบ stateless
  • ในทางกลับกัน บริการที่เขียนข้อมูลลงฐานข้อมูลคือบริการที่จัดการสถานะ
  • ควรลดจำนวนคอมโพเนนต์ที่เก็บสถานะภายในระบบให้มากที่สุด เพราะช่วยลด ความซับซ้อนและโอกาสเกิดความขัดข้อง
  • โครงสร้างที่แนะนำคือให้มีเพียงบริการเดียวที่รับผิดชอบการจัดการสถานะ ส่วนบริการอื่นให้เน้นบทบาทแบบ stateless เช่น การเรียก API หรือการส่งอีเวนต์

การออกแบบฐานข้อมูลและจุดคอขวด

การออกแบบสคีมาและดัชนี

  • เพื่อจัดเก็บข้อมูล ควรมี การออกแบบสคีมาที่มนุษย์อ่านเข้าใจง่าย
  • สคีมาที่ยืดหยุ่นเกินไป (เช่น เก็บทุกอย่างไว้ในคอลัมน์ JSON) อาจเพิ่มภาระให้ทั้งโค้ดแอปพลิเคชันและประสิทธิภาพ
  • ควรกำหนด ดัชนีที่เหมาะสม ตามคอลัมน์ที่มีการคิวรีบ่อย การใส่ดัชนีให้ทุกอย่างกลับทำให้เกิดโอเวอร์เฮดโดยไม่จำเป็น

วิธีแก้ปัญหาคอขวด

  • การเข้าถึงฐานข้อมูลมักเป็นคอขวดหนัก อยู่บ่อยครั้ง
  • หากเป็นไปได้ การจัดการข้อมูลที่ซับซ้อนด้วย JOIN ภายในฐานข้อมูลแทนการทำในแอปพลิเคชันมักให้ประสิทธิภาพดีกว่า
  • เมื่อใช้ ORM ต้องระวังความผิดพลาดจากการทำให้เกิดคิวรีภายในลูป
  • ในบางกรณี การแยกคิวรีออกเป็นหลายส่วนเพื่อปรับภาระของฐานข้อมูลหรือความซับซ้อนของคิวรีก็เป็นอีกวิธีหนึ่ง
  • กลยุทธ์ กระจาย read query ไปยัง read replica มีประสิทธิภาพในการลดภาระของโหนดหลักที่รับงานเขียน
  • เมื่อมีคิวรีจำนวนมากถาโถมเข้ามา ธุรกรรมและงานเขียนอาจทำให้ฐานข้อมูลโอเวอร์โหลดได้ง่าย จึงควรพิจารณา การ throttle หรือจำกัดคิวรี

การแยกงานช้ากับงานเร็ว

  • งานที่ผู้ใช้โต้ตอบด้วยต้องการ การตอบสนองภายในระดับไม่กี่ร้อยมิลลิวินาที
  • สำหรับงานที่ใช้เวลานาน (เช่น การแปลง PDF ขนาดใหญ่) แนวทางที่มีประสิทธิภาพคือ ให้ส่วนที่จำเป็นน้อยที่สุดกับหน้าบ้านทันที แล้วส่งงานที่เหลือไปทำเบื้องหลัง
  • งานเบื้องหลัง มักทำงานร่วมกันระหว่างคิว (เช่น Redis) และตัวรันงาน
  • สำหรับงานที่ตั้งเวลาไว้ไกลล่วงหน้า การใช้ตารางใน DB แยกต่างหากและรันด้วย scheduler มักใช้งานได้จริงมากกว่า Redis

การแคช

  • การแคช ช่วยลดต้นทุนและเพิ่มประสิทธิภาพเมื่อมีการคำนวณแบบเดิมหรือมีราคาแพงซ้ำ ๆ
  • โดยทั่วไป วิศวกรจูเนียร์ที่เพิ่งเรียนเรื่องแคช มักอยากแคชทุกอย่าง ขณะที่วิศวกรที่มีประสบการณ์จะระมัดระวังมากกว่าในการนำแคชมาใช้
  • แคชคือการเพิ่มสถานะรูปแบบใหม่ จึงมี ความเสี่ยงเรื่องการซิงก์ ความผิดพลาด และข้อมูล stale
  • แนวทางที่เหมาะสมคือควรลองปรับปรุงประสิทธิภาพก่อน เช่น เพิ่มดัชนีให้คิวรี แล้วค่อยใช้การแคช
  • สำหรับแคชขนาดใหญ่ อาจใช้วิธี บันทึกลง document storage เป็นระยะ เช่น S3/Azure Blob Storage แทน Redis/Memcached ได้เช่นกัน

การประมวลผลอีเวนต์

  • บริษัทส่วนใหญ่มักมี event hub (เช่น Kafka) และให้บริการต่าง ๆ กระจายการประมวลผลบนพื้นฐานของอีเวนต์
  • แทนที่จะใช้อีเวนต์พร่ำเพรื่อ การออกแบบ API แบบ request–response ที่เรียบง่ายมัก มีประโยชน์กว่าในด้านการบันทึกล็อกและการแก้ปัญหา
  • การประมวลผลแบบอิงอีเวนต์เหมาะเมื่อผู้ส่งไม่จำเป็นต้องสนใจพฤติกรรมของผู้รับ หรือใน สถานการณ์ปริมาณสูงและยอมรับความหน่วงได้

วิธีส่งผ่านข้อมูล: Push และ Pull

  • การส่งข้อมูลมี 2 แบบคือ Pull (ขอแล้วค่อยตอบ) และ Push (ส่งอัตโนมัติเมื่อมีการเปลี่ยนแปลง)
  • แบบ Pull นั้นเรียบง่าย แต่มีปัญหาเรื่องการร้องขอซ้ำและโอเวอร์โหลดได้
  • แบบ Push จะส่งข้อมูลไปยังไคลเอนต์ทันทีเมื่อข้อมูลฝั่งเซิร์ฟเวอร์เปลี่ยน จึง มีประสิทธิภาพและเหมาะกับการรักษาข้อมูลให้เป็นปัจจุบัน
  • หากต้องรองรับไคลเอนต์จำนวนมาก ก็จำเป็นต้องขยายอินฟราสตรักเจอร์ให้เหมาะกับแต่ละรูปแบบ (เช่น event queue, cache server หลายตัว เป็นต้น)

โฟกัสที่ฮอตพาธ (Hot Paths)

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

การบันทึกล็อก เมตริก และการติดตาม

  • เพื่อวิเคราะห์สาเหตุเมื่อเกิดปัญหา ควรบันทึก ล็อกแบบละเอียดสำหรับเส้นทางที่ไม่ปกติ (unhappy path) อย่างจริงจัง
  • จำเป็นต้องเก็บ ตัวชี้วัดด้าน observability พื้นฐาน เช่น ทรัพยากรระบบ (CPU/หน่วยความจำ) ขนาดคิว และเวลาของคำขอ/งาน
  • ไม่ควรดูแค่ค่าเฉลี่ยเท่านั้น แต่ต้องสังเกต ตัวชี้วัดการกระจายอย่างค่า latency แบบ p95, p99 ด้วย เพราะคำขอที่ช้าเพียงส่วนน้อยอาจเป็นปัญหาสำคัญของผู้ใช้หลัก

คิลสวิตช์ การรีทราย และการกู้คืนความขัดข้อง

  • การใช้ คิลสวิตช์ (หยุดระบบชั่วคราว) และการรีทรายอย่างมีกลยุทธ์เป็นสิ่งสำคัญ
  • การรีทรายแบบไม่ยั้งคิดมีแต่จะเพิ่มภาระให้บริการอื่น ดังนั้นต้องควบคุมคำขอล่วงหน้าด้วย circuit breaker เป็นต้น จึงจะได้ผล
  • การนำ Idempotency Key มาใช้ช่วยป้องกันงานซ้ำเมื่อมีการประมวลผลคำขอเดิมอีกครั้ง
  • ในบางสถานการณ์ความขัดข้อง จำเป็นต้องเลือกว่าจะใช้ fail open หรือ fail closed ตัวอย่างเช่น rate limiting ควรเอนไปทาง fail open (ยอมให้ผ่าน) เพื่อลดผลกระทบต่อผู้ใช้ ขณะที่การยืนยันตัวตนจำเป็นต้องเป็น fail closed

สรุป

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

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น