การตามล่า N+1 และการกำจัด Query ที่สิ้นเปลืองใน Laravel
งานด้านประสิทธิภาพมักจะเป็นเรื่องที่น่าเบื่อ
มันไม่ใช่เรื่องราวของฮีโร่ที่ลด Latency ได้ถึง 80% เสมอไป ส่วนใหญ่แล้วมันคือการทำความสะอาด คุณจะพบ Query ที่คุณไม่รู้ตัวว่ากำลังรันอยู่ แล้วคุณก็ลบมันทิ้งไป
หากแอป Laravel ของคุณกำลังเติบโต คุณก็น่าจะเจอปัญหาเหล่านี้ 3 อย่าง
1. Hidden N+1 Queries
คุณไม่สามารถแก้ไขสิ่งที่คุณมองไม่เห็นได้ ให้ใช้เครื่องมืออย่าง beyondcode/laravel-query-detector ในสภาพแวดล้อม Local ของคุณ มันจะบอกคุณเมื่อคุณใช้รูปแบบ loop-and-lazy-load
อย่ารันสิ่งนี้ใน Production เพราะมันจะเพิ่ม overhead ให้ลงทะเบียนใช้งานเฉพาะเมื่อแอปอยู่ในโหมด local หรือ testing เท่านั้น
2. การสิ้นเปลืองจากการทำ Eager Loading
คนส่วนใหญ่มักแก้ปัญหา N+1 ด้วยการเพิ่ม with() แต่นี่กลับสร้างปัญหาใหม่ คุณอาจจะทำ eager-load ความสัมพันธ์ที่ View ไม่ได้ใช้งานแล้ว
หากการออกแบบใหม่มีการลบคอลัมน์ออกจากตาราง คุณต้องลบการเรียกใช้ with() ที่เกี่ยวข้องใน Controller ออกด้วย มิฉะนั้นคุณกำลังเสียทรัพยากรไปกับข้อมูลที่คุณไม่ได้ใช้
ให้มองว่าการ eager load ทุกครั้งคือการร้องขอข้อมูล หากคุณไม่ได้ใช้ข้อมูลนั้น ก็ให้ยกเลิกการร้องขอนั้นเสีย
3. การคำนวณข้อมูลเดิมซ้ำๆ
ค่าบางอย่างจะคงที่ตลอดทั้ง Request หากคุณคำนวณมันซ้ำหลายครั้ง คุณกำลังสิ้นเปลือง CPU ให้ใช้ memoization เพื่อแก้ปัญหานี้
ตัวอย่าง:
protected ?string $defaultConnection = null;
public function getDefaultConnectionName(): string
{
return $this->defaultConnection ??= $this->resolveDefaultConnection();
}
ตัวดำเนินการ ??= จะคำนวณค่าเพียงครั้งเดียวและนำกลับมาใช้ใหม่ตลอดช่วงเวลาที่เหลือของ Request
4. กับดัก Query ใน Dashboard
Dashboard มักจะรัน Query count() แยกกันสำหรับแต่ละ Card หากคุณมี 6 Card คุณก็ต้องรันถึง 6 Query
ให้ลองทำสองสิ่งนี้แทน:
- รวมการนับ (Group your counts): ดึงยอดรวมทั้งหมดของตารางเดียวด้วย Query เดียว
- ใช้ Cache ระยะสั้น: สถิติบน Dashboard ไม่จำเป็นต้องเป็นข้อมูลแบบวินาทีต่อวินาที ให้ Cache ไว้สัก 5 นาที
ผู้เข้าชมเพียงคนเดียวจะเป็นผู้แบกรับภาระของ Query ส่วนคนอื่นๆ จะได้รับผลลัพธ์จาก Cache แทน
เคล็ดลับสุดท้าย: การป้องกันการเกิดปัญหาซ้ำ (Prevent Regressions)
การทำความสะอาดจะล้มเหลวหากนักพัฒนาเผลอเพิ่มโค้ดที่แย่กลับเข้ามาใหม่ในภายหลัง ให้ใช้ Pest เพื่อตรวจสอบ (assert) จำนวน Query ของคุณ โดยกำหนดเพดานสูงสุดว่าหนึ่งหน้าสามารถรัน Query ได้กี่ครั้ง หากนักพัฒนาเพิ่ม N+1 เข้ามา การทดสอบจะล้มเหลวทันที
การทำ Optimization ส่วนใหญ่คือการตัดสิ่งที่ไม่จำเป็นออก ลบการโหลดที่ไม่ใช้ ลบการคำนวณซ้ำ และลบ Query ที่ไม่จำเป็นออกไป
