SiamCafe.net Blog
Technology

WordPress Block Theme Load Testing Strategy — ทดสอบประสิทธิภาพ WordPress ด้วย k6 และ Locust

wordpress block theme load testing strategy
WordPress Block Theme Load Testing Strategy | SiamCafe Blog
2025-12-01· อ. บอม — SiamCafe.net· 1,208 คำ

ทำไมต้อง Load Test WordPress Block Theme

WordPress Block Theme (Full Site Editing) มีสถาปัตยกรรมต่างจาก Classic Theme ตรงที่ render content ผ่าน block parser แทน PHP templates ใช้ theme.json สำหรับ styling ที่ generate CSS runtime และมี REST API calls เพิ่มเติมสำหรับ block patterns การเปลี่ยนแปลงเหล่านี้อาจส่งผลต่อ performance ที่ต่างจาก Classic Theme

Load Testing ช่วยให้รู้ว่า WordPress site รองรับ concurrent users ได้กี่คน, response time เป็นอย่างไรเมื่อ traffic สูง, จุดคอขวด (bottleneck) อยู่ที่ไหน (PHP, MySQL, disk I/O, memory) และ caching strategy ที่ใช้มีประสิทธิภาพเพียงพอหรือไม่

สำหรับ Block Theme ที่ใช้ Full Site Editing ต้อง test เพิ่มเติมในส่วนของ Block rendering performance, Global styles (theme.json) processing, Template parts loading, Block patterns REST API และ Site Editor (admin) performance

การทำ Load Test เป็นประจำ (ทุกครั้งที่ update theme, plugins หรือ WordPress core) ช่วยป้องกัน performance regression และมั่นใจว่า site พร้อมรับ traffic ได้ตลอดเวลา

เครื่องมือสำหรับ Load Testing WordPress

เปรียบเทียบเครื่องมือที่เหมาะกับ WordPress

# === เครื่องมือ Load Testing ===
#
# 1. k6 (Grafana)
#    - เขียน test ด้วย JavaScript
#    - Performance ดีมาก (Go-based)
#    - Built-in metrics และ thresholds
#    - Cloud service สำหรับ distributed testing
#    - ติดตั้ง: brew install k6 / choco install k6
#
# 2. Locust (Python)
#    - เขียน test ด้วย Python
#    - Web UI สำหรับ real-time monitoring
#    - Distributed testing ง่าย
#    - เหมาะสำหรับ Python developers
#    - ติดตั้ง: pip install locust
#
# 3. Apache JMeter
#    - GUI-based, เหมาะสำหรับผู้เริ่มต้น
#    - รองรับ protocols หลากหลาย
#    - Plugins ecosystem ใหญ่
#    - ใช้ resources มาก (Java-based)
#
# 4. Artillery
#    - YAML-based configuration
#    - Node.js based
#    - Cloud integration
#    - ติดตั้ง: npm install -g artillery
#
# 5. WP CLI + ab (Apache Bench)
#    - Simple, built-in ใน most servers
#    - เหมาะสำหรับ quick tests
#    - ab -n 1000 -c 50 https://example.com/
#
# === WordPress-specific Test Scenarios ===
#
# 1. Homepage load (cached vs uncached)
# 2. Single post/page with blocks
# 3. Archive pages (category, tag, date)
# 4. Search functionality
# 5. WooCommerce product pages (if applicable)
# 6. REST API endpoints (/wp-json/wp/v2/posts)
# 7. Admin dashboard (wp-admin)
# 8. Login/authentication flow
# 9. Comment submission
# 10. Media uploads
#
# === Test Types ===
# Smoke test: 1-5 users, verify system works
# Load test: Expected traffic (e.g., 100 users)
# Stress test: Beyond expected (e.g., 500 users)
# Spike test: Sudden traffic burst (0 -> 1000 users)
# Soak test: Sustained load for hours (memory leaks)

