Load Testing cho hệ thống phân tán — k6, NBomber và chiến lược kiểm thử hiệu năng

Posted on: 4/25/2026 1:14:20 PM

Bạn vừa deploy xong một microservice mới, mọi thứ hoạt động trơn tru trên staging — response time dưới 200ms, không có lỗi. Nhưng khi lên production với 10.000 concurrent users, hệ thống sụp đổ trong vòng 3 phút. Load testing chính là lớp phòng thủ giúp bạn phát hiện những điểm yếu này trước khi chúng trở thành sự cố production.

Bài viết này đi sâu vào chiến lược kiểm thử hiệu năng cho hệ thống phân tán, so sánh hai công cụ hàng đầu — Grafana k6 (JavaScript/TypeScript) và NBomber (.NET/C#) — cùng những pattern thực chiến để tích hợp load testing vào CI/CD pipeline.

72.8%Dev teams ưu tiên AI-powered testing (2025)
70%k6 tiết kiệm CPU so với alternatives
83%API vẫn sử dụng REST protocol
340%GraphQL tăng trưởng tại Fortune 500

1. Tại sao Load Testing quan trọng với hệ thống phân tán?

Trong kiến trúc monolith, bottleneck thường nằm ở một điểm duy nhất — database hoặc application server. Nhưng với hệ thống phân tán, vấn đề phức tạp hơn nhiều: một service chậm có thể gây cascading failure qua toàn bộ call chain, connection pool exhaustion ở một node ảnh hưởng đến hàng chục service downstream.

graph LR
    A[Client
10K req/s] --> B[API Gateway] B --> C[Auth Service] B --> D[Product Service] B --> E[Order Service] D --> F[(Database)] E --> F E --> G[Payment Service] G --> H[External Payment API] style A fill:#e94560,stroke:#fff,color:#fff style B fill:#2c3e50,stroke:#fff,color:#fff style C fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style D fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style E fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style F fill:#16213e,stroke:#fff,color:#fff style G fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style H fill:#ff9800,stroke:#fff,color:#fff

Kiến trúc microservices điển hình — mỗi node đều có thể là bottleneck dưới tải cao

Load testing cho hệ thống phân tán cần trả lời được những câu hỏi sau:

  • Throughput tối đa — Hệ thống xử lý được bao nhiêu request/giây trước khi response time vượt ngưỡng chấp nhận?
  • Điểm gãy (breaking point) — Ở mức tải nào hệ thống bắt đầu trả lỗi hoặc timeout?
  • Cascading failure — Khi một service bị quá tải, ảnh hưởng lan rộng đến đâu?
  • Resource saturation — CPU, memory, connection pool cạn kiệt ở service nào trước?
  • Recovery time — Sau khi tải giảm, hệ thống mất bao lâu để phục hồi về trạng thái bình thường?

2. Các loại kiểm thử hiệu năng

Mỗi loại kiểm thử phục vụ một mục đích khác nhau. Hiểu rõ từng loại giúp bạn thiết kế test suite phù hợp với yêu cầu SLA của hệ thống.

graph TB
    subgraph Types["Các loại Performance Testing"]
        A["🔥 Smoke Test
Kiểm tra cơ bản
1-5 VUs"] B["📊 Load Test
Tải trung bình
Target VUs"] C["💪 Stress Test
Vượt ngưỡng
2-3x Target"] D["⚡ Spike Test
Tăng đột biến
0 → Max → 0"] E["🕐 Soak Test
Chạy dài hạn
4-24 giờ"] F["🎯 Breakpoint Test
Tìm giới hạn
Ramp liên tục"] end style A fill:#4CAF50,stroke:#fff,color:#fff style B fill:#2196F3,stroke:#fff,color:#fff style C fill:#ff9800,stroke:#fff,color:#fff style D fill:#e94560,stroke:#fff,color:#fff style E fill:#9C27B0,stroke:#fff,color:#fff style F fill:#16213e,stroke:#fff,color:#fff

6 loại performance testing chính — từ kiểm tra cơ bản đến tìm điểm gãy hệ thống

Loại testMục tiêuMô hình tảiThời gianKhi nào dùng
Smoke TestVerify script chạy đúng1-5 VUs cố định1-3 phútSau mỗi thay đổi test script
Load TestĐánh giá hiệu năng bình thườngRamp up → steady → ramp down10-30 phútMỗi sprint/release
Stress TestTìm giới hạn hệ thốngTải vượt 2-3x bình thường15-30 phútTrước launch lớn
Spike TestKiểm tra phản ứng với tải đột biến0 → peak → 0 trong vài giây5-10 phútFlash sale, viral event
Soak TestPhát hiện memory leak, connection leakTải trung bình kéo dài4-24 giờTrước production release
Breakpoint TestXác định maximum capacityRamp liên tục đến khi failKhông xác địnhCapacity planning

💡 Thứ tự kiểm thử khuyến nghị

Luôn bắt đầu với Smoke Test để đảm bảo script hoạt động đúng, sau đó Load Test để có baseline, rồi mới chuyển sang Stress/Spike/Soak cho các kịch bản nâng cao. Đừng nhảy thẳng vào stress test — bạn sẽ tốn thời gian debug test script thay vì debug hệ thống.

3. Grafana k6 — Load testing hiện đại với JavaScript/TypeScript

3.1 Tại sao chọn k6?

k6 là công cụ load testing mã nguồn mở từ Grafana Labs, viết bằng Go nhưng cho phép scripting bằng JavaScript/TypeScript. Điểm mạnh lớn nhất của k6 là engine hiệu năng cao — sử dụng ít CPU hơn 70% so với các tool tương tự, cho phép tạo hàng nghìn virtual users trên một máy.

Từ phiên bản 1.0, k6 hỗ trợ TypeScript native — bạn có type safety, IDE autocomplete mà không cần build step riêng.

3.2 Viết test script đầu tiên

// load-test.ts — k6 load test cho API endpoint
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';

// Custom metrics
const errorRate = new Rate('errors');
const responseTime = new Trend('response_time_ms');

// Test configuration
export const options = {
  scenarios: {
    average_load: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 100 },  // Ramp up to 100 VUs
        { duration: '5m', target: 100 },  // Stay at 100 VUs
        { duration: '2m', target: 0 },    // Ramp down
      ],
    },
  },
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'],
    errors: ['rate<0.01'],  // Error rate < 1%
    response_time_ms: ['p(95)<400'],
  },
};

