SiamCafe · Blog
Medusa Commerce Load Testing Strategy — ทดสอบ
บทความ

Medusa Commerce Load Testing Strategy — ทดสอบ

เผยแพร่ 28 พฤษภาคม 2569

Medusa Commerce Load Testing

Medusa Commerce Load Testing k6 Stress Spike Soak PostgreSQL Node.js API Performance Bottleneck Throughput Production

Test TypeLoadDurationPurpose
Load TestExpected (100-500 VUs)10-30 minยืนยัน Performance ที่ Load ปกติ
Stress Test2-5x Expected10-20 minหาจุด Breaking Point
Spike Test0 → Max → 0 ทันที5-10 minจำลอง Flash Sale Traffic Spike
Soak TestExpected Load ต่อเนื่อง2-8 hoursหา Memory Leak Connection Leak

k6 Test Script

# === k6 Load Test for Medusa Commerce ===

# import http from 'k6/http';
# import { check, group, sleep } from 'k6';
# import { Rate, Trend } from 'k6/metrics';
#
# const BASE_URL = 'http://localhost:9000/store';
# const checkoutErrors = new Rate('checkout_errors');
# const cartLatency = new Trend('cart_add_latency');
#
# export const options = {
#   stages: [
#     { duration: '2m', target: 50 },   // ramp-up to 50 VUs
#     { duration: '5m', target: 50 },   // hold 50 VUs
#     { duration: '2m', target: 200 },  // ramp-up to 200 (stress)
#     { duration: '5m', target: 200 },  // hold 200 VUs
#     { duration: '2m', target: 0 },    // ramp-down
#   ],
#   thresholds: {
#     http_req_duration: ['p(95)<500', 'p(99)<1000'],
#     http_req_failed: ['rate<0.01'],
#     checkout_errors: ['rate<0.05'],
#     cart_add_latency: ['p(95)<300'],
#   },
# };
#
# export default function () {
#   group('Browse Products', () => {
#     const res = http.get(`/products?limit=20`);
#     check(res, { 'products 200': (r) => r.status === 200 });
#     sleep(2);
#   });
#   group('View Product Detail', () => {
#     const res = http.get(`/products/prod_01`);
#     check(res, { 'detail 200': (r) => r.status === 200 });
#     sleep(1);
#   });
#   group('Add to Cart', () => {
#     const start = Date.now();
#     const res = http.post(`/carts/cart_01/line-items`,
#       JSON.stringify({ variant_id: 'var_01', quantity: 1 }),
#       { headers: { 'Content-Type': 'application/json' } });
#     cartLatency.add(Date.now() - start);
#     check(res, { 'cart 200': (r) => r.status === 200 });
#     sleep(1);
#   });
#   group('Checkout', () => {
#     const res = http.post(`/carts/cart_01/complete`);
#     const ok = check(res, { 'checkout 200': (r) => r.status === 200 });
#     if (!ok) checkoutErrors.add(1);
#     sleep(3);
#   });
# }

from dataclasses import dataclass

@dataclass
class TestScenario:
    scenario: str
    endpoint: str
    method: str
    target_p95: str
    weight: str

scenarios = [
    TestScenario("Browse Products",
        "/store/products?limit=20",
        "GET",
        "< 200ms",
        "40% of requests"),
    TestScenario("Product Detail",
        "/store/products/{id}",
        "GET",
        "< 150ms",
        "25% of requests"),
    TestScenario("Add to Cart",
        "/store/carts/{id}/line-items",
        "POST",
        "< 300ms",
        "15% of requests"),
    TestScenario("Checkout",
        "/store/carts/{id}/complete",
        "POST",
        "< 500ms",
        "5% of requests"),
    TestScenario("Search Products",
        "/store/products?q={query}",
        "GET",
        "< 300ms",
        "10% of requests"),
    TestScenario("Order History",
        "/store/customers/me/orders",
        "GET",
        "< 200ms",
        "5% of requests"),
]

print("=== Test Scenarios ===")
for s in scenarios:
    print(f"  [{s.scenario}] {s.method} {s.endpoint}")
    print(f"    Target P95: {s.target_p95} | Weight: {s.weight}")

Bottleneck Analysis

# === Bottleneck Analysis ===

# PostgreSQL Slow Query Analysis
# SELECT query, calls, mean_exec_time, total_exec_time
# FROM pg_stat_statements
# ORDER BY mean_exec_time DESC
# LIMIT 20;
#
# Check Missing Index
# SELECT schemaname, tablename, seq_scan, idx_scan
# FROM pg_stat_user_tables
# WHERE seq_scan > idx_scan
# ORDER BY seq_scan DESC;
#
# Check Connection Pool
# SELECT count(*), state FROM pg_stat_activity GROUP BY state;
#
# Node.js Event Loop Lag
# const { monitorEventLoopDelay } = require('perf_hooks');
# const h = monitorEventLoopDelay({ resolution: 20 });
# h.enable();
# setInterval(() => {
#   console.log(`Event Loop P99: ms`);
# }, 5000);