# ติดตั้ง k6
# Windows: choco install k6
# macOS: brew install k6
# Linux: sudo apt install k6
# Docker: docker run --rm -i grafana/k6 run -

สร้าง Load Test Scripts ด้วย k6

k6 scripts สำหรับ WordPress Block Theme

// wordpress_load_test.js — k6 Load Test for WordPress Block Theme
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';

// Custom metrics
const errorRate = new Rate('errors');
const pageLoadTime = new Trend('page_load_time');
const ttfb = new Trend('time_to_first_byte');
const cacheHitRate = new Rate('cache_hits');

// Test configuration
export const options = {
  stages: [
    { duration: '1m', target: 10 },   // Ramp up
    { duration: '3m', target: 50 },   // Sustained load
    { duration: '2m', target: 100 },  // Peak load
    { duration: '1m', target: 50 },   // Scale down
    { duration: '1m', target: 0 },    // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<2000'],  // 95% requests < 2s
    http_req_failed: ['rate<0.05'],     // Error rate < 5%
    errors: ['rate<0.1'],
    time_to_first_byte: ['p(95)<500'],  // TTFB < 500ms
  },
};

const BASE_URL = __ENV.BASE_URL || 'https://example.com';

export default function() {
  // Scenario 1: Homepage
  group('Homepage', function() {
    const res = http.get(`/`, {
      headers: { 'Accept-Encoding': 'gzip, deflate, br' },
    });
    
    check(res, {
      'status is 200': (r) => r.status === 200,
      'has block theme markup': (r) => r.body.includes('wp-block'),
      'response time < 2s': (r) => r.timings.duration < 2000,
    });
    
    errorRate.add(res.status !== 200);
    pageLoadTime.add(res.timings.duration);
    ttfb.add(res.timings.waiting);
    cacheHitRate.add(res.headers['X-Cache'] === 'HIT');
  });

  sleep(1);

  // Scenario 2: Blog post with blocks
  group('Blog Post', function() {
    const res = http.get(`/sample-post/`);
    
    check(res, {
      'status is 200': (r) => r.status === 200,
      'has content blocks': (r) => r.body.includes('wp-block-'),
      'has schema markup': (r) => r.body.includes('application/ld+json'),
    });
    
    errorRate.add(res.status !== 200);
    pageLoadTime.add(res.timings.duration);
  });

  sleep(1);

  // Scenario 3: REST API
  group('REST API', function() {
    const res = http.get(`/wp-json/wp/v2/posts?per_page=10`, {
      headers: { 'Accept': 'application/json' },
    });
    
    check(res, {
      'api status 200': (r) => r.status === 200,
      'returns JSON': (r) => r.headers['Content-Type'].includes('application/json'),
      'has posts': (r) => JSON.parse(r.body).length > 0,
    });
    
    errorRate.add(res.status !== 200);
  });

  sleep(1);

  // Scenario 4: Search
  group('Search', function() {
    const res = http.get(`/?s=wordpress+block+theme`);
    
    check(res, {
      'search returns 200': (r) => r.status === 200,
    });
    
    errorRate.add(res.status !== 200);
    pageLoadTime.add(res.timings.duration);
  });

  sleep(Math.random() * 3);
}

// Run: k6 run --env BASE_URL=https://example.com wordpress_load_test.js
// With HTML report: k6 run --out json=results.json wordpress_load_test.js

Load Testing ด้วย Locust Python

Locust scripts สำหรับ advanced WordPress testing

#!/usr/bin/env python3
# locustfile.py — WordPress Block Theme Load Test with Locust
from locust import HttpUser, task, between, events
from locust.runners import MasterRunner
import random
import logging
import json
from datetime import datetime

logger = logging.getLogger("wp_loadtest")