export default function () {
  const res = http.get('https://api.example.com/products', {
    headers: { 'Authorization': `Bearer ${__ENV.API_TOKEN}` },
    tags: { name: 'GetProducts' },
  });

  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
    'body has products': (r) => JSON.parse(r.body).length > 0,
  });

  errorRate.add(res.status !== 200);
  responseTime.add(res.timings.duration);

  sleep(1); // Think time giữa các request
}

3.3 Scenarios và Executors nâng cao

k6 cung cấp nhiều loại executor cho các mô hình tải khác nhau:

export const options = {
  scenarios: {
    // Scenario 1: Constant arrival rate — kiểm soát RPS
    constant_rps: {
      executor: 'constant-arrival-rate',
      rate: 200,           // 200 iterations/giây
      timeUnit: '1s',
      duration: '5m',
      preAllocatedVUs: 50,
      maxVUs: 200,
    },
    // Scenario 2: Spike test
    spike: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '10s', target: 0 },
        { duration: '10s', target: 500 },  // Spike lên 500
        { duration: '30s', target: 500 },
        { duration: '10s', target: 0 },    // Drop về 0
      ],
      startTime: '6m', // Bắt đầu sau scenario 1
    },
  },
};

Constant VUs vs Constant Arrival Rate

Constant VUs (ramping-vus): mỗi VU hoàn thành 1 iteration rồi bắt đầu iteration mới. RPS phụ thuộc vào response time — server chậm → RPS giảm. Dùng khi muốn mô phỏng số lượng người dùng đồng thời.

Constant Arrival Rate: đảm bảo chính xác N iterations/giây bất kể response time. k6 sẽ spawn thêm VUs nếu cần. Dùng khi muốn đo hệ thống dưới throughput cố định — phù hợp cho SLA testing.

3.4 Browser testing với k6

