Chuyển đến nội dung chính

Lesson 6: API Performance Testing — REST, GraphQL, gRPC, WebSocket

Load testing REST APIs, GraphQL query complexity, gRPC streaming, WebSocket connection scaling, event-driven throughput testing.

🔒 DevSecOps — Lesson 6 Lesson 6: API Performance Testing — REST, GraphQL, gRPC, WebSocket__HTMLTAG_55___

Performance Testing & Pentest: Enterprise Standard Process 2026

Part 2: Advanced Performance Testing

xdev.asia

1. REST API Performance Testing

// k6 — REST API load test với correlation
import http from 'k6/http';
import { check, group } from 'k6';

export const options = {
  scenarios: {
    rest_api: {
      executor: 'constant-arrival-rate',
      rate: 200,           // 200 RPS
      timeUnit: '1s',
      duration: '10m',
      preAllocatedVUs: 100,
    },
  },
  thresholds: {
    'http_req_duration{name:ListProducts}': ['p(95)<150'],
    'http_req_duration{name:GetProduct}': ['p(95)<100'],
    'http_req_duration{name:CreateOrder}': ['p(95)<500'],
  },
};

export default function () {
  group('REST API Flow', () => {
    // 1. List products (paginated)
    const listRes = http.get(`${BASE_URL}/api/products?page=1&limit=20`, {
      tags: { name: 'ListProducts' },
    });
    check(listRes, { 'list 200': (r) => r.status === 200 });

    // 2. Correlation: lấy ID từ response trước
    const products = JSON.parse(listRes.body).data;
    const productId = products[0]?.id;

    if (productId) {
      const detailRes = http.get(`${BASE_URL}/api/products/${productId}`, {
        tags: { name: 'GetProduct' },
      });
      check(detailRes, { 'detail 200': (r) => r.status === 200 });
    }

    // 3. POST with JSON body
    const orderRes = http.post(`${BASE_URL}/api/orders`, JSON.stringify({
      productId,
      quantity: 1,
    }), {
      headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
      tags: { name: 'CreateOrder' },
    });
    check(orderRes, { 'order 201': (r) => r.status === 201 });
  });
}

2. GraphQL Performance Testing

// k6 — GraphQL load test
import http from 'k6/http';
import { check } from 'k6';

const GRAPHQL_URL = `${__ENV.BASE_URL}/graphql`;

// Query đơn giản
function listProducts() {
  const query = `
    query ListProducts($limit: Int!, $offset: Int!) {
      products(limit: $limit, offset: $offset) {
        id
        name
        price
        category { name }
      }
    }
  `;
  return http.post(GRAPHQL_URL, JSON.stringify({
    query,
    variables: { limit: 20, offset: 0 },
  }), {
    headers: { 'Content-Type': 'application/json' },
    tags: { name: 'GQL_ListProducts' },
  });
}

// Query phức tạp — test N+1 performance
function deepNestedQuery() {
  const query = `
    query DeepQuery {
      users(limit: 50) {
        id
        name
        orders(limit: 10) {
          id
          total
          items {
            product {
              name
              reviews(limit: 5) {
                rating
                author { name }
              }
            }
          }
        }
      }
    }
  `;
  return http.post(GRAPHQL_URL, JSON.stringify({ query }), {
    headers: { 'Content-Type': 'application/json' },
    tags: { name: 'GQL_DeepNested' },
  });
}

export default function () {
  // Test query complexity impact
  const simpleRes = listProducts();
  check(simpleRes, {
    'simple query < 200ms': (r) => r.timings.duration < 200,
    'no errors': (r) => !JSON.parse(r.body).errors,
  });

  const deepRes = deepNestedQuery();
  check(deepRes, {
    'deep query < 2000ms': (r) => r.timings.duration < 2000,
  });
}

3. gRPC Performance Testing

// k6 — gRPC load test (k6 có built-in gRPC support)
import grpc from 'k6/net/grpc';
import { check } from 'k6';

