#การทำ Load Test ด้วย k6 ตั้งแต่พื้นฐานจนถึงใช้งานจริง

เมื่อระบบ Web Application หรือ API เริ่มมีผู้ใช้งานมากขึ้น คำถามสำคัญที่ทีมพัฒนาต้องตอบให้ได้คือ

ระบบสามารถรองรับผู้ใช้งานพร้อมกันได้มากแค่ไหน และเมื่อ Load เพิ่มขึ้น ระบบยังตอบสนองได้เร็วพอหรือไม่?

การทดสอบ Functional Testing เพียงอย่างเดียวไม่สามารถตอบคำถามนี้ได้ เพราะ Functional Test สนใจว่า ระบบทำงานถูกต้องหรือไม่ ขณะที่ Performance Testing สนใจว่า ระบบทำงานได้เร็ว เสถียร และรองรับ Load ได้มากเพียงใด

หนึ่งในเครื่องมือที่ได้รับความนิยมสำหรับงาน Performance Testing คือ k6 ซึ่งเป็นเครื่องมือ Open Source ที่ออกแบบมาให้ Developer และ QA Engineer สามารถเขียน Load Test ด้วย JavaScript ได้อย่างง่ายดาย และสามารถนำไปใช้งานร่วมกับ CI/CD Pipeline ได้ดี

บทความนี้จะอธิบายตั้งแต่พื้นฐานจนถึงแนวทางนำ k6 ไปใช้งานจริง


#Load Testing คืออะไร

Load Testing คือการทดสอบระบบภายใต้จำนวนผู้ใช้งานหรือจำนวน Request ที่กำหนด เพื่อประเมินว่าระบบสามารถรองรับ Load ได้ตาม Requirement หรือไม่

ตัวอย่างคำถามที่ Load Test ช่วยตอบได้ เช่น

  • ระบบรองรับผู้ใช้งานพร้อมกัน 100 คนได้หรือไม่
  • API รองรับ 500 Requests ต่อวินาทีได้หรือไม่
  • เมื่อ Load เพิ่มขึ้น Response Time เพิ่มขึ้นมากแค่ไหน
  • Error Rate เริ่มสูงเมื่อ Load ระดับใด
  • ระบบมี Bottleneck ที่ Application, Database หรือ Infrastructure หรือไม่

ตัวชี้วัดสำคัญที่มักใช้ประกอบด้วย

Metric ความหมาย
Response Time เวลาที่ระบบใช้ตอบกลับ Request
Throughput จำนวน Request หรือ Transaction ที่ระบบรองรับต่อช่วงเวลา
Error Rate สัดส่วน Request ที่เกิดข้อผิดพลาด
Concurrent Users จำนวนผู้ใช้งานพร้อมกัน
p90 / p95 / p99 Percentile ของ Response Time

#k6 คืออะไร

k6 เป็นเครื่องมือสำหรับ Load Testing และ Performance Testing ที่พัฒนาโดย Grafana Labs

จุดเด่นของ k6 ได้แก่

  • เขียน Test Script ด้วย JavaScript
  • ทำงานผ่าน Command Line
  • รองรับ HTTP/HTTPS API
  • รองรับ WebSocket
  • มี Built-in Metrics
  • รองรับ Checks
  • รองรับ Thresholds
  • รองรับ Scenarios หลายรูปแบบ
  • สามารถใช้กับ CI/CD Pipeline
  • เชื่อมต่อกับ Grafana และระบบ Monitoring ได้
  • เหมาะกับ Developer, QA และ DevOps

แนวคิดโดยรวมของ k6 สามารถมองได้ดังนี้

k6 Script
    |
    v
Virtual Users
    |
    v
Web / API
    |
    v
Metrics
    |
    v
Thresholds

Virtual User หรือ VU คือผู้ใช้งานจำลองที่ k6 สร้างขึ้นเพื่อเรียกใช้งานระบบตาม Test Script


#การติดตั้ง k6

#macOS

ติดตั้งผ่าน Homebrew

brew install k6

ตรวจสอบเวอร์ชัน

k6 version

#Windows

ติดตั้งผ่าน Chocolatey

choco install k6

หรือใช้ Windows Package Manager

winget install k6

ตรวจสอบเวอร์ชัน

