การตามล่า 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 ที่ไม่จำเป็นออกไป

Source: https://dev.to/nasrulhazim/a-day-of-performance-hardening-hunting-n1s-and-killing-wasted-queries-in-laravel-568p