class WordPressUser(HttpUser):
    wait_time = between(1, 5)
    
    def on_start(self):
        self.posts = []
        self.categories = []
        self._fetch_content_list()
    
    def _fetch_content_list(self):
        try:
            resp = self.client.get("/wp-json/wp/v2/posts?per_page=20", name="/api/posts")
            if resp.status_code == 200:
                self.posts = [p["link"] for p in resp.json()]
            
            resp = self.client.get("/wp-json/wp/v2/categories?per_page=10", name="/api/categories")
            if resp.status_code == 200:
                self.categories = [c["link"] for c in resp.json()]
        except Exception as e:
            logger.warning(f"Failed to fetch content: {e}")
    
    @task(30)
    def view_homepage(self):
        with self.client.get("/", name="Homepage", catch_response=True) as resp:
            if resp.status_code == 200:
                if "wp-block" not in resp.text:
                    resp.failure("Missing block theme markup")
                else:
                    resp.success()
            else:
                resp.failure(f"Status {resp.status_code}")
    
    @task(25)
    def view_blog_post(self):
        if not self.posts:
            return
        url = random.choice(self.posts)
        path = url.replace(self.host, "")
        
        with self.client.get(path, name="Blog Post", catch_response=True) as resp:
            if resp.status_code == 200:
                resp.success()
            elif resp.status_code == 404:
                resp.failure("Post not found")
    
    @task(15)
    def view_category(self):
        if not self.categories:
            return
        url = random.choice(self.categories)
        path = url.replace(self.host, "")
        self.client.get(path, name="Category Archive")
    
    @task(10)
    def search(self):
        queries = ["wordpress", "theme", "plugin", "security", "performance"]
        query = random.choice(queries)
        self.client.get(f"/?s={query}", name="Search")
    
    @task(10)
    def api_posts(self):
        page = random.randint(1, 5)
        self.client.get(
            f"/wp-json/wp/v2/posts?per_page=10&page={page}",
            name="/api/posts?page=N"
        )
    
    @task(5)
    def view_sitemap(self):
        self.client.get("/sitemap.xml", name="Sitemap")
    
    @task(3)
    def view_feed(self):
        self.client.get("/feed/", name="RSS Feed")
    
    @task(2)
    def static_assets(self):
        self.client.get("/wp-content/themes/theme/style.css", name="Theme CSS")

class AdminUser(HttpUser):
    wait_time = between(3, 10)
    weight = 1  # 1% of users
    
    def on_start(self):
        self.client.post("/wp-login.php", data={
            "log": "admin",
            "pwd": "admin_password",
            "wp-submit": "Log In",
            "redirect_to": "/wp-admin/",
        }, name="Login")
    
    @task
    def view_dashboard(self):
        self.client.get("/wp-admin/", name="Admin Dashboard")
    
    @task
    def edit_post(self):
        self.client.get("/wp-admin/post.php?post=1&action=edit", name="Edit Post")

# Custom event handlers
@events.test_start.add_listener
def on_test_start(environment, **kwargs):
    logger.info(f"Load test started at {datetime.now()}")

@events.request.add_listener
def on_request(request_type, name, response_time, response_length, **kwargs):
    if response_time > 5000:
        logger.warning(f"Slow request: {name} took {response_time}ms")

# Run: locust -f locustfile.py --host=https://example.com
# Web UI: http://localhost:8089
# Headless: locust -f locustfile.py --host=https://example.com --headless -u 100 -r 10 --run-time 10m

วิเคราะห์ผลและ Optimize Performance

วิเคราะห์ผลการ test และ optimization

#!/usr/bin/env python3
# analyze_results.py — Load Test Results Analyzer
import json
import statistics
from pathlib import Path
from datetime import datetime