k6 version

#Docker

หากไม่ต้องการติดตั้ง k6 ลงในเครื่อง สามารถใช้งานผ่าน Docker ได้

docker run --rm -i grafana/k6 run - < script.js

#สร้าง Load Test แรกด้วย k6

สร้างไฟล์ชื่อ

script.js

แล้วเขียนโค้ดดังนี้

import http from 'k6/http';
import { check, sleep } from 'k6';

export default function () {

  const response = http.get('https://test.k6.io');

  check(response, {
    'status is 200': (r) => r.status === 200,
  });

  sleep(1);
}

จากนั้นรัน

k6 run script.js

k6 จะเริ่มสร้าง Virtual User และเรียก URL ที่กำหนดใน Script


#กำหนดจำนวน Virtual Users

เราสามารถกำหนดจำนวนผู้ใช้งานจำลองและระยะเวลาการทดสอบผ่าน options

import http from 'k6/http';

export const options = {

  vus: 10,

  duration: '30s',

};

export default function () {

  http.get('https://test.k6.io');

}

ค่าต่อไปนี้

vus: 10

หมายถึงสร้าง Virtual Users จำนวน 10 คน

และ

duration: '30s'

หมายถึงให้ทดสอบเป็นเวลา 30 วินาที


#การทำ Ramp Up และ Ramp Down

ในระบบจริง จำนวนผู้ใช้งานมักไม่ได้เพิ่มจาก 0 เป็น 100 คนทันที

เราจึงสามารถเพิ่มจำนวน User แบบค่อยเป็นค่อยไปด้วย stages

import http from 'k6/http';
import { sleep } from 'k6';

export const options = {

  stages: [

    { duration: '30s', target: 20 },

    { duration: '1m', target: 20 },

    { duration: '30s', target: 50 },

    { duration: '1m', target: 50 },

    { duration: '30s', target: 0 },

  ],

};

export default function () {

  http.get('https://test.k6.io');

  sleep(1);

}

Workload จะมีลักษณะประมาณนี้

0 users

   |
   | 30 sec
   v

20 users

   |
   | 1 min
   v

20 users

   |
   | 30 sec
   v

50 users

   |
   | 1 min
   v

50 users

   |
   | 30 sec
   v

0 users

#ตรวจสอบ Response ด้วย Check

k6 มีคำสั่ง check() สำหรับตรวจสอบ Response

ตัวอย่าง

import http from 'k6/http';
import { check } from 'k6';

export default function () {

  const response = http.get('https://test.k6.io');

  check(response, {

    'status is 200':
      (r) => r.status === 200,

    'response time < 500ms':
      (r) => r.timings.duration < 500,

  });

}

Check มีลักษณะคล้าย Assertion ใน Automated Testing

อย่างไรก็ตาม Check ที่ Fail ไม่ได้ทำให้ Test Fail โดยอัตโนมัติ

หากต้องการกำหนดเกณฑ์ Pass / Fail ของ Performance Test ควรใช้ Threshold


#Threshold คือหัวใจของ Performance Testing

Threshold ใช้กำหนด Performance Requirement ของระบบ

ตัวอย่าง

export const options = {

  thresholds: {

    http_req_failed: [
      'rate<0.01',
    ],

    http_req_duration: [
      'p(95)<500',
    ],

  },

};

ความหมายคือ

Error Rate < 1%

และ

95% ของ Request
ต้องตอบกลับเร็วกว่า 500 ms

สามารถกำหนดหลาย Percentile ได้

thresholds: {

  http_req_failed: [
    'rate<0.01',
  ],

  http_req_duration: [

    'p(90)<300',

    'p(95)<500',

    'p(99)<1000',

  ],

}

นี่คือแนวทางที่เหมาะสมกว่าการดูเพียงค่าเฉลี่ยของ Response Time


#ตัวอย่าง Load Test REST API

สมมติเรามี API

GET /api/users

สามารถเขียน k6 Script ได้ดังนี้

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {

  stages: [

    { duration: '20s', target: 10 },

    { duration: '30s', target: 30 },

    { duration: '30s', target: 30 },

    { duration: '20s', target: 0 },

  ],

  thresholds: {

    http_req_failed: [
      'rate<0.01',
    ],

    http_req_duration: [
      'p(95)<500',
    ],

  },

};

