เพิ่มความเร็ว Laravel สำหรับ High-Traffic Database Optimization ที่นักพัฒนา 90% ยังไม่รู้

โดย CyberMAN


Laravel Performance Series · EP.1

เพิ่มความเร็ว Laravel สำหรับ High-Traffic
Database Optimization ที่นักพัฒนา 90% ยังไม่รู้

ตั้งแต่ N+1 Query, Eager Loading, Indexing ไปจนถึง Route/Config Caching — ทุก Technique ในบทความเดียว

อ่านประมาณ 8 นาทีLaravel 10 / 11ระดับ Beginner → Intermediate
📚 PHP Code Mania — Laravel Optimization Series

ทำไม Laravel ถึง "คิดนาน" เกินไป?

ลองนึกภาพว่าคุณเปิดร้านอาหาร แต่ทุกครั้งที่ลูกค้าสั่งข้าว พ่อครัวต้องไปอ่านสูตรอาหารใหม่ทุกครั้ง นั่นคือสิ่งที่เกิดขึ้นกับ Laravel ที่ไม่ได้ optimize — Framework กำลังทำงานซ้ำๆ ที่ไม่จำเป็นในทุก Request

ปัญหาหลักที่ทำให้ Laravel Application ช้าในสภาพแวดล้อม Production มักจะมาจาก 3 แหล่ง ได้แก่ N+1 Query Problem, Missing Database Index, และ Framework overhead ที่ไม่จำเป็น บทความนี้จะพาไปแก้ทีละจุด พร้อม Code Example ที่ใช้ได้จริง

💡

เหมาะสำหรับใคร? นักพัฒนาที่ใช้ Laravel (หรือ CodeIgniter 4) ที่เริ่มพบปัญหา Application ช้าเมื่อมี User มากขึ้น หรืออยากเตรียมรับมือตั้งแต่ต้น


1. ปัญหา N+1 Query — ศัตรูเงียบของ Database

N+1 Problem คือปัญหาที่พบบ่อยที่สุดใน ORM ทุกตัว รวมถึง Laravel Eloquent เกิดขึ้นเมื่อโค้ดดึงข้อมูล Parent แล้ว Loop ดึง Child ทีละรายการ

❌ โค้ดที่มีปัญหา (N+1)

PHP · Eloquent (Bad)
// ดึง Posts ทั้งหมด → 1 Query
$posts = Post::all();

foreach ($posts as $post) {
    // ดึง Author ของแต่ละ Post → N Queries!
    echo $post->author->name;
}
// ถ้ามี 100 Posts = 1 + 100 = 101 Queries 🔥

✅ แก้ด้วย Eager Loading

PHP · Eloquent (Good)
// with() โหลด Author พร้อมกันใน 1 Query เพิ่ม
$posts = Post::with('author')->get();

foreach ($posts as $post) {
    echo $post->author->name;
}
// 100 Posts = 2 Queries เท่านั้น ✅

✅ Nested Eager Loading (Relationship ซ้อนกัน)

PHP · Eloquent
// โหลด author และ comments ของแต่ละ post พร้อมกัน
$posts = Post::with(['author', 'comments.user'])->get();

// เลือกเฉพาะ Column ที่ต้องการ เพื่อลด Memory
$posts = Post::with([
    'author:id,name,avatar',
    'comments' => fn($q) => $q->latest()->limit(5)
])->paginate(20);
🛠️

ติดตั้ง barryvdh/laravel-debugbar หรือ spatie/laravel-query-detector เพื่อจับ N+1 Query ใน Development โดยอัตโนมัติ


2. Database Indexing — ทำ Query ให้เร็วขึ้น 100x

Index คือ "สารบัญ" ของตาราง Database ถ้าไม่มี Index MySQL ต้องสแกนทุก Row เพื่อหาข้อมูลที่ต้องการ (Full Table Scan) ซึ่งช้ามากเมื่อข้อมูลมีหลายล้านแถว

เพิ่ม Index ผ่าน Laravel Migration

PHP · Migration
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained();
    $table->string('slug')->unique();   // Unique Index
    $table->string('status');
    $table->timestamp('published_at')->nullable();
    $table->timestamps();

    // Composite Index — Query บ่อยด้วย 2 Column นี้พร้อมกัน
    $table->index(['status', 'published_at']);

    // Index สำหรับ Foreign Key
    $table->index('user_id');
});

เพิ่ม Index ให้ตารางที่มีอยู่แล้ว

PHP · Migration (Add Index)
Schema::table('posts', function (Blueprint $table) {
    $table->index('user_id', 'idx_posts_user_id');
    $table->index(['status', 'published_at'], 'idx_posts_status_date');
});
⚠️

ใส่ Index มากเกินไปก็ไม่ดี เพราะ INSERT/UPDATE จะช้าลง ใส่เฉพาะ Column ที่ใช้ใน WHERE, ORDER BY, JOIN เท่านั้น


3. Select เฉพาะ Column ที่ต้องการ — อย่าดึง * ทุกครั้ง

การใช้ Post::all() หรือ select * จะดึงข้อมูลทุก Column แม้แต่ Column ที่หนัก เช่น content หรือ metadata ที่ไม่ได้ใช้

PHP · Eloquent
// ❌ ดึงข้อมูลทั้งหมด รวมถึง Column ที่ไม่ใช้
$posts = Post::all();

// ✅ เลือกเฉพาะที่ต้องการ
$posts = Post::select('id', 'title', 'slug', 'published_at')
    ->where('status', 'published')
    ->latest('published_at')
    ->paginate(15);

