คอลัมน์ที่ไม่มี Index ทำฐานข้อมูลของเราพังได้อย่างไร

แค่ลืมสร้าง Index เพียงจุดเดียว ก็เปลี่ยน API ที่เร็ว 8 ms ให้กลายเป็นฝันร้ายนาน 8 วินาที

เราทำการทดสอบ Load Test ก่อนการปล่อยเวอร์ชันสำคัญ ในเครื่อง Local development ข้อมูล 100 แถวทำงานได้ไม่มีปัญหา แต่ใน Staging เราจำลองผู้ใช้งาน 500 คน กับข้อมูล 1.5 ล้านแถว

ทุกอย่างพังพินาศ

  • เวลาตอบสนองของ API พุ่งจาก 45 ms เป็น 8,000 ms
  • CPU ของฐานข้อมูลพุ่งสูงถึง 100 %
  • Connection pools เต็มจนไม่สามารถใช้งานได้
  • Request ต่างๆ เกิด Timeout

ตัวการคือ Query ง่ายๆ ที่ดึงประวัติการสั่งซื้อโดยใช้ user_id และ status

เมื่อรัน EXPLAIN ANALYZE บน PostgreSQL พบว่าเป็นแบบ Sequential Scan เนื่องจากไม่มี Index บน user_id ทำให้ Engine ต้องอ่านข้อมูลทั้งหมด 1.5 ล้านแถวในทุกๆ Request

เมื่อมี 100 concurrent requests ฐานข้อมูลต้องสแกนข้อมูลถึง 150 ล้านแถวพร้อมกัน

การแก้ไขใช้เวลาเพียง 5 นาที เราสร้าง composite index บน (user_id, status, created_at DESC) โดยใช้ CREATE INDEX CONCURRENTLY เพื่อให้ตารางยังคงออนไลน์ใช้งานได้ตามปกติ

ผลลัพธ์:

  • เวลาในการ Query ลดลงจาก 8,150 ms เหลือเพียง 0.14 ms
  • API latency ลดลงจาก 8 วินาที เหลือเพียง 12 ms
  • การใช้งาน CPU ลดลงจาก 100 % เหลือไม่ถึง 8 %

บทเรียนที่ได้รับ:

  • การทดสอบบน Local อาจทำให้เข้าใจผิด ข้อมูล 100 แถวไม่ใช่ตัวแทนของข้อมูลหลักล้าน
  • อย่าลืมทำ Index ให้กับ Foreign keys เพราะ ORM ส่วนใหญ่มักจะข้ามขั้นตอนนี้ไป
  • ให้รัน EXPLAIN ANALYZE เพราะฐานข้อมูลจะชี้ให้เห็นถึงปัญหาเอง
  • ปรับแต่ง Query ให้เหมาะสมก่อนที่จะคิดซื้อ Hardware เพิ่ม

อย่าเพิ่งรีบ Scale server ให้เริ่มจากการ Scale query ก่อน

ที่มา: https://dev.to/mia_keller_ffd2584c046ecb/how-an-unindexed-column-silently-killed-our-database-under-load-and-the-5-minute-fix-m32