Kuasai K6 dari konsep inti hingga produksi. Pelajari load testing, stress testing, dan spike testing. Bangun complete e-commerce API dengan NestJS dan stress test dengan realistic scenarios termasuk checkout flows, inventory management, dan payment processing.

Setiap aplikasi memiliki limits. Pada beberapa point, terlalu banyak users hit API Anda simultaneously, dan semuanya breaks. Performance testing mengungkap limits ini sebelum users discover mereka di production.
K6 adalah modern load testing platform yang membuat performance testing accessible, scriptable, dan developer-friendly. Digunakan oleh companies seperti Grafana, Datadog, dan thousands dari engineering teams, K6 enable Anda untuk simulate real-world traffic patterns dan identify bottlenecks sebelum mereka impact users.
Dalam artikel ini, kita akan mengeksplorasi arsitektur K6, memahami load testing fundamentals, dan membangun production-ready e-commerce API dengan NestJS yang kita stress test dengan realistic scenarios termasuk checkout flows, inventory management, dan payment processing.
Traditional load testing tools memiliki significant limitations:
Complex Setup: Tools seperti JMeter memerlukan GUI configuration dan sulit untuk version control.
Not Developer-Friendly: Non-developers tidak bisa easily write atau modify tests.
Limited Scripting: Sulit untuk simulate complex user behaviors dan workflows.
Poor Integration: Difficult untuk integrate ke CI/CD pipelines.
Expensive: Enterprise tools cost thousands per month.
Slow Feedback: Results memerlukan hours untuk analyze.
K6 dibangun oleh Grafana Labs untuk solve problems ini:
JavaScript-Based: Write tests dalam JavaScript, familiar ke developers.
Version Control: Tests adalah code, stored di Git.
CI/CD Integration: Runs di pipelines, automated performance testing.
Realistic Scenarios: Simulate complex user journeys.
Open Source: Free dan community-driven.
Fast Feedback: Results dalam minutes, bukan hours.
Cloud & Local: Run tests locally atau di cloud.
Virtual Users (VUs): Simulated users executing test script Anda.
Iterations: Number dari times setiap VU execute test.
Stages: Ramp-up, steady-state, dan ramp-down phases.
Metrics: Response time, throughput, error rate, custom metrics.
Thresholds: Pass/fail criteria untuk tests Anda.
Groups: Organize related requests untuk better reporting.
Checks: Assertions yang validate responses.
Test Script → K6 Engine → Virtual Users → HTTP Requests → Target API → Metrics CollectionK6 menggunakan unique execution model yang optimized untuk performance:
Local Machine → K6 Engine (Go-based) → Virtual Users (Lightweight)
↓
HTTP Requests
↓
Target APIK6 engine ditulis dalam Go, membuat extremely efficient. Setiap virtual user adalah lightweight, allowing thousands dari concurrent users pada single machine.
VUs adalah simulated users. Setiap VU execute test script Anda independently.
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
vus: 10, // 10 virtual users
duration: '30s', // Run untuk 30 seconds
};
export default function () {
http.get('https://api.example.com/users');
sleep(1);
}Use Cases:
Stages define bagaimana VUs scale over time.
export const options = {
stages: [
{ duration: '2m', target: 100 }, // Ramp up ke 100 VUs
{ duration: '5m', target: 100 }, // Stay di 100 VUs
{ duration: '2m', target: 200 }, // Ramp up ke 200 VUs
{ duration: '5m', target: 200 }, // Stay di 200 VUs
{ duration: '2m', target: 0 }, // Ramp down ke 0 VUs
],
};
export default function () {
http.get('https://api.example.com/users');
}Use Cases:
Checks validate response correctness tanpa fail test.
import http from 'k6/http';
import { check } from 'k6';
export default function () {
const response = http.get('https://api.example.com/users');
check(response, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
'has user data': (r) => r.json('data.length') > 0,
});
}Use Cases:
Thresholds define pass/fail criteria untuk test Anda.
export const options = {
thresholds: {
http_req_duration: ['p(95)<500', 'p(99)<1000'], // 95th percentile < 500ms
http_req_failed: ['rate<0.1'], // Error rate < 10%
http_reqs: ['rate>100'], // At least 100 req/s
},
};
export default function () {
http.get('https://api.example.com/users');
}Use Cases:
Track custom metrics specific ke application Anda.
import http from 'k6/http';
import { Counter, Trend, Gauge } from 'k6/metrics';
const checkoutTime = new Trend('checkout_duration');
const cartAdditions = new Counter('cart_additions');
const activeUsers = new Gauge('active_users');
export default function () {
const startTime = new Date();
http.post('https://api.example.com/cart/add', { productId: 1 });
cartAdditions.add(1);
const response = http.post('https://api.example.com/checkout', {
items: [{ id: 1, quantity: 1 }],
});
checkoutTime.add(new Date() - startTime);
activeUsers.add(1);
}Metric Types:
Organize related requests untuk better reporting.
import http from 'k6/http';
import { group } from 'k6';
export default function () {
group('User Authentication', function () {
http.post('https://api.example.com/login', {
email: 'user@example.com',
password: 'password',
});
});
group('Product Browsing', function () {
http.get('https://api.example.com/products');
http.get('https://api.example.com/products/1');
});
group('Checkout', function () {
http.post('https://api.example.com/checkout', {
items: [{ id: 1, quantity: 1 }],
});
});
}Use Cases:
Define different user behaviors dan traffic patterns.
export const options = {
scenarios: {
browsing: {
executor: 'constant-vus',
vus: 50,
duration: '5m',
exec: 'browsing',
},
checkout: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 100 },
{ duration: '5m', target: 100 },
{ duration: '2m', target: 0 },
],
exec: 'checkout',
},
},
};
export function browsing() {
http.get('https://api.example.com/products');
}
export function checkout() {
http.post('https://api.example.com/checkout', {});
}Use Cases:
Use external data dalam tests Anda.
import http from 'k6/http';
import { check } from 'k6';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
import { SharedArray } from 'k6/data';
const data = new SharedArray('users', function () {
return papaparse.parse(open('./users.csv')).data;
});
export default function () {
const user = data[Math.floor(Math.random() * data.length)];
const response = http.post('https://api.example.com/login', {
email: user.email,
password: user.password,
});
check(response, {
'login successful': (r) => r.status === 200,
});
}Use Cases:
Extract dan use data dari responses.
import http from 'k6/http';
export default function () {
// Get product list
const productsResponse = http.get('https://api.example.com/products');
const products = productsResponse.json('data');
// Extract product ID
const productId = products[0].id;
// Use dalam next request
const detailsResponse = http.get(
`https://api.example.com/products/${productId}`
);
// Extract session token
const sessionToken = detailsResponse.headers['X-Session-Token'];
// Use dalam subsequent requests
http.post(
'https://api.example.com/cart/add',
{ productId },
{
headers: { 'X-Session-Token': sessionToken },
}
);
}Use Cases:
K6 provides features untuk efficient testing.
import http from 'k6/http';
import { batch } from 'k6/http';
export const options = {
// Connection pooling
ext: {
loadimpact: {
projectID: 3356643,
name: 'API Load Test',
},
},
};
export default function () {
// Batch requests untuk efficiency
batch([
['GET', 'https://api.example.com/users'],
['GET', 'https://api.example.com/products'],
['GET', 'https://api.example.com/orders'],
]);
}Use Cases:
Sekarang mari kita bangun production-ready e-commerce API yang demonstrate realistic scenarios. Sistem handle:
npm i -g @nestjs/cli
nest new ecommerce-api
cd ecommerce-api
npm install @nestjs/typeorm typeorm pg class-validator class-transformerimport { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 5432,
username: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'password',
database: process.env.DB_NAME || 'ecommerce',
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
synchronize: true,
}),
],
})
export class DatabaseModule {}import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { CartItem } from './cart-item.entity';
@Entity('products')
export class Product {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column('text')
description: string;
@Column('decimal', { precision: 10, scale: 2 })
price: number;
@Column({ default: 0 })
stock: number;
@Column({ default: 0 })
reserved: number;
@Column({ default: true })
isActive: boolean;
@OneToMany(() => CartItem, (item) => item.product)
cartItems: CartItem[];
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
updatedAt: Date;
}import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Product } from '../entities/product.entity';
@Injectable()
export class ProductsService {
constructor(
@InjectRepository(Product)
private productRepository: Repository<Product>,
) {}
async getAllProducts(limit: number = 20, offset: number = 0) {
return this.productRepository.find({
where: { isActive: true },
take: limit,
skip: offset,
});
}
async getProductById(id: number) {
return this.productRepository.findOne({ where: { id } });
}
async checkAvailability(productId: number, quantity: number) {
const product = await this.productRepository.findOne({
where: { id: productId },
});
if (!product) return false;
return product.stock - product.reserved >= quantity;
}
async reserveStock(productId: number, quantity: number) {
const product = await this.productRepository.findOne({
where: { id: productId },
});
if (!product || product.stock - product.reserved < quantity) {
throw new Error('Insufficient stock');
}
product.reserved += quantity;
return this.productRepository.save(product);
}
async confirmStock(productId: number, quantity: number) {
const product = await this.productRepository.findOne({
where: { id: productId },
});
if (product) {
product.stock -= quantity;
product.reserved = Math.max(0, product.reserved - quantity);
await this.productRepository.save(product);
}
}
}import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Order } from '../entities/order.entity';
import { ProductsService } from '../products/products.service';
@Injectable()
export class OrdersService {
constructor(
@InjectRepository(Order)
private orderRepository: Repository<Order>,
private productsService: ProductsService,
) {}
async createOrder(sessionId: string, items: any[]) {
if (items.length === 0) {
throw new Error('Cart is empty');
}
// Reserve stock untuk semua items
for (const item of items) {
await this.productsService.reserveStock(item.productId, item.quantity);
}
const totalAmount = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const order = this.orderRepository.create({
sessionId,
items,
totalAmount,
status: 'pending',
});
return this.orderRepository.save(order);
}
async processPayment(orderId: string, paymentDetails: any) {
const order = await this.orderRepository.findOne({
where: { id: orderId },
});
if (!order) throw new Error('Order not found');
// Simulate payment processing
const paymentId = `PAY-${Date.now()}`;
const isSuccessful = Math.random() > 0.05; // 95% success rate
order.paymentId = paymentId;
order.paymentStatus = isSuccessful ? 'completed' : 'failed';
order.status = isSuccessful ? 'processing' : 'failed';
return this.orderRepository.save(order);
}
async completeOrder(orderId: string) {
const order = await this.orderRepository.findOne({
where: { id: orderId },
});
if (!order) throw new Error('Order not found');
// Confirm stock deduction
for (const item of order.items) {
await this.productsService.confirmStock(item.productId, item.quantity);
}
order.status = 'completed';
return this.orderRepository.save(order);
}
async getOrderStatus(orderId: string) {
return this.orderRepository.findOne({ where: { id: orderId } });
}
}import { Controller, Get, Param, Query } from '@nestjs/common';
import { ProductsService } from './products.service';
@Controller('products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
@Get()
async getAllProducts(
@Query('limit') limit: number = 20,
@Query('offset') offset: number = 0,
) {
const products = await this.productsService.getAllProducts(limit, offset);
return { count: products.length, products };
}
@Get(':id')
async getProduct(@Param('id') id: number) {
const product = await this.productsService.getProductById(id);
return product;
}
@Get(':id/availability')
async checkAvailability(
@Param('id') id: number,
@Query('quantity') quantity: number,
) {
const available = await this.productsService.checkAvailability(id, quantity);
return { productId: id, quantity, available };
}
}import { Controller, Post, Get, Body, Param, Headers } from '@nestjs/common';
import { OrdersService } from './orders.service';
@Controller('orders')
export class OrdersController {
constructor(private readonly ordersService: OrdersService) {}
@Post('create')
async createOrder(
@Headers('x-session-id') sessionId: string,
@Body() createOrderDto: { items: any[] },
) {
return this.ordersService.createOrder(sessionId, createOrderDto.items);
}
@Post(':id/payment')
async processPayment(
@Param('id') orderId: string,
@Body() paymentDetails: any,
) {
return this.ordersService.processPayment(orderId, paymentDetails);
}
@Post(':id/complete')
async completeOrder(@Param('id') orderId: string) {
return this.ordersService.completeOrder(orderId);
}
@Get(':id')
async getOrderStatus(@Param('id') orderId: string) {
return this.ordersService.getOrderStatus(orderId);
}
}version: '3.8'
services:
postgres:
image: postgres:15
ports:
- '5432:5432'
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: ecommerce
volumes:
- postgres_data:/var/lib/postgresql/data
api:
build: .
ports:
- '3000:3000'
environment:
DB_HOST: postgres
DB_PORT: 5432
DB_USER: postgres
DB_PASSWORD: password
DB_NAME: ecommerce
depends_on:
- postgres
volumes:
postgres_data:# Start services
docker-compose up -d
# Install dependencies
npm install
# Run application
npm run start:devSekarang mari kita create comprehensive K6 tests yang simulate realistic e-commerce scenarios.
import http from 'k6/http';
import { check, group, sleep } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 50 },
{ duration: '3m', target: 100 },
{ duration: '2m', target: 150 },
{ duration: '3m', target: 150 },
{ duration: '2m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<500', 'p(99)<1000'],
http_req_failed: ['rate<0.05'],
},
};
export default function () {
const baseUrl = 'http://localhost:3000';
const sessionId = `session-${__VU}-${Date.now()}`;
group('Browse Products', function () {
const productsRes = http.get(`${baseUrl}/products?limit=20`, {
headers: { 'x-session-id': sessionId },
});
check(productsRes, {
'browse status 200': (r) => r.status === 200,
'browse response time < 300ms': (r) => r.timings.duration < 300,
});
sleep(2);
});
group('View Product Details', function () {
const detailRes = http.get(`${baseUrl}/products/1`, {
headers: { 'x-session-id': sessionId },
});
check(detailRes, {
'detail status 200': (r) => r.status === 200,
});
sleep(1);
});
group('Checkout', function () {
const createOrderRes = http.post(
`${baseUrl}/orders/create`,
JSON.stringify({
items: [
{ productId: 1, quantity: 2, price: 100 },
{ productId: 2, quantity: 1, price: 50 },
],
}),
{
headers: {
'x-session-id': sessionId,
'Content-Type': 'application/json',
},
}
);
check(createOrderRes, {
'create order status 200': (r) => r.status === 200,
'order has ID': (r) => r.json('id') !== undefined,
});
const orderId = createOrderRes.json('id');
sleep(1);
const paymentRes = http.post(
`${baseUrl}/orders/${orderId}/payment`,
JSON.stringify({
method: 'credit_card',
amount: createOrderRes.json('totalAmount'),
}),
{
headers: { 'Content-Type': 'application/json' },
}
);
check(paymentRes, {
'payment status 200': (r) => r.status === 200,
});
sleep(1);
const completeRes = http.post(
`${baseUrl}/orders/${orderId}/complete`,
null
);
check(completeRes, {
'complete order status 200': (r) => r.status === 200,
'order completed': (r) => r.json('status') === 'completed',
});
});
}import http from 'k6/http';
import { check, group } from 'k6';
import { Counter, Trend } from 'k6/metrics';
const checkoutErrors = new Counter('checkout_errors');
const checkoutTime = new Trend('checkout_duration');
export const options = {
stages: [
{ duration: '2m', target: 100 },
{ duration: '2m', target: 200 },
{ duration: '2m', target: 300 },
{ duration: '2m', target: 400 },
{ duration: '2m', target: 500 },
{ duration: '3m', target: 0 },
],
thresholds: {
http_req_failed: ['rate<0.1'],
checkout_errors: ['count<50'],
checkout_duration: ['p(95)<2000'],
},
};
export default function () {
const baseUrl = 'http://localhost:3000';
const sessionId = `stress-${__VU}-${Date.now()}`;
const startTime = new Date();
group('Stress Test - Full Checkout Flow', function () {
// Browse
http.get(`${baseUrl}/products?limit=20`, {
headers: { 'x-session-id': sessionId },
});
// Create order
const orderRes = http.post(
`${baseUrl}/orders/create`,
JSON.stringify({
items: [
{ productId: Math.floor(Math.random() * 100) + 1, quantity: 1, price: 100 },
],
}),
{
headers: {
'x-session-id': sessionId,
'Content-Type': 'application/json',
},
}
);
if (orderRes.status !== 200) {
checkoutErrors.add(1);
}
const orderId = orderRes.json('id');
// Process payment
const paymentRes = http.post(
`${baseUrl}/orders/${orderId}/payment`,
JSON.stringify({ method: 'credit_card' }),
{ headers: { 'Content-Type': 'application/json' } }
);
// Complete order
const completeRes = http.post(`${baseUrl}/orders/${orderId}/complete`, null);
const duration = new Date() - startTime;
checkoutTime.add(duration);
check(completeRes, {
'checkout successful': (r) => r.status === 200,
});
});
}import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 50 }, // Normal load
{ duration: '30s', target: 500 }, // Spike ke 500 VUs
{ duration: '1m', target: 500 }, // Maintain spike
{ duration: '30s', target: 50 }, // Back ke normal
{ duration: '1m', target: 0 }, // Cool down
],
thresholds: {
http_req_duration: ['p(95)<1000'],
http_req_failed: ['rate<0.2'],
},
};
export default function () {
const baseUrl = 'http://localhost:3000';
const sessionId = `spike-${__VU}-${Date.now()}`;
const res = http.get(`${baseUrl}/products?limit=20`, {
headers: { 'x-session-id': sessionId },
});
check(res, {
'spike test status 200': (r) => r.status === 200,
'spike test response time < 1s': (r) => r.timings.duration < 1000,
});
}import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { Counter, Trend, Rate } from 'k6/metrics';
const checkoutSuccessRate = new Rate('checkout_success');
const inventoryErrors = new Counter('inventory_errors');
const paymentFailures = new Counter('payment_failures');
const checkoutDuration = new Trend('checkout_duration');
export const options = {
scenarios: {
browsers: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 100 },
{ duration: '5m', target: 100 },
{ duration: '2m', target: 0 },
],
exec: 'browsing',
},
buyers: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 50 },
{ duration: '5m', target: 50 },
{ duration: '2m', target: 0 },
],
exec: 'checkout',
},
},
thresholds: {
http_req_failed: ['rate<0.1'],
checkout_success: ['rate>0.9'],
checkout_duration: ['p(95)<2000'],
},
};
export function browsing() {
const baseUrl = 'http://localhost:3000';
const sessionId = `browser-${__VU}-${Date.now()}`;
group('Product Browsing', function () {
// List products
const listRes = http.get(`${baseUrl}/products?limit=20&offset=0`, {
headers: { 'x-session-id': sessionId },
});
check(listRes, {
'list status 200': (r) => r.status === 200,
});
sleep(2);
// View random product
const productId = Math.floor(Math.random() * 100) + 1;
const detailRes = http.get(`${baseUrl}/products/${productId}`, {
headers: { 'x-session-id': sessionId },
});
check(detailRes, {
'detail status 200': (r) => r.status === 200,
});
sleep(3);
// Check availability
const availRes = http.get(
`${baseUrl}/products/${productId}/availability?quantity=1`,
{
headers: { 'x-session-id': sessionId },
}
);
check(availRes, {
'availability status 200': (r) => r.status === 200,
});
sleep(1);
});
}
export function checkout() {
const baseUrl = 'http://localhost:3000';
const sessionId = `buyer-${__VU}-${Date.now()}`;
const startTime = new Date();
group('Complete Checkout Flow', function () {
// Browse products
http.get(`${baseUrl}/products?limit=20`, {
headers: { 'x-session-id': sessionId },
});
sleep(1);
// Create order dengan multiple items
const orderRes = http.post(
`${baseUrl}/orders/create`,
JSON.stringify({
items: [
{ productId: 1, quantity: 2, price: 100 },
{ productId: 2, quantity: 1, price: 50 },
{ productId: 3, quantity: 1, price: 75 },
],
}),
{
headers: {
'x-session-id': sessionId,
'Content-Type': 'application/json',
},
}
);
if (orderRes.status !== 200) {
inventoryErrors.add(1);
checkoutSuccessRate.add(0);
return;
}
const orderId = orderRes.json('id');
sleep(1);
// Process payment
const paymentRes = http.post(
`${baseUrl}/orders/${orderId}/payment`,
JSON.stringify({
method: 'credit_card',
amount: orderRes.json('totalAmount'),
}),
{
headers: { 'Content-Type': 'application/json' },
}
);
if (paymentRes.status !== 200 || paymentRes.json('paymentStatus') === 'failed') {
paymentFailures.add(1);
checkoutSuccessRate.add(0);
return;
}
sleep(1);
// Complete order
const completeRes = http.post(`${baseUrl}/orders/${orderId}/complete`, null);
const duration = new Date() - startTime;
checkoutDuration.add(duration);
checkoutSuccessRate.add(completeRes.status === 200 ? 1 : 0);
check(completeRes, {
'order completed': (r) => r.status === 200,
'order status is completed': (r) => r.json('status') === 'completed',
});
});
}# Install K6
# macOS
brew install k6
# Linux
sudo apt-get install k6
# Windows
choco install k6
# Run user journey test
k6 run tests/user-journey-test.js
# Run stress test
k6 run tests/stress-test.js
# Run spike test
k6 run tests/spike-test.js
# Run complex scenario
k6 run tests/complex-scenario.js
# Run dengan custom options
k6 run tests/user-journey-test.js --vus 50 --duration 5m
# Run dengan output
k6 run tests/user-journey-test.js --out json=results.json// ❌ Wrong - sudden spike
export const options = {
vus: 1000,
duration: '1m',
};
// ✅ Correct - gradual ramp-up
export const options = {
stages: [
{ duration: '2m', target: 100 },
{ duration: '2m', target: 500 },
{ duration: '2m', target: 1000 },
{ duration: '2m', target: 0 },
],
};// ❌ Wrong - no delays between requests
export default function () {
http.get('https://api.example.com/products');
http.get('https://api.example.com/products/1');
http.get('https://api.example.com/cart/add');
}
// ✅ Correct - realistic think time
export default function () {
http.get('https://api.example.com/products');
sleep(2);
http.get('https://api.example.com/products/1');
sleep(1);
http.get('https://api.example.com/cart/add');
}// ❌ Wrong - same data every time
export default function () {
http.post('https://api.example.com/orders', {
productId: 1,
quantity: 1,
});
}
// ✅ Correct - varied data
export default function () {
http.post('https://api.example.com/orders', {
productId: Math.floor(Math.random() * 1000) + 1,
quantity: Math.floor(Math.random() * 10) + 1,
});
}// ❌ Wrong - no validation
export default function () {
http.get('https://api.example.com/users');
}
// ✅ Correct - validate responses
export default function () {
const res = http.get('https://api.example.com/users');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
}Mulai dengan small loads dan gradually increase untuk find breaking points.
export const options = {
stages: [
{ duration: '1m', target: 10 },
{ duration: '1m', target: 50 },
{ duration: '1m', target: 100 },
{ duration: '1m', target: 0 },
],
};Simulate actual user behavior dengan think time dan realistic workflows.
export default function () {
group('Browse', () => {
http.get('https://api.example.com/products');
sleep(2);
});
group('View Details', () => {
http.get('https://api.example.com/products/1');
sleep(1);
});
group('Checkout', () => {
http.post('https://api.example.com/checkout', {});
sleep(1);
});
}Track business-relevant metrics alongside technical metrics.
const checkoutSuccess = new Rate('checkout_success');
const paymentFailures = new Counter('payment_failures');
export default function () {
// Track metrics
checkoutSuccess.add(success ? 1 : 0);
paymentFailures.add(failed ? 1 : 0);
}Define thresholds untuk fail tests automatically.
export const options = {
thresholds: {
http_req_failed: ['rate<0.05'],
http_req_duration: ['p(95)<500'],
checkout_success: ['rate>0.95'],
},
};Run multiple test types untuk understand system behavior.
# Load test
k6 run tests/load-test.js
# Stress test
k6 run tests/stress-test.js
# Spike test
k6 run tests/spike-test.jsK6 transforms performance testing dari specialized skill menjadi developer practice. Memahami load testing fundamentals—VUs, stages, checks, dan thresholds—enable Anda untuk build reliable systems.
E-commerce platform example demonstrate production patterns:
Key takeaways:
Next steps:
K6 makes performance testing accessible dan practical. Master it, dan Anda akan build systems yang scale reliably under real-world traffic patterns.