เมื่อระบบบอกว่าปกติแต่จริงๆ คือพัง: บทเรียนการแก้ปัญหา Performance สำหรับโปรแกรมเมอร์

8 นาที 11 views บันทึกเป็น PDF
เมื่อระบบบอกว่าปกติแต่จริงๆ คือพัง: บทเรียนการแก้ปัญหา Performance สำหรับโปรแกรมเมอร์

ทำไมระบบที่ดูเหมือนทำงานปกติถึงล่มได้? เรียนรู้การวิเคราะห์ปัญหา Memory และ Disk I/O ใน Kubernetes เพื่อก้าวสู่การเป็นโปรแกรมเมอร์ที่แก้ปัญหาได้จริง

เมื่อระบบบอกว่า "ปกติ" แต่ความจริงคือ "พัง"

ในโลกของ DevOps (การรวมทีมพัฒนาและทีมปฏิบัติการเข้าด้วยกัน) และ SRE (วิศวกรความน่าเชื่อถือของไซต์งาน) เรามักจะเชื่อมั่นในระบบตรวจสอบที่บอกสถานะว่า Synced (ข้อมูลตรงกัน) หรือ Healthy (สุขภาพดี) แต่ในความเป็นจริง ระบบที่ดูเหมือน "รันอยู่" อาจกำลังซ่อนปัญหาใหญ่ไว้ข้างใต้ การเข้าใจว่าทำไมระบบที่ดูสมบูรณ์แบบถึงพังได้ คือก้าวสำคัญของโปรแกรมเมอร์มืออาชีพ

เปรียบเทียบง่ายๆ เหมือนคุณขับรถที่หน้าปัดแสดงว่า "เครื่องยนต์ปกติ" แต่รถกลับวิ่งไม่ออกเพราะน้ำมันขาด นี่คือสถานการณ์ที่เรียกว่า False Positive หรือการที่เครื่องมือวัดผลให้ผลลัพธ์ว่า "ดี" ทั้งที่จริงๆ แล้วมีปัญหาซ่อนอยู่ การเป็นโปรแกรมเมอร์ที่เก่งไม่ได้วัดกันแค่ที่การเขียนโค้ด แต่คือการมองทะลุผ่าน "ค่าสถานะ" ไปยัง "อาการจริง" ของระบบ

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

การอ่านค่า IO PSI: สัญญาณเตือนภัยที่ถูกมองข้าม

วันหนึ่งระบบแจ้งเตือน IO PSI (Input/Output Pressure Stall Information) ซึ่งเป็นค่าที่บอกว่า "ระบบต้องรอคอยการอ่านเขียนข้อมูลจากดิสก์นานเกินไป" เกิน 15% เป็นเวลาสองนาที การรอคอยนี้เปรียบเสมือนคุณไปธนาคารแล้วต้องต่อคิวนานจนทำงานอื่นไม่ได้ แม้คอมพิวเตอร์จะดูเหมือนทำงานอยู่ แต่จริงๆ แล้วมันกำลัง "ค้าง" อยู่กับการรอข้อมูลจากฮาร์ดดิสก์

เรามักจะตรวจสอบด้วยคำสั่ง vmstat เพื่อดูว่าระบบกำลังทำอะไรอยู่ ในกรณีนี้พบว่ามีการอ่านข้อมูลมหาศาลถึง 90,000 บล็อกต่อวินาที แต่เมื่อตรวจสอบด้วยคำสั่ง /proc (ไดเรกทอรีที่เก็บข้อมูลสถานะของระบบใน Linux) กลับพบว่าไม่มีไฟล์ไหนถูกเปิดใช้งานในลักษณะที่อธิบายการใช้ดิสก์ขนาดใหญ่นี้ได้เลย นี่คือจุดที่มือใหม่มักจะเดาทางผิด

การเข้าใจการทำงานของ System Calls (คำสั่งที่โปรแกรมเรียกใช้ระบบปฏิบัติการ) เป็นเรื่องสำคัญมาก เพราะสิ่งที่โปรแกรม "ขอ" กับสิ่งที่ระบบปฏิบัติการ "ทำจริง" อาจไม่เท่ากัน การที่ดิสก์ทำงานหนักไม่ได้หมายความว่าโปรแกรมกำลังอ่านไฟล์อยู่เสมอไป แต่บางครั้งมันคือการที่ระบบต้อง "ดึงข้อมูลกลับ" จากหน่วยความจำสำรองเพราะแรมไม่พอ ซึ่งเป็นหัวใจสำคัญของปัญหาที่เรากำลังเจอ