class LoadTestAnalyzer:
    def __init__(self, results_file):
        self.data = self._load_results(results_file)
    
    def _load_results(self, filepath):
        results = []
        with open(filepath) as f:
            for line in f:
                try:
                    results.append(json.loads(line))
                except json.JSONDecodeError:
                    continue
        return results
    
    def analyze(self):
        http_reqs = [d for d in self.data if d.get("type") == "Point" and d.get("metric") == "http_req_duration"]
        
        if not http_reqs:
            print("No HTTP request data found")
            return
        
        durations = [d["data"]["value"] for d in http_reqs]
        
        report = {
            "total_requests": len(durations),
            "avg_response_ms": round(statistics.mean(durations), 2),
            "median_response_ms": round(statistics.median(durations), 2),
            "p95_response_ms": round(sorted(durations)[int(len(durations) * 0.95)], 2),
            "p99_response_ms": round(sorted(durations)[int(len(durations) * 0.99)], 2),
            "min_response_ms": round(min(durations), 2),
            "max_response_ms": round(max(durations), 2),
        }
        
        print(f"\n{'='*50}")
        print(f"Load Test Analysis — {datetime.now().strftime('%Y-%m-%d %H:%M')}")
        print(f"{'='*50}")
        for key, value in report.items():
            print(f"  {key}: {value}")
        
        # Performance recommendations
        print(f"\n--- Recommendations ---")
        if report["p95_response_ms"] > 2000:
            print("  [CRITICAL] P95 > 2s: Enable page caching (WP Super Cache, W3 Total Cache)")
        if report["avg_response_ms"] > 500:
            print("  [WARNING] Avg > 500ms: Consider object caching (Redis/Memcached)")
        if report["p99_response_ms"] > 5000:
            print("  [CRITICAL] P99 > 5s: Check database queries, enable query caching")
        
        return report

# WordPress Performance Optimization Checklist:
#
# 1. Page Caching:
#    wp plugin install wp-super-cache --activate
#    หรือใช้ Nginx FastCGI cache
#
# 2. Object Caching:
#    wp plugin install redis-cache --activate
#    wp redis enable
#
# 3. Database Optimization:
#    wp db optimize
#    wp transient delete --all
#
# 4. Image Optimization:
#    wp plugin install imagify --activate
#    Convert to WebP format
#
# 5. CDN:
#    CloudFlare, BunnyCDN, AWS CloudFront
#
# 6. PHP Optimization:
#    PHP 8.2+ with OPcache enabled
#    opcache.memory_consumption=256
#    opcache.max_accelerated_files=20000
#
# 7. Block Theme Specific:
#    Minimize custom blocks
#    Lazy load block assets
#    Reduce template parts nesting
#    Optimize theme.json (remove unused styles)
#
# 8. Server:
#    Nginx > Apache for WordPress
#    HTTP/2 or HTTP/3 enabled
#    Gzip/Brotli compression
#    Keep-alive connections

analyzer = LoadTestAnalyzer("results.json")
analyzer.analyze()

CI/CD Integration สำหรับ Performance Testing

รวม load testing เข้ากับ deployment pipeline

# .github/workflows/performance-test.yml
name: WordPress Performance Test

on:
  push:
    branches: [main]
    paths:
      - 'wp-content/themes/**'
      - 'wp-content/plugins/**'
  schedule:
    - cron: '0 3 * * 1'  # Weekly Monday 03:00