export default function () {

  const response =
    http.get('https://example.com/api/users');

  check(response, {

    'status is 200':
      (r) => r.status === 200,

  });

  sleep(1);

}

รันด้วยคำสั่ง

k6 run users-load-test.js

#ทดสอบ POST API

สมมติระบบมี Login API

POST /api/login

ตัวอย่าง

import http from 'k6/http';
import { check } from 'k6';

export const options = {

  vus: 10,

  duration: '30s',

  thresholds: {

    http_req_failed: [
      'rate<0.01',
    ],

    http_req_duration: [
      'p(95)<800',
    ],

  },

};

export default function () {

  const url =
    'https://example.com/api/login';

  const payload =
    JSON.stringify({

      email: 'user@example.com',

      password: 'password',

    });

  const params = {

    headers: {

      'Content-Type':
        'application/json',

    },

  };

  const response =
    http.post(
      url,
      payload,
      params
    );

  check(response, {

    'login success':
      (r) => r.status === 200,

  });

}

#ใช้ Environment Variable

ไม่ควร Hard Code URL ของระบบใน Test Script

ตัวอย่าง

import http from 'k6/http';

const BASE_URL =
  __ENV.BASE_URL ||
  'http://localhost:8000';

export default function () {

  http.get(
    `${BASE_URL}/api/users`
  );

}

รัน

k6 run \
-e BASE_URL=https://api.example.com \
script.js

ข้อดีคือ Test Script เดียวสามารถใช้ได้หลาย Environment

Development

Staging

Performance

Production

#การใช้ Scenarios

k6 สามารถกำหนดรูปแบบ Load ได้ละเอียดขึ้นผ่าน scenarios

ตัวอย่าง

import http from 'k6/http';

export const options = {

  scenarios: {

    normal_load: {

      executor:
        'ramping-vus',

      stages: [

        {
          duration: '30s',
          target: 20
        },

        {
          duration: '2m',
          target: 20
        },

        {
          duration: '30s',
          target: 0
        },

      ],

    },

  },

};

export default function () {

  http.get(
    'https://example.com/api/products'
  );

}

#Constant Arrival Rate

บางระบบกำหนด Performance Requirement เป็นจำนวน Transaction ต่อวินาที

เช่น

ระบบต้องรองรับ
100 Transactions / Second

กรณีนี้สามารถใช้ Executor

constant-arrival-rate

ตัวอย่าง

import http from 'k6/http';

export const options = {

  scenarios: {

    api_load: {

      executor:
        'constant-arrival-rate',

      rate: 100,

      timeUnit: '1s',

      duration: '2m',

      preAllocatedVUs: 50,

      maxVUs: 200,

    },

  },

};

export default function () {

  http.get(
    'https://example.com/api/products'
  );

}

หมายความว่า k6 จะพยายามเริ่ม

100 Iterations / Second

ตลอดระยะเวลาที่กำหนด


#การจำลอง User Journey

Performance Test ที่ดีไม่ควรทดสอบเพียง Endpoint เดียวเสมอไป

แต่ควรจำลองพฤติกรรมจริงของ User เช่น

Login

  ↓

View Products

  ↓

View Product Detail

  ↓

Add To Cart

  ↓

Checkout

ตัวอย่าง

import http from 'k6/http';
import { check, sleep } from 'k6';

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

export const options = {

  vus: 20,

  duration: '1m',

  thresholds: {

    http_req_failed: [
      'rate<0.01',
    ],

    http_req_duration: [
      'p(95)<1000',
    ],

  },

};

export default function () {

  const login =
    http.post(

      `${BASE_URL}/api/login`,

      JSON.stringify({

        email:
          'user@example.com',

        password:
          'password',

      }),

      {

        headers: {

          'Content-Type':
            'application/json',

        },

      }

    );

  check(login, {

    'login status 200':
      (r) => r.status === 200,

  });

  sleep(1);

  const products =
    http.get(
      `${BASE_URL}/api/products`
    );

  check(products, {

    'products status 200':
      (r) => r.status === 200,

  });

  sleep(2);

}

#Metrics สำคัญของ k6

หลังจากรัน Test k6 จะแสดง Metrics หลายค่า