k6 tích hợp browser API tương thích Playwright, cho phép chạy hybrid test — kết hợp protocol-level (HTTP) và browser-level trong cùng một kịch bản:

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

export const options = {
  scenarios: {
    browser_test: {
      executor: 'constant-vus',
      vus: 5,
      duration: '3m',
      options: { browser: { type: 'chromium' } },
    },
    api_test: {
      executor: 'constant-arrival-rate',
      rate: 100,
      timeUnit: '1s',
      duration: '3m',
      preAllocatedVUs: 20,
    },
  },
};

export async function browser_test() {
  const page = await browser.newPage();
  await page.goto('https://app.example.com/dashboard');

  // Đo Web Vitals thực tế
  const lcp = await page.evaluate(() => {
    return new Promise(resolve => {
      new PerformanceObserver(list => {
        const entries = list.getEntries();
        resolve(entries[entries.length - 1].startTime);
      }).observe({ type: 'largest-contentful-paint', buffered: true });
    });
  });

  console.log(`LCP: ${lcp}ms`);
  await page.close();
}

4. NBomber — Load testing thuần .NET cho C# developer

4.1 Khi nào chọn NBomber?

Nếu team bạn chủ yếu sử dụng .NET và muốn viết load test bằng C#/F# — tận dụng IDE, debug, NuGet packages — thì NBomber là lựa chọn lý tưởng. NBomber hoạt động như một thư viện .NET, cài qua NuGet, và test scenario chạy như unit test thông thường.

4.2 Viết scenario với NBomber

using NBomber.CSharp;
using NBomber.Http.CSharp;

var httpClient = new HttpClient();

var scenario = Scenario.Create("get_products", async context =>
{
    var request = Http.CreateRequest("GET", "https://api.example.com/products")
        .WithHeader("Authorization", "Bearer " + Environment.GetEnvironmentVariable("API_TOKEN"));

    var response = await Http.Send(httpClient, request);

    return response;
})
.WithLoadSimulations(
    Simulation.RampingInject(rate: 100, interval: TimeSpan.FromSeconds(1),
                             during: TimeSpan.FromMinutes(2)),
    Simulation.Inject(rate: 100, interval: TimeSpan.FromSeconds(1),
                      during: TimeSpan.FromMinutes(5)),
    Simulation.RampingInject(rate: 0, interval: TimeSpan.FromSeconds(1),
                             during: TimeSpan.FromMinutes(2))
);

NBomberRunner
    .RegisterScenarios(scenario)
    .WithReportFormats(ReportFormat.Html, ReportFormat.Csv)
    .WithReportFolder("./reports")
    .Run();

4.3 Test đa protocol

Sức mạnh của NBomber là khả năng test bất kỳ protocol nào — HTTP, gRPC, WebSocket, database, message queue — trong cùng một scenario:

var httpStep = Scenario.Create("mixed_workload", async context =>
{
    // Step 1: Gọi REST API
    var apiResponse = await Http.Send(httpClient,
        Http.CreateRequest("GET", "https://api.example.com/products"));

    if (apiResponse.StatusCode != "200")
        return Response.Fail();

    // Step 2: Gọi gRPC service
    var grpcResponse = await grpcClient.GetProductDetailsAsync(
        new ProductRequest { Id = context.ScenarioInfo.ThreadNumber });

    // Step 3: Publish message to RabbitMQ
    channel.BasicPublish(exchange: "", routingKey: "orders",
        body: Encoding.UTF8.GetBytes($"order-{context.InvocationNumber}"));

    return Response.Ok(statusCode: "200",
        sizeBytes: apiResponse.SizeBytes + grpcResponse.CalculateSize());
})
.WithLoadSimulations(
    Simulation.KeepConstant(copies: 50, during: TimeSpan.FromMinutes(10))
);

5. So sánh k6 vs NBomber