// ✅ ใช้ cursor() สำหรับ Large Dataset แทน get()
Post::select('id', 'title')->cursor()->each(function ($post) {
    // Process ทีละ Row ไม่โหลด Memory ทั้งหมดพร้อมกัน
    processPost($post);
});

4. Route & Config Cache — เคล็ดลับ 30ms ที่คนมองข้าม

โดย Default ทุกครั้งที่มี HTTP Request เข้ามา Laravel จะทำการ compile Regex Pattern ของทุก Route และสร้าง Routing Table ใน Memory ใหม่ทุกครั้ง ถ้ามี 200 Routes และ Request 1 ล้านครั้งต่อวัน นั่นหมายถึง CPU เสียไปกว่า 8 ชั่วโมง เพียงเพื่อหาว่าต้องเรียก Controller ไหน

~30ms
Routing Overhead (ไม่มี Cache)
~2ms
Routing Overhead (มี Cache)
15×
เร็วขึ้นด้วย Command เดียว

คำสั่ง Optimize สำหรับ Production

Shell · Artisan
# คำสั่งเดียวที่ครอบคลุม "Big Four" Cache
php artisan optimize

# เทียบเท่ากับการรันพร้อมกัน 4 คำสั่งนี้:
# php artisan config:cache
# php artisan route:cache
# php artisan view:cache
# php artisan event:cache

# ล้าง Cache ทั้งหมดก่อน (สำคัญมาก!)
php artisan optimize:clear

ตัวอย่าง Deployment Script (CI/CD)

Shell · Deploy Script
#!/bin/bash
# deploy.sh — ใช้ใน GitHub Actions / GitLab CI

composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan optimize:clear   # ล้างก่อนเสมอ
php artisan optimize         # Cache ใหม่
php artisan storage:link
🚨

อย่าลืม optimize:clear ก่อน Deploy ทุกครั้ง ถ้า Cache Route เก่าค้างอยู่ Route ใหม่จะไม่ทำงาน จนกว่าจะ Clear Cache


5. Query Caching ด้วย Laravel Cache

สำหรับข้อมูลที่ไม่เปลี่ยนบ่อย เช่น Category, Setting, หรือ Dashboard Stats การ Cache ผลลัพธ์ Query ไว้ใน Redis/Memcached จะลด Database Load ได้มหาศาล

PHP · Cache with Eloquent
use Illuminate\Support\Facades\Cache;

// Cache ผลลัพธ์ไว้ 60 นาที
$categories = Cache::remember('all_categories', 3600, function () {
    return Category::with('children')
        ->where('active', true)
        ->orderBy('sort_order')
        ->get();
});

// ล้าง Cache เมื่อข้อมูลเปลี่ยน (ใน Observer หรือ Service)
Cache::forget('all_categories');

// หรือใช้ Cache Tags (Redis เท่านั้น)
$posts = Cache::tags(['posts', 'published'])
    ->remember('homepage_posts', 1800, function () {
        return Post::published()->latest()->take(10)->get();
    });

6. สรุปเปรียบเทียบ: ก่อน vs หลัง Optimize

Techniqueก่อน Optimizeหลัง Optimizeผลลัพธ์
N+1 Query101 Queries (100 posts)2 Queries-98%
Route Cache~30ms overhead/request~2ms overhead/request15× เร็วขึ้น
Missing IndexFull Table ScanIndex Seekสูงสุด 100×
Select *โหลดทุก Columnเฉพาะที่ต้องการลด Memory
Query CacheDB Hit ทุก RequestRedis/Memory Hitลด DB Load 90%+
ไม่มีอะไรเลยApp ช้า, DB เจ๊งต้องแก้ด่วน!

7. Production Checklist ก่อน Go-Live

  • 01รัน php artisan telescope:install หรือ DebugBar ใน Dev เพื่อหา N+1 Queries
  • 02ตรวจ Migration ทุกตัร — มี Index บน Column ที่ใช้ใน WHERE และ JOIN ครบหรือยัง
  • 03เปลี่ยน APP_ENV=production และ APP_DEBUG=false ใน .env
  • 04รัน php artisan optimize ใน Deployment Script เสมอ
  • 05ตั้งค่า Redis เป็น Cache Driver และใช้ Cache::remember() สำหรับ Query ที่ดึงบ่อย
  • 06ใช้ paginate() แทน get() เสมอสำหรับ List ข้อมูล

สรุป

การ Optimize Laravel Application ไม่ได้ซับซ้อนอย่างที่คิด เพียงแค่เข้าใจต้นเหตุของปัญหา และใช้ Tool ที่ Framework มีให้อยู่แล้ว Techniques ที่พูดถึงในบทความนี้สามารถ ลด Query จาก 100+ เหลือ 2, ลด Routing Overhead ลง 15 เท่า และลด Database Load ได้มากกว่า 90%

จุดเริ่มต้นที่ดีที่สุดคือ หา N+1 Query ก่อน ติดตั้ง DebugBar รัน Application แล้วดูว่ามี Query ซ้ำกี่ตัว จากนั้นค่อยๆ ใส่ with(), เพิ่ม Index, และสุดท้ายรัน php artisan optimize ก่อน Deploy

แค่นี้ Application ของคุณก็พร้อมรับ Traffic หนักๆ ได้แล้ว 🚀



PHP CI MANIA - PHP Code Generator 

โปรแกรมช่วยสร้างโค้ด "ลดเวลาการเขียนโปรแกรม"
ราคาสุดคุ้ม  
http://www.phpcodemania.com