ตัวอย่าง

http_req_duration

http_req_failed

http_reqs

iteration_duration

iterations

vus

vus_max

#http_req_duration

แสดงเวลาที่ HTTP Request ใช้ในการทำงาน

ควรดูค่า

p90

p95

p99

ตัวอย่าง

avg = 180 ms

p95 = 350 ms

p99 = 950 ms

แม้ว่าค่าเฉลี่ยจะดูเร็ว แต่ p99 อาจแสดงให้เห็นว่าผู้ใช้บางส่วนกำลังได้รับ Response ที่ช้ามาก


#http_req_failed

แสดงสัดส่วน HTTP Request ที่ผิดพลาด

ตัวอย่าง

http_req_failed = 0.35%

หาก Requirement กำหนด

Error Rate < 1%

ค่าดังกล่าวยังถือว่าผ่าน


#http_reqs

แสดงจำนวน HTTP Request ที่เกิดขึ้นทั้งหมด รวมถึงอัตรา Request ต่อวินาทีที่เกิดขึ้นจริง


#ประเภทของ Performance Test

k6 สามารถใช้สร้าง Performance Test ได้หลายแบบ


#Smoke Test

ใช้ตรวจสอบว่า Script และระบบทำงานได้ก่อนเริ่ม Load Test จริง

export const options = {

  vus: 1,

  duration: '10s',

};

เหมาะสำหรับตรวจสอบ

  • URL
  • Authentication
  • Test Data
  • Script Error
  • Check
  • Connectivity

#Load Test

ใช้ Load ระดับที่ใกล้เคียงกับการใช้งานจริง

ตัวอย่าง

Normal Traffic

100 Concurrent Users
stages: [

  {
    duration: '2m',
    target: 100
  },

  {
    duration: '10m',
    target: 100
  },

  {
    duration: '2m',
    target: 0
  },

]

#Stress Test

ใช้เพิ่ม Load จนสูงกว่าปกติเพื่อหา Breaking Point

stages: [

  {
    duration: '2m',
    target: 100
  },

  {
    duration: '2m',
    target: 200
  },

  {
    duration: '2m',
    target: 300
  },

  {
    duration: '2m',
    target: 400
  },

  {
    duration: '2m',
    target: 0
  },

]

Stress Test ช่วยค้นหาว่า

  • ระบบเริ่มช้าที่ Load เท่าใด
  • Error Rate เริ่มเพิ่มเมื่อใด
  • Database Connection เต็มเมื่อใด
  • CPU หรือ Memory กลายเป็น Bottleneck เมื่อใด

#Spike Test

Spike Test ใช้จำลอง Load ที่เพิ่มขึ้นอย่างรวดเร็ว

ตัวอย่าง

stages: [

  {
    duration: '30s',
    target: 20
  },

  {
    duration: '10s',
    target: 500
  },

  {
    duration: '1m',
    target: 500
  },

  {
    duration: '10s',
    target: 20
  },

  {
    duration: '30s',
    target: 0
  },

]

เหมาะกับสถานการณ์ เช่น

  • Flash Sale
  • Ticket Booking
  • เปิดลงทะเบียน
  • Campaign Traffic
  • Breaking News

#Soak Test

Soak Test หรือ Endurance Test ใช้ตรวจสอบระบบภายใต้ Load ต่อเนื่องเป็นเวลานาน

ตัวอย่าง

100 Users

เป็นเวลา 4 ชั่วโมง

เหมาะสำหรับค้นหา

  • Memory Leak
  • Connection Leak
  • Resource Exhaustion
  • Queue Backlog
  • Cache Degradation
  • Database Performance Degradation

#กลยุทธ์การทำ Performance Test

ไม่ควรเริ่มต้นด้วย Stress Test ทันที

ลำดับที่แนะนำคือ

Smoke Test

    ↓

Average Load Test

    ↓

Peak Load Test

    ↓

Stress Test

    ↓

Spike Test

    ↓

Soak Test

#k6 กับ Monitoring

ข้อมูลจาก k6 เพียงอย่างเดียวสามารถบอกได้ว่า

ระบบช้า

หรือ

ระบบมี Error

แต่ไม่ได้บอกเสมอไปว่า ทำไมระบบถึงช้า

