SQL ของคุณเร็ว แต่ API กลับช้า

คิวรีฐานข้อมูลของคุณทำงานได้รวดเร็ว ระบบแคชทำงานได้ดี งานเบื้องหลัง (background jobs) ก็ไม่มีปัญหา

แต่ API ของคุณยังคงช้า และการใช้งาน CPU ก็สูงมาก

ปัญหาไม่ได้อยู่ที่ฐานข้อมูล แต่อยู่ที่เลเยอร์ของ Ruby

คอขวดมักเกิดขึ้นหลังจากที่ข้อมูลออกจากฐานข้อมูลและเข้าสู่แอปพลิเคชันของคุณ ซึ่งเกิดจากปัญหาหลัก 3 ประการ:

  • การทำ Serialization ที่บวมเกินความจำเป็น (Bloated serialization)
  • การสร้าง Object มากเกินไป (Excessive object allocation)
  • การคำนวณซ้ำซ้อน (Repeated computation)

นี่คือวิธีแก้ไขปัญหาเหล่านั้น

1. หยุดการทำ Serialization ที่บวมเกินความจำเป็น

นักพัฒนาหลายคนเปลี่ยน Model ทั้งหมดให้เป็น JSON

render json: @shipments

หาก shipment หนึ่งมี 40 คอลัมน์ แต่ frontend ของคุณต้องการเพียง 5 คอลัมน์ คุณกำลังเสียรอบการทำงานของ CPU ไปโดยเปล่าประโยชน์ นอกจากนี้ยังเสี่ยงต่อการทำข้อมูลส่วนตัวรั่วไหล เช่น API keys หรือต้นทุนต่างๆ

วิธีแก้ไข: ส่งคืนเฉพาะฟิลด์ที่คุณต้องการเท่านั้น

render json: @shipments.as_json(only: [:id, :tracking_no, :status])

เพื่อความเร็วที่ดียิ่งขึ้น ให้ใช้ pluck เพื่อดึงข้อมูลออกมาเป็น array ซึ่งจะช่วยหลีกเลี่ยงการสร้าง ActiveRecord objects ที่หนักเครื่องได้โดยสิ้นเชิง

2. ลดการสร้าง Object (Object Allocation)

ทุกๆ Object ที่ Ruby สร้างขึ้นต้องใช้หน่วยความจำ การสร้าง Object นับพันรายการในหนึ่ง request จะทำให้ Garbage Collector (GC) ต้องทำงานหนักขึ้น ซึ่งจะทำให้ระบบทั้งหมดของคุณช้าลง

หลีกเลี่ยงการสร้าง hash หรือ string ใหม่ภายใน loop

แบบที่ไม่ดี:

@shipments.map do |s|
  { label: "#{s.tracking_no} - #{s.status.upcase}" }
end

แบบที่ดี: ย้ายข้อมูลที่เป็นค่าคงที่ (static data) ออกไปนอก loop และพยายามจัดการข้อมูลในฐานข้อมูลให้มากขึ้นแทนที่จะทำใน Ruby

3. หลีกเลี่ยงการคำนวณซ้ำซ้อน

หากคุณเรียกใช้ method เดิมซ้ำหลายครั้งในหนึ่ง request คุณกำลังเสียเวลาไปโดยเปล่าประโยชน์

ตัวอย่าง:

def total_weight
  shipments.sum(&:weight)
end

หากทั้ง view, helper และ serializer ของคุณเรียกใช้ method นี้ทั้งหมด คุณจะทำการคำนวณผลรวมถึงสามครั้ง

วิธีแก้ไข: ใช้ memoization

def total_weight
  @total_weight ||= shipments.sum(&:weight)
end

วิธีนี้จะช่วยให้มั่นใจว่าการคำนวณจะเกิดขึ้นเพียงครั้งเดียวต่อหนึ่ง request เท่านั้น

ตารางสรุป:

  • การทำ Serialization ที่บวมเกินไป: ส่งคืนเฉพาะฟิลด์ที่จำเป็นหรือใช้ pluck
  • การสร้าง Object มากเกินไป: สร้าง Object ให้น้อยลงภายใน loop
  • การคำนวณซ้ำซ้อน: ใช้ memoization เพื่อนำผลลัพธ์กลับมาใช้ใหม่

การปรับแต่งฐานข้อมูล (Database optimization) คือการขอข้อมูลให้น้อยลง ส่วนการปรับแต่งแอปพลิเคชัน (Application optimization) คือการลดภาระงานที่ไม่จำเป็นหลังจากที่คุณได้รับข้อมูลมาแล้ว

Source: https://dev.to/danewu/your-sql-is-fast-but-the-api-is-slow-its-the-ruby-layer-2fno