jobs:
  load-test:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: rootpass
          MYSQL_DATABASE: wordpress
        ports: ["3306:3306"]
      
      redis:
        image: redis:7-alpine
        ports: ["6379:6379"]

    steps:
      - uses: actions/checkout@v4
      
      - name: Setup WordPress
        run: |
          docker run -d --name wordpress \
            --network host \
            -e WORDPRESS_DB_HOST=127.0.0.1 \
            -e WORDPRESS_DB_USER=root \
            -e WORDPRESS_DB_PASSWORD=rootpass \
            -e WORDPRESS_DB_NAME=wordpress \
            -p 8080:80 \
            wordpress:latest
          
          sleep 30
          
          # Install WP CLI and setup
          docker exec wordpress bash -c "
            curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
            chmod +x wp-cli.phar
            php wp-cli.phar core install \
              --url=http://localhost:8080 \
              --title='Test Site' \
              --admin_user=admin \
              --admin_password=admin123 \
              --admin_email=test@test.com \
              --allow-root
          "
      
      - name: Install k6
        run: |
          sudo gpg -k
          sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
          echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
          sudo apt-get update
          sudo apt-get install k6
      
      - name: Run Load Test
        run: |
          k6 run \
            --env BASE_URL=http://localhost:8080 \
            --out json=results.json \
            --summary-export=summary.json \
            tests/wordpress_load_test.js
      
      - name: Check Thresholds
        run: |
          python3 -c "
          import json
          with open('summary.json') as f:
              s = json.load(f)
          
          p95 = s['metrics']['http_req_duration']['values']['p(95)']
          err_rate = s['metrics']['http_req_failed']['values']['rate']
          
          print(f'P95 Response Time: {p95:.0f}ms')
          print(f'Error Rate: {err_rate:.2%}')
          
          if p95 > 2000:
              print('FAIL: P95 exceeds 2000ms')
              exit(1)
          if err_rate > 0.05:
              print('FAIL: Error rate exceeds 5%')
              exit(1)
          print('PASS: All thresholds met')
          "
      
      - name: Upload Results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: load-test-results
          path: |
            results.json
            summary.json
      
      - name: Comment PR
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const summary = JSON.parse(fs.readFileSync('summary.json'));
            const p95 = summary.metrics.http_req_duration.values['p(95)'].toFixed(0);
            const avg = summary.metrics.http_req_duration.values['avg'].toFixed(0);
            
            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: `## Load Test Results\n| Metric | Value |\n|--------|-------|\n| P95 Response | ms |\n| Avg Response | ms |`
            });

FAQ คำถามที่พบบ่อย

Q: Block Theme ช้ากว่า Classic Theme จริงไหม?

A: Block Theme อาจช้ากว่าเล็กน้อยเพราะต้อง parse blocks, process theme.json และ generate CSS runtime แต่ด้วย proper caching (page cache + object cache) ความแตกต่างแทบจะไม่มี Block Theme มีข้อดีคือ frontend rendering เร็วกว่าเพราะ CSS ถูก optimize มากกว่า ลด unused CSS ได้ดีกว่า Classic Theme

Q: k6 กับ Locust เลือกอันไหน?

A: k6 เหมาะสำหรับ CI/CD integration มากกว่าเพราะ single binary, CLI-first, built-in thresholds และ performance ดีมาก (generate 10,000+ RPS จากเครื่องเดียว) Locust เหมาะสำหรับ Python developers ที่ต้องการ flexibility สูง มี Web UI ที่สะดวก สำหรับ WordPress แนะนำ k6 สำหรับ automated pipeline และ Locust สำหรับ exploratory testing

Q: WordPress รองรับ concurrent users ได้กี่คน?

A: ขึ้นอยู่กับ server specs และ optimization WordPress ไม่มี cache บน shared hosting รองรับ 10-50 concurrent users WordPress + page cache บน VPS 2 cores/4GB RAM รองรับ 200-500 concurrent users WordPress + full caching stack (Nginx + Redis + CDN) บน dedicated server รองรับ 1,000-10,000+ concurrent users

Q: ควร load test production หรือ staging?

A: ควร test staging ที่มี specs เหมือน production เสมอ การ test production โดยตรงมีความเสี่ยงทำให้ site ช้าหรือล่มสำหรับ users จริง ถ้าจำเป็นต้อง test production ให้ทำในช่วง low traffic (เช่น กลางคืน) ใช้ ramp-up ช้าๆ มี monitoring พร้อม และสามารถ stop test ได้ทันทีถ้ามีปัญหา

📖 บทความที่เกี่ยวข้อง

WordPress Block Theme Home Lab Setupอ่านบทความ → GraphQL Federation Load Testing Strategyอ่านบทความ → WordPress Block Theme Chaos Engineeringอ่านบทความ → WordPress Block Theme Community Buildingอ่านบทความ → WordPress Block Theme Kubernetes Deploymentอ่านบทความ →

📚 ดูบทความทั้งหมด →