จึงควรทำ Monitoring ฝั่ง Server ควบคู่กัน

ตัวอย่าง

k6
 |
 +-- Response Time
 |
 +-- Throughput
 |
 +-- Error Rate

        |

        v

Monitoring

 |
 +-- CPU
 |
 +-- Memory
 |
 +-- Database
 |
 +-- Connection Pool
 |
 +-- Network
 |
 +-- Containers
 |
 +-- Application Metrics

Stack ที่นิยมใช้ร่วมกันคือ

k6

+

Prometheus

+

Grafana

รวมถึง OpenTelemetry หรือ APM Platform อื่น ๆ


#ใช้ k6 กับ CI/CD

หนึ่งในข้อดีของ k6 คือสามารถนำเข้า CI/CD Pipeline ได้ง่าย

ตัวอย่าง Threshold

thresholds: {

  http_req_failed: [
    'rate<0.01'
  ],

  http_req_duration: [
    'p(95)<500'
  ],

}

จากนั้นใน Pipeline เรียก

k6 run tests/performance/api-load-test.js

หาก Threshold ไม่ผ่าน k6 สามารถคืน Exit Code ที่ทำให้ Pipeline Fail ได้

Flow ตัวอย่าง

Developer Push

      ↓

Build

      ↓

Unit Test

      ↓

Integration Test

      ↓

Deploy Test Environment

      ↓

k6 Load Test

      ↓

Threshold

   ┌───────────┐

 PASS         FAIL

   |             |

Deploy       Stop Pipeline

สำหรับ Performance Test ขนาดใหญ่ควรพิจารณาแยกเป็น

Nightly Test

Scheduled Test

Pre-release Test

แทนการรันทุก Pull Request


#โครงสร้าง Project ที่แนะนำ

สามารถจัดโครงสร้าง Test Project ได้ดังนี้

performance-tests/

├── tests/

│   ├── smoke.js

│   ├── load.js

│   ├── stress.js

│   └── spike.js

│
├── scenarios/

│   ├── login.js

│   ├── products.js

│   └── checkout.js

│
├── data/

│   └── users.json

│
├── utils/

│   ├── config.js

│   └── auth.js

│
└── README.md

ข้อดีคือสามารถแยก

Scenario

Test Data

Configuration

Utility

Workload

ออกจากกันได้อย่างชัดเจน


#Best Practices สำหรับ k6

#1. เริ่มจาก Performance Requirement

ก่อนเขียน Test ควรมี Requirement เช่น

รองรับ 200 Requests / Second

p95 < 500 ms

Error Rate < 1%

จากนั้นจึงออกแบบ Test ให้ตรงกับ Requirement


#2. ใช้ Threshold เสมอ

ไม่ควรดูเพียงว่า Test รันจบหรือไม่

ควรมี Pass / Fail Criteria

thresholds: {

  http_req_failed: [
    'rate<0.01'
  ],

  http_req_duration: [
    'p(95)<500'
  ],

}

#3. ดู Percentile มากกว่า Average

ไม่ควรดูเพียง

Average Response Time

ควรดู

p90

p95

p99

เพราะค่าเฉลี่ยอาจซ่อน Slow Request บางส่วนไว้


#4. จำลอง Traffic ให้สมจริง

ระบบ E-Commerce อาจมี Traffic เช่น

Browse Products   60%

Search            20%

Add To Cart       10%

Checkout          10%

ดังนั้น Test Scenario ควรสะท้อนสัดส่วนจริงของระบบ


#5. ใช้ Think Time

User จริงไม่ได้กด Request ต่อเนื่องทุก Millisecond

สามารถใช้

sleep(1);

เพื่อจำลองเวลาที่ User อ่านหรือคิดก่อนทำ Action ต่อไป


#6. แยก Test Data

ไม่ควรใช้ User Account เดียวสำหรับ Virtual User จำนวนมาก หากระบบมี Session หรือ Business Rule ที่ทำให้ผลผิดเพี้ยน

ควรเตรียม Test Data เช่น

user001

user002

user003

...

user500

#7. ทำ Monitoring พร้อม Load Test

เมื่อ Response Time เพิ่มขึ้น ต้องตรวจสอบได้ว่า Bottleneck อยู่ที่ใด