# ตรวจสอบการใช้งานดิสก์ของ process ด้วย PID 713618
# ดูว่าไฟล์ไหนถูกเปิดอยู่บ้าง
ls -l /proc/713618/fd

# ตรวจสอบการอ่านข้อมูลจริงเปรียบเทียบกับที่โปรแกรมร้องขอ
cat /proc/713618/io
# rchar: คือจำนวนที่โปรแกรมขออ่าน
# read_bytes: คือจำนวนที่ดึงจากดิสก์จริง

เมื่อแรมเต็มจนระบบต้อง "กลืนกินตัวเอง"

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

ในสถานการณ์นี้ ระบบปฏิบัติการจะสั่ง OOM Kill (Out of Memory Kill) หรือการฆ่าโปรเซสที่กินแรมมากที่สุดทิ้งเพื่อให้ระบบอยู่รอดได้ สิ่งที่น่าสนใจคือ โปรเซสที่ตายไปมักจะพยายามเกิดใหม่ทันที และเมื่อมันกลับมาทำงานภายใต้สภาวะเดิม มันก็จะกินแรมจนถึงขีดจำกัดเดิมและตายซ้ำอีก กลายเป็นวงจรที่ไม่มีวันจบสิ้น

มือใหม่มักจะพลาดโดยการรีสตาร์ทโปรแกรมหรือเพิ่มทรัพยากรให้เพียงอย่างเดียว โดยไม่ได้ดูว่าการตั้งค่า Memory Request และ Memory Limit ใน Kubernetes นั้นเหมาะสมหรือไม่ หากเราตั้ง Limit สูงเกินกว่าความสามารถของเครื่อง (Overcommit) ระบบจะรับปากว่าจะจัดสรรให้ แต่สุดท้ายเมื่อถึงเวลาใช้งานจริง เครื่องจะไม่มีแรมเหลือให้ใช้

ความแตกต่างระหว่าง "Request" และ "Limit"

ในการเขียนคอนฟิกของ Kubernetes เราจะมีสองค่าหลักคือ Request (จำนวนที่ขอจองไว้) และ Limit (จำนวนสูงสุดที่อนุญาตให้ใช้) ปัญหาส่วนใหญ่เกิดจากความเข้าใจผิดว่าระบบจะจัดการทุกอย่างให้เราเอง แต่จริงๆ แล้ว Scheduler (ตัวจัดตารางงาน) ดูแค่ค่า Request ในการตัดสินใจว่าจะวางแอปไว้ที่ไหน

หากคุณตั้งค่า Request ไว้ต่ำ แต่แอปพลิเคชันของคุณกินแรมจริงสูงกว่านั้นมาก ระบบจะยอมให้คุณวางแอปตัวนั้นลงในเครื่องที่ไม่มีแรมพอรองรับการใช้งานจริง ผลลัพธ์คือเมื่อแอปทำงานหนักขึ้น ระบบจะเกิดการแย่งชิงแรมและนำไปสู่การล่มในที่สุด นี่คือเหตุผลว่าทำไมการทำ Capacity Planning (การวางแผนรองรับภาระงาน) จึงสำคัญ

ตัวอย่างการตั้งค่าที่ปลอดภัยควรจะสะท้อนความเป็นจริง:

  • Request: ตั้งให้เท่ากับปริมาณแรมที่แอปใช้ในสภาวะปกติจริงๆ
  • Limit: ตั้งให้สูงขึ้นเผื่อช่วงที่งานหนัก (Burst) แต่ต้องไม่เกินความจุของเครื่อง
  • Monitoring: ต้องมีระบบเตือนเมื่อแรมใช้งานจริงใกล้ถึง Limit ไม่ใช่แค่ดูว่าแอปยังรันอยู่ไหม

บทเรียนจากเหตุการณ์จริง: การสืบสวนเชิงลึก

เหตุการณ์ที่เล่ามาสอนให้เรารู้ว่า การเชื่อมั่นในสถานะ Running นั้นไม่เพียงพอ ในฐานะโปรแกรมเมอร์ คุณต้องฝึกนิสัยการเป็นนักสืบ เมื่อมีแจ้งเตือนให้ตั้งคำถามเสมอว่า "ทำไม" แทนที่จะรีบแก้ไขที่ปลายเหตุ การใช้เครื่องมืออย่าง dmesg (ดูข้อความจาก Kernel) หรือการอ่านค่าจาก /proc คือทักษะที่จะแยกโปรแกรมเมอร์ทั่วไปออกจากโปรแกรมเมอร์ที่เก่ง