@dataclass
class Bottleneck:
    layer: str
    symptom: str
    diagnosis: str
    fix: str
    impact: str

bottlenecks = [
    Bottleneck("Database: Slow Query",
        "P95 Latency สูง ทุก API ที่ Query DB",
        "pg_stat_statements: mean_exec_time > 100ms",
        "สร้าง Index ตาม Query Pattern Optimize Query",
        "ลด Latency 50-90% (เร็วสุด ง่ายสุด)"),
    Bottleneck("Database: Connection Pool",
        "Connection Timeout Errors ที่ High Load",
        "pg_stat_activity: active > pool_size",
        "เพิ่ม Pool Size ใช้ PgBouncer",
        "ลด Error Rate จาก Connection Exhaustion"),
    Bottleneck("App: Event Loop Blocking",
        "Response Time เพิ่มทุก API พร้อมกัน",
        "Event Loop Delay P99 > 100ms",
        "Offload Heavy Work Worker Thread/Queue",
        "ลด Latency สำหรับทุก Request"),
    Bottleneck("App: Memory Leak",
        "Memory เพิ่มขึ้นเรื่อยๆ (Soak Test)",
        "Heap Usage เพิ่มไม่ลง GC ทำงานบ่อย",
        "Profile ด้วย --inspect หา Leak Fix Code",
        "ป้องกัน OOM Crash Production"),
    Bottleneck("Cache: Low Hit Rate",
        "DB Load สูงแม้ Traffic ไม่มาก",
        "Redis Hit Rate < 50%",
        "Cache Popular Queries Product List Detail",
        "ลด DB Load 50-80%"),
    Bottleneck("External: Payment Gateway",
        "Checkout Latency สูงกว่า API อื่น",
        "Payment API P95 > 2s",
        "Async Payment Processing Queue-based",
        "ลด Checkout Latency ป้องกัน Timeout"),
]

print("=== Bottleneck Analysis ===")
for b in bottlenecks:
    print(f"\n  [{b.layer}]")
    print(f"    Symptom: {b.symptom}")
    print(f"    Diagnosis: {b.diagnosis}")
    print(f"    Fix: {b.fix}")
    print(f"    Impact: {b.impact}")

Production Optimization

# === Performance Optimization Checklist ===

@dataclass
class OptItem:
    category: str
    action: str
    expected_gain: str
    effort: str

optimizations = [
    OptItem("Database",
        "สร้าง Index ตาม pg_stat_statements Top Queries",
        "Latency ลด 50-90% สำหรับ Slow Queries",
        "ต่ำ (1-2 ชม.)"),
    OptItem("Cache",
        "Redis Cache: Product List Detail Category",
        "DB Load ลด 50-80% Latency ลด 70%",
        "ปานกลาง (1-2 วัน)"),
    OptItem("Connection Pool",
        "PgBouncer + Optimize Pool Size",
        "รองรับ Concurrent Users เพิ่ม 2-5x",
        "ต่ำ (2-4 ชม.)"),
    OptItem("CDN",
        "CDN สำหรับ Product Images Static Assets",
        "Image Load Time ลด 50-80%",
        "ต่ำ (1-2 ชม.)"),
    OptItem("Horizontal Scale",
        "เพิ่ม Medusa Instance + Load Balancer",
        "Throughput เพิ่ม 2-4x ต่อ Instance",
        "ปานกลาง (1 วัน)"),
    OptItem("Read Replica",
        "PostgreSQL Read Replica สำหรับ Read API",
        "DB Write Performance ไม่กระทบจาก Read",
        "ปานกลาง (1 วัน)"),
    OptItem("Queue",
        "Queue สำหรับ Payment Email Notification",
        "Checkout Response Time ลด 30-50%",
        "ปานกลาง (2-3 วัน)"),
]

print("=== Optimization Checklist ===")
for o in optimizations:
    print(f"  [{o.category}] {o.action}")
    print(f"    Gain: {o.expected_gain} | Effort: {o.effort}")

เคล็ดลับ

  • k6: ใช้ k6 สำหรับ Load Test เขียน JavaScript ง่าย เร็ว
  • Index: เริ่มจาก Database Index เร็วสุด Impact สูงสุด
  • Cache: Cache Product List Detail ลด DB Load 50-80%
  • Spike Test: ทำ Spike Test ก่อน Flash Sale Campaign
  • Soak Test: ทำ Soak Test 2-8 ชม. หา Memory Leak

Medusa Commerce คืออะไร

Open Source Headless Commerce Node.js TypeScript PostgreSQL REST API Product Cart Checkout Order Payment Shipping Plugin