เช่น

Application

Database

CPU

Memory

Network

External Service

#8. ใช้ Environment ที่ใกล้ Production

หาก Performance Environment แตกต่างจาก Production มาก ผลที่ได้อาจไม่สามารถนำไปใช้คาดการณ์ Production ได้อย่างแม่นยำ


#ตัวอย่าง Complete k6 Load Test

ตัวอย่าง Test Script ที่รวม

  • Scenario
  • Threshold
  • Check
  • Environment Variable
  • Tags

ไว้ในไฟล์เดียว

import http from 'k6/http';
import { check, sleep } from 'k6';

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

export const options = {

  scenarios: {

    average_load: {

      executor:
        'ramping-vus',

      stages: [

        {
          duration: '30s',
          target: 20
        },

        {
          duration: '2m',
          target: 20
        },

        {
          duration: '30s',
          target: 50
        },

        {
          duration: '2m',
          target: 50
        },

        {
          duration: '30s',
          target: 0
        },

      ],

    },

  },

  thresholds: {

    http_req_failed: [
      'rate<0.01'
    ],

    http_req_duration: [

      'p(90)<300',

      'p(95)<500',

      'p(99)<1000',

    ],

    checks: [
      'rate>0.99'
    ],

  },

};

export default function () {

  const response =
    http.get(

      `${BASE_URL}/api/products`,

      {

        tags: {

          endpoint:
            'products',

        },

      }

    );

  check(response, {

    'status is 200':
      (r) => r.status === 200,

    'body is not empty':
      (r) =>
        r.body &&
        r.body.length > 0,

  });

  sleep(1);

}

รันด้วย

k6 run \
-e BASE_URL=https://api.example.com \
load-test.js

#ตัวอย่าง Performance Requirement

ก่อนเริ่มทดสอบควรนิยาม Acceptance Criteria ให้ชัดเจน

ตัวอย่าง

Metric Requirement
Concurrent Users 200 Users
Throughput ≥ 100 req/s
p95 Response Time < 500 ms
p99 Response Time < 1,000 ms
Error Rate < 1%

จาก Requirement นี้สามารถแปลงเป็น Threshold ได้ เช่น

thresholds: {

  http_req_failed: [
    'rate<0.01'
  ],

  http_req_duration: [

    'p(95)<500',

    'p(99)<1000',

  ],

}

แนวทางนี้ทำให้ Performance Test เป็นสิ่งที่สามารถตัดสินได้แบบอัตโนมัติ ไม่ใช่เพียงดูกราฟหลังจากรัน Test เสร็จ


#สรุป

k6 เป็นเครื่องมือที่เหมาะสำหรับการทำ Load Testing และ Performance Testing โดยเฉพาะทีมที่ต้องการ Test แบบ Developer-friendly และต้องการนำ Performance Testing เข้าไปเป็นส่วนหนึ่งของ CI/CD

แนวคิดสำคัญของ k6 ประกอบด้วย

Virtual Users

+

Scenarios

+

Checks

+

Thresholds

+

Metrics

+

Monitoring

หากเริ่มต้นใช้งาน แนะนำให้ทำตามลำดับดังนี้

  1. สร้าง Smoke Test
  2. เพิ่ม Check
  3. กำหนด Threshold
  4. สร้าง Load Scenario
  5. วิเคราะห์ p95 และ p99
  6. ตรวจ Error Rate
  7. Monitor CPU, Memory และ Database
  8. เพิ่ม Stress Test
  9. เพิ่ม Spike Test
  10. เพิ่ม Soak Test
  11. นำ Test เข้า CI/CD Pipeline

เป้าหมายของ Load Testing ไม่ใช่การสร้าง Request ให้ได้จำนวนมากที่สุด แต่คือการตอบคำถามว่า

ระบบสามารถรองรับ Workload ที่ธุรกิจต้องการได้อย่างเสถียรและมีประสิทธิภาพหรือไม่

เมื่อออกแบบ Test Scenario ให้ใกล้เคียง Production และกำหนด Threshold อย่างชัดเจน k6 จะกลายเป็นเครื่องมือที่มีประโยชน์อย่างมากในการวัด Performance, Reliability และ Scalability ของระบบ


#References