เมื่อคุณเจอเหตุการณ์ที่ระบบบอกว่าปกติแต่เครื่องค้าง ให้เริ่มจากขั้นตอนเหล่านี้:

  1. ตรวจสอบบันทึกของระบบ: ใช้คำสั่ง dmesg -T | grep -i oom เพื่อดูว่ามีโปรแกรมไหนถูกฆ่าทิ้งเพราะแรมไม่พอหรือไม่
  2. วิเคราะห์การใช้ทรัพยากร: ตรวจสอบว่าแรมที่ใช้จริงสูงกว่า Request ที่ตั้งไว้หรือไม่
  3. ดูพฤติกรรมย้อนหลัง: อย่าดูแค่ปัจจุบัน ให้ดูค่าเฉลี่ยย้อนหลังเพื่อหาจุดเริ่มต้นของปัญหา
ผลลัพธ์ที่คุณควรเห็นคือการพบ "ต้นตอ" ของการใช้ทรัพยากรที่ผิดปกติ ไม่ใช่แค่การเห็นข้อความแจ้งเตือนที่บอกว่ามีอะไรบางอย่างพังไปแล้ว

สรุป: มองให้เห็นมากกว่าที่เครื่องมือรายงาน

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

อย่าหลงเชื่อสถานะเพียงอย่างเดียว จงฝึกฝนการใช้เครื่องมือพื้นฐานอย่าง top, htop, vmstat และการอ่านไฟล์ใน /proc ให้คล่องแคล่ว เพราะเมื่อถึงเวลาที่ระบบเกิดปัญหาขึ้นจริงๆ เครื่องมือเหล่านี้จะเป็นสิ่งเดียวที่บอกความจริงกับคุณได้ ไม่ใช่แดชบอร์ดที่สวยหรูแต่รายงานค่าที่ผิดพลาด

ตัวอย่างสุดท้าย: ในการทำโปรเจกต์ส่วนตัว หากคุณพบว่าแอปบน Docker ทำงานช้าลงเรื่อยๆ แทนที่จะรีสตาร์ทคอนเทนเนอร์ทันที ให้ลองใช้คำสั่ง docker stats เพื่อดูการใช้แรมแบบเรียลไทม์ และตรวจสอบว่าแรมมีการเพิ่มขึ้นเรื่อยๆ หรือไม่ (Memory Leak) นี่คือวิธีที่โปรแกรมเมอร์มืออาชีพใช้ในการแก้ไขปัญหาอย่างยั่งยืน แทนที่จะแก้ปัญหาแบบชั่วคราวซ้ำแล้วซ้ำเล่า


ที่มา: Synced, Healthy, Running, Wrong — DEV Community

แชร์บทความ

Facebook X LINE

บทความที่เกี่ยวข้อง

เทคนิคเขียนแอป Flutter สำหรับ Meta Smart Glasses ให้ลื่นไหลและมีประสิทธิภาพ

เทคนิคเขียนแอป Flutter สำหรับ Meta Smart Glasses ให้ลื่นไหลและมีประสิทธิภาพ

เรียนรู้วิธีเขียนแอป Flutter เชื่อมต่อ Meta Smart Glasses ให้ทำงานเร็ว ไม่กระตุก ด้วยการวางสถาปัตยกรรมโค้ดและการจัดการข้อมูลแบบมือโปรที่มือใหม่ทำตามได้จริง

ที่มา: DEV Community

7 hours ago 10 นาที
5 views
เปรียบเทียบ WebSocket, SSE และ Polling เลือกวิธีทำระบบ Real-Time ให้เหมาะกับงาน

เปรียบเทียบ WebSocket, SSE และ Polling เลือกวิธีทำระบบ Real-Time ให้เหมาะกับงาน

อยากทำระบบ Real-Time แต่ไม่รู้จะเลือกใช้ Polling, SSE หรือ WebSocket ดี? มาดูวิธีเลือกใช้ให้เหมาะกับงาน เพื่อให้แอปของคุณทำงานลื่นไหลและประหยัดทรัพยากรเซิร์ฟเวอร์

ที่มา: DEV Community

10 hours ago 10 นาที
5 views