ทำไมต้อง Load Test WooCommerce ก่อน Launch
WooCommerce เป็น e-commerce plugin บน WordPress ที่รองรับร้านค้าออนไลน์ตั้งแต่ขนาดเล็กจนถึงหลายหมื่น SKU ปัญหาที่พบบ่อยคือเว็บล่มตอนทำ flash sale หรือช่วง campaign ใหญ่ เพราะไม่เคย load test ก่อน การทำ load testing ช่วยให้รู้ว่าระบบรับ concurrent users ได้กี่คน และ bottleneck อยู่ตรงไหน ไม่ว่าจะเป็น PHP workers, database queries หรือ object cache
เตรียม Environment สำหรับ Load Test
ห้าม load test บน production โดยตรง ให้สร้าง staging environment ที่มี spec เหมือน production
# สร้าง staging server ด้วย Docker
# docker-compose.yml
version: '3.8'
services:
wordpress:
image: wordpress:6.4-php8.2-apache
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_USER: woo_user
WORDPRESS_DB_PASSWORD: SecureDBPass123!
WORDPRESS_DB_NAME: woocommerce
WORDPRESS_CONFIG_EXTRA: |
define('WP_CACHE', true);
define('WP_MEMORY_LIMIT', '512M');
define('WP_MAX_MEMORY_LIMIT', '1024M');
define('DISABLE_WP_CRON', true);
volumes:
- wp_data:/var/www/html
- ./php-custom.ini:/usr/local/etc/php/conf.d/custom.ini
depends_on:
mysql:
condition: service_healthy
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: woocommerce
MYSQL_USER: woo_user
MYSQL_PASSWORD: SecureDBPass123!
MYSQL_ROOT_PASSWORD: RootPass123!
volumes:
- mysql_data:/var/lib/mysql
- ./my.cnf:/etc/mysql/conf.d/custom.cnf
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
ports:
- "6379:6379"
volumes:
wp_data:
mysql_data:
# php-custom.ini — ปรับ PHP สำหรับ WooCommerce
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 512M
max_execution_time = 300
max_input_vars = 5000
opcache.enable = 1
opcache.memory_consumption = 256
opcache.max_accelerated_files = 20000
opcache.revalidate_freq = 60
opcache.jit = 1255
opcache.jit_buffer_size = 128M
# my.cnf — ปรับ MySQL สำหรับ WooCommerce
[mysqld]
innodb_buffer_pool_size = 1G
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
max_connections = 200
query_cache_type = 0
tmp_table_size = 64M
max_heap_table_size = 64M
join_buffer_size = 4M
sort_buffer_size = 4M
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
สร้าง Test Data ด้วย WP-CLI
# ติดตั้ง WP-CLI ใน container
docker compose exec wordpress bash -c "
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
mv wp-cli.phar /usr/local/bin/wp
"
# ติดตั้ง WooCommerce
docker compose exec wordpress wp plugin install woocommerce --activate --allow-root
# สร้าง test products 500 ชิ้น
docker compose exec wordpress wp eval '
for ($i = 1; $i <= 500; $i++) {
$product = new WC_Product_Simple();
$product->set_name("Test Product " . $i);
$product->set_regular_price(rand(100, 9999));
$product->set_description("รายละเอียดสินค้าทดสอบ " . $i);
$product->set_short_description("สินค้าทดสอบ");
$product->set_stock_quantity(rand(10, 1000));
$product->set_manage_stock(true);
$product->set_status("publish");
$product->save();
if ($i % 50 == 0) echo "Created $i products\n";
}
echo "Done: 500 products created\n";
' --allow-root
# สร้าง test categories
docker compose exec wordpress wp wc product_cat create \
--name="Electronics" --allow-root
docker compose exec wordpress wp wc product_cat create \
--name="Clothing" --allow-root
docker compose exec wordpress wp wc product_cat create \
--name="Books" --allow-root
Load Testing ด้วย k6
k6 เป็น load testing tool จาก Grafana Labs เขียน test script ด้วย JavaScript รองรับ protocol หลายแบบ
# ติดตั้ง k6
# Ubuntu/Debian
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 update && sudo apt install k6
# macOS
brew install k6
# Docker
docker run --rm -i grafana/k6 run - < test.js
// woo-load-test.js — k6 script สำหรับ WooCommerce
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend } from 'k6/metrics';
const errorRate = new Rate('errors');
const cartDuration = new Trend('cart_duration');
export const options = {
scenarios: {
// สถานการณ์ที่ 1: ผู้เยี่ยมชมทั่วไป (browse)
browsers: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 50 },
{ duration: '5m', target: 100 },
{ duration: '2m', target: 200 },
{ duration: '3m', target: 200 },
{ duration: '2m', target: 0 },
],
exec: 'browseProducts',
},
// สถานการณ์ที่ 2: ลูกค้าที่สั่งซื้อ
buyers: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 10 },
{ duration: '5m', target: 30 },
{ duration: '2m', target: 50 },
{ duration: '3m', target: 50 },
{ duration: '2m', target: 0 },
],
exec: 'purchaseFlow',
},
},
thresholds: {
http_req_duration: ['p(95)<3000'],
http_req_failed: ['rate<0.05'],
errors: ['rate<0.1'],
cart_duration: ['p(95)<5000'],
},
};
const BASE = __ENV.BASE_URL || 'http://localhost:8080';
export function browseProducts() {
group('Browse Shop', () => {
// หน้าแรก
let res = http.get(`/`);
check(res, { 'homepage 200': (r) => r.status === 200 });
sleep(1);
// หน้า shop
res = http.get(`/shop/`);
check(res, { 'shop 200': (r) => r.status === 200 });
sleep(1);
// หน้าสินค้า
const productId = Math.floor(Math.random() * 500) + 1;
res = http.get(`/?p=`);
check(res, { 'product 200': (r) => r.status === 200 });
sleep(2);
// ค้นหาสินค้า
res = http.get(`/?s=test&post_type=product`);
check(res, { 'search 200': (r) => r.status === 200 });
});
sleep(Math.random() * 3);
}
export function purchaseFlow() {
group('Purchase Flow', () => {
const start = Date.now();
// เพิ่มสินค้าลงตะกร้า
const productId = Math.floor(Math.random() * 500) + 1;
let res = http.post(`/?wc-ajax=add_to_cart`, {
product_id: productId,
quantity: 1,
});
check(res, { 'add to cart': (r) => r.status === 200 });
errorRate.add(res.status !== 200);
sleep(1);
// หน้า cart
res = http.get(`/cart/`);
check(res, { 'cart page': (r) => r.status === 200 });
sleep(1);
// หน้า checkout
res = http.get(`/checkout/`);
check(res, { 'checkout page': (r) => r.status === 200 });
cartDuration.add(Date.now() - start);
});
sleep(Math.random() * 5);
}
# รัน load test
k6 run --env BASE_URL=http://localhost:8080 woo-load-test.js
# ตัวอย่าง output:
# scenarios: (100.00%) 2 scenarios, 250 max VUs, 14m30s max duration
#
# ✓ homepage 200
# ✓ shop 200
# ✓ product 200
# ✓ search 200
# ✓ add to cart
# ✓ cart page
# ✓ checkout page
#
# http_req_duration..: avg=245ms min=45ms med=180ms max=8.2s p(90)=890ms p(95)=1.8s
# http_req_failed....: 2.3% ✓ 156 ✗ 6544
# iterations.........: 6700 46.8/s
# ส่ง results ไป Grafana Cloud
k6 run --out cloud woo-load-test.js
วิเคราะห์ Bottleneck จาก Load Test Results
# ดู MySQL slow queries ระหว่าง test
docker compose exec mysql tail -f /var/log/mysql/slow.log
# ตัวอย่าง slow query ที่พบบ่อยใน WooCommerce:
# Time: 2.3s
# SELECT * FROM wp_posts p
# JOIN wp_postmeta pm ON p.ID = pm.post_id
# WHERE p.post_type = 'product'
# AND pm.meta_key = '_price'
# ORDER BY pm.meta_value+0 ASC
# LIMIT 0, 20;
# แก้ไข: เพิ่ม index
docker compose exec mysql mysql -u root -pRootPass123! woocommerce -e "
ALTER TABLE wp_postmeta ADD INDEX idx_meta_key_value (meta_key, meta_value(191));
ALTER TABLE wp_posts ADD INDEX idx_post_type_status (post_type, post_status, post_date);
"
# ดู PHP-FPM status (ถ้าใช้ php-fpm)
curl http://localhost:8080/status?full
# ดู Apache status
curl http://localhost:8080/server-status?auto
Optimization หลังจากพบ Bottleneck
# ติดตั้ง Redis Object Cache
docker compose exec wordpress wp plugin install redis-cache --activate --allow-root
docker compose exec wordpress wp redis enable --allow-root
# ตรวจสอบ Redis hit rate
docker compose exec redis redis-cli INFO stats | grep -E "keyspace_hits|keyspace_misses"
# keyspace_hits:45231
# keyspace_misses:3421
# Hit rate = 45231/(45231+3421) = 93%
# ติดตั้ง page cache (WP Super Cache)
docker compose exec wordpress wp plugin install wp-super-cache --activate --allow-root
# wp-config.php เพิ่ม
# define('WP_CACHE', true);
# ตั้ง cron แทน WP-Cron
# เพิ่มใน crontab ของ host:
echo "*/5 * * * * docker compose exec -T wordpress wp cron event run --due-now --allow-root" | crontab -
# Nginx reverse proxy + cache หน้า WordPress
# /etc/nginx/conf.d/woocommerce.conf
proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=woo_cache:100m max_size=2g inactive=60m;
server {
listen 80;
server_name shop.example.com;
# ไม่ cache หน้า cart, checkout, my-account
set $skip_cache 0;
if ($request_uri ~* "/cart/|/checkout/|/my-account/|wc-ajax") {
set $skip_cache 1;
}
if ($http_cookie ~* "woocommerce_items_in_cart|wp_woocommerce_session") {
set $skip_cache 1;
}
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_cache woo_cache;
proxy_cache_bypass $skip_cache;
proxy_no_cache $skip_cache;
proxy_cache_valid 200 10m;
proxy_cache_valid 404 1m;
add_header X-Cache-Status $upstream_cache_status;
}
}
Continuous Load Testing ใน CI/CD
# .github/workflows/load-test.yml
name: WooCommerce Load Test
on:
schedule:
- cron: '0 2 * * 1' # ทุกวันจันทร์ตี 2
workflow_dispatch:
jobs:
load-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Start WooCommerce stack
run: docker compose up -d
- name: Wait for WordPress
run: |
for i in $(seq 1 60); do
curl -sf http://localhost:8080/ && break
sleep 5
done
- name: Install k6
run: |
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 update && sudo apt install k6
- name: Run load test
run: k6 run --env BASE_URL=http://localhost:8080 woo-load-test.js
- name: Collect logs on failure
if: failure()
run: |
docker compose logs wordpress > wordpress.log
docker compose logs mysql > mysql.log
- uses: actions/upload-artifact@v4
if: always()
with:
name: load-test-results
path: "*.log"
FAQ - คำถามที่พบบ่อย
Q: WooCommerce รับ concurrent users ได้กี่คนบน shared hosting?
A: shared hosting ทั่วไปรับได้ประมาณ 20-50 concurrent users ถ้ามี object cache อาจได้ถึง 100 ถ้าต้องการมากกว่านั้นต้องใช้ VPS หรือ dedicated server ที่ tune PHP-FPM workers และ MySQL ให้เหมาะสม
Q: ควร load test นานแค่ไหร?
A: อย่างน้อย 10-15 นาที เพื่อให้เห็น pattern ที่แท้จริง test สั้นเกินจะไม่เจอปัญหา memory leak หรือ connection pool exhaustion ถ้าต้องการทดสอบ endurance ควรรัน 1-2 ชั่วโมง
Q: k6 กับ JMeter ต่างกันอย่างไร?
A: k6 เขียน script ด้วย JavaScript เบากว่า ใช้ memory น้อย เหมาะกับ CI/CD JMeter มี GUI สร้าง test ได้ง่ายกว่าสำหรับคนไม่เขียน code แต่กิน resource มากกว่า สำหรับ WooCommerce ที่ต้อง test บ่อยแนะนำ k6
Q: ทำ load test แล้วเว็บช้ามาก แก้อย่างไร?
A: เรียงลำดับการแก้ตามผลกระทบ: (1) เปิด object cache ด้วย Redis (2) เปิด page cache สำหรับหน้าที่ไม่ dynamic (3) ปรับ PHP-FPM workers ให้เพียงพอ (4) เพิ่ม index ใน MySQL สำหรับ slow queries (5) ใช้ CDN สำหรับ static files (6) ถ้ายังไม่พอต้อง scale server ขึ้น