Tiêu chíGrafana k6NBomber
Ngôn ngữJavaScript / TypeScriptC# / F#
EngineGo (goroutines).NET (async/await)
Cài đặtBinary standaloneNuGet package
IDE supportVS Code + extensionsVisual Studio / Rider (full debug)
Browser testingCó (Chromium built-in)Có (qua Playwright NuGet)
ProtocolHTTP, WebSocket, gRPC (extension)Bất kỳ (HTTP, gRPC, WS, DB, MQ...)
Distributed testingk6 Cloud hoặc k6-operator (K8s)NBomber Cluster
ReportingGrafana dashboards, JSON, CSVHTML, CSV, TXT, Markdown
CI/CDNative (exit code dựa trên thresholds)xUnit/NUnit runner, threshold assertions
GiáOSS miễn phí, Cloud trả phíFree (personal), $99/tháng (business)
Phù hợpTeam đa ngôn ngữ, DevOps-drivenTeam .NET, developer-driven testing

💡 Khuyến nghị thực tế

Nếu team bạn chủ yếu dùng .NET và muốn load test chạy như unit test trong CI pipeline → chọn NBomber. Nếu team đa ngôn ngữ hoặc ưu tiên scripting nhanh với TypeScript → chọn k6. Nhiều tổ chức lớn sử dụng cả hai: k6 cho API-level testing nhanh trong CI, NBomber cho integration testing phức tạp cần debug.

6. Chiến lược Load Testing cho Microservices

6.1 Mô hình kiểm thử phân tầng