const client = new grpc.Client();
client.load(['proto'], 'product.proto');

export const options = {
  scenarios: {
    grpc_test: {
      executor: 'constant-arrival-rate',
      rate: 500,
      timeUnit: '1s',
      duration: '5m',
      preAllocatedVUs: 100,
    },
  },
  thresholds: {
    grpc_req_duration: ['p(95)<100', 'p(99)<300'],
  },
};

export default function () {
  client.connect('localhost:50051', { plaintext: true });

  // Unary RPC
  const response = client.invoke('product.ProductService/GetProduct', {
    id: 'prod-001',
  });
  check(response, {
    'status OK': (r) => r.status === grpc.StatusOK,
    'has product': (r) => r.message.name !== '',
  });

  // Server streaming
  const stream = client.invoke('product.ProductService/ListProducts', {
    category: 'electronics',
    limit: 100,
  });
  check(stream, {
    'stream OK': (r) => r.status === grpc.StatusOK,
  });

  client.close();
}

4. WebSocket Performance Testing

// k6 — WebSocket connection scaling
import ws from 'k6/ws';
import { check } from 'k6';
import { Counter, Trend } from 'k6/metrics';

const wsMessages = new Counter('ws_messages_received');
const wsLatency = new Trend('ws_message_latency');

export const options = {
  stages: [
    { duration: '2m', target: 100 },   // 100 concurrent WebSocket connections
    { duration: '5m', target: 500 },   // Scale to 500
    { duration: '5m', target: 1000 },  // Scale to 1000
    { duration: '2m', target: 0 },
  ],
};

export default function () {
  const url = 'wss://api.example.com/ws/chat';
  
  const res = ws.connect(url, {
    headers: { 'Authorization': `Bearer ${token}` },
  }, function (socket) {
    socket.on('open', () => {
      // Join room
      socket.send(JSON.stringify({
        event: 'joinRoom',
        data: { room: 'general' },
      }));

      // Send messages periodically
      socket.setInterval(() => {
        const sendTime = Date.now();
        socket.send(JSON.stringify({
          event: 'sendMessage',
          data: { room: 'general', message: `Msg-${sendTime}`, ts: sendTime },
        }));
      }, 2000);
    });

    socket.on('message', (msg) => {
      wsMessages.add(1);
      const data = JSON.parse(msg);
      if (data.ts) {
        wsLatency.add(Date.now() - data.ts);
      }
    });

    socket.setTimeout(() => socket.close(), 60000); // Close after 60s
  });

  check(res, {
    'WS connected': (r) => r && r.status === 101,
  });
}

5. Event-driven Systems Testing

// k6 xk6-kafka — Kafka producer/consumer throughput
import { Writer, Reader, Connection } from 'k6/x/kafka';

const writer = new Writer({
  brokers: ['kafka:9092'],
  topic: 'orders',
});

const reader = new Reader({
  brokers: ['kafka:9092'],
  topic: 'order-events',
  groupID: 'load-test-consumer',
});

export default function () {
  // Produce messages
  const messages = Array.from({ length: 100 }, (_, i) => ({
    key: `order-${Date.now()}-${i}`,
    value: JSON.stringify({
      orderId: `ORD-${Date.now()}`,
      amount: Math.random() * 1000,
      timestamp: new Date().toISOString(),
    }),
  }));
  
  writer.produce({ messages });

  // Consume and measure lag
  const consumed = reader.consume({ limit: 100 });
  // Measure consumer lag, processing time, throughput
}

export function teardown() {
  writer.close();
  reader.close();
}

6. Summary

  • REST: Correlation, parameterization, per-endpoint thresholds
  • GraphQL: Test query complexity impact, N+1 detection, batch queries
  • gRPC: Built-in k6 support, streaming performance, serialization overhead
  • WebSocket: Connection scaling, message latency, reconnection handling
  • Event-driven: Producer/consumer throughput, consumer lag, scaling partition

The next article will explore Database Performance Testing.