graph TB
    subgraph L1["Tầng 1 — Component Testing"]
        A["Test từng service
riêng lẻ"] B["Mock dependencies"] C["Đo baseline
response time"] end subgraph L2["Tầng 2 — Integration Testing"] D["Test chuỗi 2-3
services"] E["Real dependencies"] F["Đo latency
end-to-end"] end subgraph L3["Tầng 3 — System Testing"] G["Test toàn bộ
hệ thống"] H["Production-like
traffic"] I["Đo throughput
& error rate"] end L1 --> L2 --> L3 style A fill:#4CAF50,stroke:#fff,color:#fff style B fill:#4CAF50,stroke:#fff,color:#fff style C fill:#4CAF50,stroke:#fff,color:#fff style D fill:#2196F3,stroke:#fff,color:#fff style E fill:#2196F3,stroke:#fff,color:#fff style F fill:#2196F3,stroke:#fff,color:#fff style G fill:#e94560,stroke:#fff,color:#fff style H fill:#e94560,stroke:#fff,color:#fff style I fill:#e94560,stroke:#fff,color:#fff

Mô hình 3 tầng kiểm thử hiệu năng cho microservices

6.2 Thiết kế test scenario sát thực tế

Sai lầm phổ biến nhất khi load testing là tạo traffic pattern không giống thực tế. Production traffic hiếm khi đồng đều — thường có hot paths (20% endpoints nhận 80% traffic) và cold paths.

// k6: Mô phỏng traffic pattern thực tế
export const options = {
  scenarios: {
    // 70% traffic: Browse products (đọc)
    browse: {
      executor: 'constant-arrival-rate',
      rate: 700,
      timeUnit: '1s',
      duration: '10m',
      preAllocatedVUs: 100,
      exec: 'browseProducts',
    },
    // 20% traffic: Search (đọc + tính toán)
    search: {
      executor: 'constant-arrival-rate',
      rate: 200,
      timeUnit: '1s',
      duration: '10m',
      preAllocatedVUs: 50,
      exec: 'searchProducts',
    },
    // 10% traffic: Checkout (ghi)
    checkout: {
      executor: 'constant-arrival-rate',
      rate: 100,
      timeUnit: '1s',
      duration: '10m',
      preAllocatedVUs: 30,
      exec: 'checkout',
    },
  },
};

export function browseProducts() {
  const categoryId = Math.floor(Math.random() * 20) + 1;
  http.get(`${BASE_URL}/products?category=${categoryId}`);
  sleep(Math.random() * 3 + 1); // Think time 1-4s
}

export function searchProducts() {
  const terms = ['laptop', 'phone', 'tablet', 'headphone', 'camera'];
  const q = terms[Math.floor(Math.random() * terms.length)];
  http.get(`${BASE_URL}/search?q=${q}`);
  sleep(Math.random() * 2 + 0.5);
}

export function checkout() {
  const payload = JSON.stringify({
    productId: Math.floor(Math.random() * 1000) + 1,
    quantity: Math.floor(Math.random() * 3) + 1,
  });
  http.post(`${BASE_URL}/orders`, payload, {
    headers: { 'Content-Type': 'application/json' },
  });
  sleep(Math.random() * 5 + 2); // Think time dài hơn cho checkout
}

6.3 Thresholds và SLA validation

Thresholds chính là cách biến load test từ "báo cáo" thành "quality gate" — nếu metric vượt ngưỡng, test fail và CI pipeline dừng lại.

export const options = {
  thresholds: {
    // Global thresholds
    http_req_duration: [
      'p(50)<200',   // 50th percentile < 200ms
      'p(95)<500',   // 95th percentile < 500ms
      'p(99)<1000',  // 99th percentile < 1s
    ],
    http_req_failed: ['rate<0.01'],  // <1% lỗi

    // Per-endpoint thresholds
    'http_req_duration{name:GetProducts}': ['p(95)<300'],
    'http_req_duration{name:Checkout}': ['p(95)<800'],
    'http_req_duration{name:Search}': ['p(95)<600'],

    // Custom metrics
    'errors{scenario:checkout}': ['rate<0.005'], // Checkout: <0.5% lỗi
  },
};

7. Tích hợp Load Testing vào CI/CD Pipeline

graph LR
    A[Code Push] --> B[Build & Unit Test]
    B --> C[Deploy to Staging]
    C --> D[Smoke Test
k6 / NBomber] D -->|Pass| E[Load Test
10 min] D -->|Fail| F[Alert & Stop] E -->|Thresholds OK| G[Deploy to Prod] E -->|Thresholds Fail| F G --> H[Canary Test
5% traffic] H -->|Healthy| I[Full Rollout] H -->|Degraded| J[Rollback] style A fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style D fill:#4CAF50,stroke:#fff,color:#fff style E fill:#2196F3,stroke:#fff,color:#fff style F fill:#e94560,stroke:#fff,color:#fff style G fill:#4CAF50,stroke:#fff,color:#fff style H fill:#ff9800,stroke:#fff,color:#fff style I fill:#4CAF50,stroke:#fff,color:#fff style J fill:#e94560,stroke:#fff,color:#fff

Load testing tích hợp CI/CD — smoke test gate trước, load test gate sau

7.1 GitHub Actions + k6

# .github/workflows/load-test.yml
name: Load Test
on:
  pull_request:
    branches: [main]

jobs:
  load-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy to staging
        run: ./deploy-staging.sh

      - 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 C5AD17C747E3415A3642D57D77C6C491D6AC1D68
          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 smoke test
        run: k6 run --tag testid=smoke tests/smoke.ts
        env:
          API_TOKEN: ${{ secrets.STAGING_API_TOKEN }}

      - name: Run load test
        run: k6 run tests/load.ts
        env:
          API_TOKEN: ${{ secrets.STAGING_API_TOKEN }}

      - name: Upload results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: k6-results
          path: results/

7.2 .NET CI + NBomber

// LoadTests/ProductApiLoadTest.cs — chạy như xUnit test
public class ProductApiLoadTest
{
    [Fact]
    public void Products_endpoint_handles_100rps()
    {
        var httpClient = new HttpClient();

        var scenario = Scenario.Create("get_products", async context =>
        {
            var response = await Http.Send(httpClient,
                Http.CreateRequest("GET", "https://staging.api.example.com/products"));
            return response;
        })
        .WithLoadSimulations(
            Simulation.Inject(rate: 100, interval: TimeSpan.FromSeconds(1),
                              during: TimeSpan.FromMinutes(5))
        );

        var stats = NBomberRunner
            .RegisterScenarios(scenario)
            .Run();

        var scnStats = stats.ScenarioStats[0];

        // Assert SLA
        Assert.True(scnStats.Ok.Latency.Percent95 < 500,
            $"P95 latency {scnStats.Ok.Latency.Percent95}ms exceeds 500ms threshold");
        Assert.True(scnStats.Fail.Request.Percent < 1,
            $"Error rate {scnStats.Fail.Request.Percent}% exceeds 1% threshold");
    }
}

8. Phân tích kết quả và debug bottleneck

8.1 Các metric quan trọng cần theo dõi

MetricÝ nghĩaNgưỡng đề xuấtKhi vượt ngưỡng
p50 (median)Response time trung vị< 200msHệ thống chậm toàn diện
p9595% request nhanh hơn giá trị này< 500msTail latency ảnh hưởng UX
p9999% request nhanh hơn giá trị này< 1000msOutlier do GC, cold start, DB lock
Error rate% request lỗi (4xx, 5xx, timeout)< 1%Service overloaded hoặc bug
Throughput (RPS)Request per second thực tế≥ target SLABottleneck ở compute hoặc I/O
VU activeSố virtual users đang hoạt độngTheo kế hoạchVU stuck = connection leak

8.2 Pattern phát hiện bottleneck

⚠️ Những dấu hiệu phổ biến

Response time tăng tuyến tính theo VU → CPU-bound: application đang serialize xử lý, cần review async code hoặc scale horizontal.

Response time tăng đột ngột tại ngưỡng N VUs → Connection pool exhaustion: database hoặc downstream service hết connection. Kiểm tra MaxPoolSize, HttpClient lifecycle.

Error rate spike kèm response time giảm → Circuit breaker đang mở hoặc request bị reject sớm. Tốt cho hệ thống (self-protection) nhưng cần tune ngưỡng.

Memory tăng dần trong soak test → Memory leak: thường do event handler không unsubscribe, HttpClient tạo mới liên tục, hoặc cache không có eviction policy.

9. Best Practices cho Production Load Testing

9.1 Quy tắc vàng

  1. Test trên môi trường giống production — Cùng hardware spec, cùng data volume, cùng network topology. Test trên laptop với 100 rows trong DB không nói lên điều gì.
  2. Sử dụng dữ liệu thực tế — Tạo dataset phản ánh phân bố thực: product catalog đầy đủ, user profiles đa dạng, search queries từ access log thật.
  3. Đo từ phía client, không phải server — Server metrics cho thấy response time xử lý, nhưng user trải nghiệm bao gồm cả network latency, DNS resolution, TLS handshake.
  4. Warm up trước khi đo — JIT compilation (.NET), connection pool initialization, cache warming đều ảnh hưởng đến kết quả nếu đo ngay từ request đầu tiên.
  5. Chạy nhiều lần, lấy trung bình — Kết quả một lần chạy có variance cao. Chạy ít nhất 3 lần với cùng config, loại bỏ outlier, báo cáo kết quả trung bình.

9.2 Anti-patterns cần tránh

  1. Test không có think time — User thật không bắn request liên tục. Thêm sleep(1-5s) giữa các request để mô phỏng reading time.
  2. Hardcode test data — Gọi cùng một API endpoint với cùng parameter → cache hit 100% → kết quả không phản ánh thực tế.
  3. Bỏ qua ramp-up period — Bắn 10.000 VUs cùng lúc tạo thundering herd — không phải load test mà là DDoS. Luôn ramp up dần.
  4. Chỉ test happy path — Production có 404, 401, timeout, malformed request. Test script nên bao gồm error scenarios.
  5. Không monitor resource phía server — Load test chỉ cho output metrics. Cần kết hợp APM (Application Performance Monitoring) để biết CPU/memory/disk I/O phía server đang ở mức nào.

10. Kết luận

Load testing không phải là hoạt động chạy một lần trước launch rồi quên đi. Trong hệ thống phân tán, mỗi lần thêm service mới, thay đổi schema database, hoặc upgrade dependency đều có thể ảnh hưởng đến hiệu năng tổng thể. Tích hợp load testing vào CI/CD pipeline — bắt đầu với smoke test gate, tiến tới load test gate — là cách duy nhất để đảm bảo performance regression được phát hiện sớm.

Dù bạn chọn k6 cho sự linh hoạt của JavaScript/TypeScript hay NBomber cho sức mạnh của .NET ecosystem, điều quan trọng nhất là bắt đầu — một smoke test đơn giản trong CI pipeline còn có giá trị hơn một kế hoạch load testing hoàn hảo mà không bao giờ được thực hiện.

Tham khảo