Load Testing Strategies for Web Applications: Find Breaking Points Before Users Do

Load test web applications with k6, Artillery, and Locust. Covers test design, traffic modeling, performance baselines, and result interpretation strategies.

E
ECOSIRE Research and Development Team
|March 16, 20267 min read1.5k Words|

Part of our Performance & Scalability series

Read the complete guide

Load Testing Strategies for Web Applications: Find Breaking Points Before Users Do

80% of performance issues are discovered by end users, not by testing. Load testing flips this ratio by simulating real-world traffic patterns against your application before users encounter problems. The difference between a site that handles Black Friday traffic and one that crashes is almost always whether someone ran a load test first.

This guide covers load testing methodology, tool selection, test design, and result interpretation for web applications, eCommerce platforms, and ERP systems.

Key Takeaways

  • Load testing should simulate realistic user behavior, not just hammer a single endpoint
  • Establish performance baselines before optimizing --- you cannot improve what you have not measured
  • Run load tests in a production-like environment; staging results may not reflect production behavior
  • Automate load tests in CI/CD to catch performance regressions before deployment

Types of Load Tests

Test TypePurposeDurationLoad Pattern
Smoke testVerify basic functionality under minimal load1-2 minutes1-5 users
Load testValidate performance under expected traffic10-30 minutesNormal traffic
Stress testFind the breaking point15-30 minutesGradually increasing
Spike testTest sudden traffic surges5-10 minutesSudden jump
Soak testDetect memory leaks and degradation2-8 hoursSustained normal load

Load Testing with k6

Basic Load Test

// k6/load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 50 },   // Ramp up to 50 users
    { duration: '5m', target: 50 },   // Stay at 50 users
    { duration: '2m', target: 100 },  // Ramp up to 100 users
    { duration: '5m', target: 100 },  // Stay at 100 users
    { duration: '2m', target: 0 },    // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'],
    http_req_failed: ['rate<0.01'],
    http_reqs: ['rate>100'],
  },
};

export default function () {
  // Simulate realistic user behavior
  const homeResponse = http.get('https://example.com/');
  check(homeResponse, {
    'homepage status is 200': (r) => r.status === 200,
    'homepage loads in under 1s': (r) => r.timings.duration < 1000,
  });
  sleep(Math.random() * 3 + 1); // 1-4 seconds think time

  const productsResponse = http.get('https://example.com/api/v1/products');
  check(productsResponse, {
    'products API is 200': (r) => r.status === 200,
    'products API under 500ms': (r) => r.timings.duration < 500,
  });
  sleep(Math.random() * 2 + 1);
}

eCommerce User Journey Test

// k6/ecommerce-journey.js
import http from 'k6/http';
import { check, group, sleep } from 'k6';

export const options = {
  scenarios: {
    browsing: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '5m', target: 200 },
        { duration: '10m', target: 200 },
        { duration: '5m', target: 0 },
      ],
      exec: 'browsingScenario',
    },
    purchasing: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '5m', target: 20 },
        { duration: '10m', target: 20 },
        { duration: '5m', target: 0 },
      ],
      exec: 'purchaseScenario',
    },
  },
  thresholds: {
    'http_req_duration{scenario:browsing}': ['p(95)<800'],
    'http_req_duration{scenario:purchasing}': ['p(95)<2000'],
    http_req_failed: ['rate<0.01'],
  },
};

export function browsingScenario() {
  group('Browse Products', () => {
    http.get('https://store.example.com/');
    sleep(2);
    http.get('https://store.example.com/products');
    sleep(3);
    http.get('https://store.example.com/products/sample-product');
    sleep(2);
  });
}

export function purchaseScenario() {
  group('Purchase Flow', () => {
    // Browse
    http.get('https://store.example.com/products/sample-product');
    sleep(1);

    // Add to cart
    http.post('https://store.example.com/api/cart', JSON.stringify({
      productId: 'prod_123',
      quantity: 1,
    }), { headers: { 'Content-Type': 'application/json' } });
    sleep(2);

    // Checkout
    http.get('https://store.example.com/cart');
    sleep(3);

    // Place order (simulated)
    const orderResponse = http.post('https://store.example.com/api/checkout/validate', JSON.stringify({
      email: `test-${__VU}@example.com`,
    }), { headers: { 'Content-Type': 'application/json' } });

    check(orderResponse, {
      'checkout validates': (r) => r.status === 200 || r.status === 201,
    });
    sleep(1);
  });
}

Stress Test (Find the Breaking Point)

// k6/stress-test.js
import http from 'k6/http';
import { check } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 100 },
    { duration: '5m', target: 100 },
    { duration: '2m', target: 200 },
    { duration: '5m', target: 200 },
    { duration: '2m', target: 500 },
    { duration: '5m', target: 500 },
    { duration: '2m', target: 1000 },
    { duration: '5m', target: 1000 },
    { duration: '5m', target: 0 },
  ],
};

export default function () {
  const res = http.get('https://example.com/api/v1/products');
  check(res, {
    'status is 200': (r) => r.status === 200,
  });
}

Interpreting Results

Key Metrics

MetricHealthyWarningCritical
P95 Response Time<500ms500ms-2s>2s
P99 Response Time<1s1-5s>5s
Error Rate<0.1%0.1-1%>1%
ThroughputMeets target80% of target<80% of target

Common Bottleneck Patterns

CPU-bound bottleneck: Response time increases linearly with load. P95 and P99 diverge slowly.

  • Fix: Optimize hot code paths, add CPU capacity, or scale horizontally

Database bottleneck: Response time increases exponentially at a specific load threshold. Connection pool exhaustion.

Memory bottleneck: Gradual degradation over time. GC pauses cause latency spikes.

  • Fix: Increase memory, fix memory leaks, optimize object allocation

Network bottleneck: Response time increases uniformly across all endpoints. Bandwidth saturation.

  • Fix: CDN for static assets, compression, reduce payload sizes

Performance Baselines

Establishing a Baseline

Before optimizing, document your current performance:

# Run baseline test
k6 run --out json=baseline-results.json k6/load-test.js

# Compare after optimization
k6 run --out json=optimized-results.json k6/load-test.js

Performance Budget

Define acceptable performance for each endpoint:

EndpointP95 TargetThroughput Target
Homepage500ms200 req/s
Product listing800ms150 req/s
Product detail600ms200 req/s
Add to cart300ms100 req/s
Checkout2000ms50 req/s
Search500ms100 req/s
Admin dashboard1500ms20 req/s

CI/CD Integration

Automated Performance Regression Testing

# .github/workflows/performance.yml
name: Performance Test
on:
  push:
    branches: [main]

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

      - name: Run k6 load test
        uses: grafana/[email protected]
        with:
          filename: k6/load-test.js
          flags: --out json=results.json
        env:
          K6_TARGET_URL: ${{ secrets.STAGING_URL }}

      - name: Check thresholds
        run: |
          if grep -q '"thresholds":{".*":"fail"' results.json; then
            echo "Performance thresholds exceeded!"
            exit 1
          fi

Load Testing Checklist

Before Testing

  • Environment matches production (instance types, database size)
  • Test data is representative (realistic product count, user count)
  • Monitoring is active (track server metrics during test)
  • Stakeholders are notified (load tests can trigger alerts)
  • CDN and caching are configured as in production

During Testing

  • Monitor server CPU, memory, disk I/O
  • Monitor database connections and query latency
  • Watch for error rate increases
  • Check for resource exhaustion (file descriptors, connections)
  • Note the load level where performance degrades

After Testing

  • Document baseline results
  • Identify bottlenecks and their load thresholds
  • Create tickets for performance improvements
  • Compare against performance budget
  • Schedule follow-up test after optimizations

Frequently Asked Questions

Should we load test production or staging?

Both, if possible. Staging for regular testing and CI/CD integration. Production for periodic validation (during low-traffic hours) because staging often differs in database size, cache warmth, CDN configuration, and network topology. If you can only test one environment, test staging but make it as production-like as possible.

How often should we run load tests?

Smoke tests on every deployment (automated in CI/CD). Full load tests weekly or before major releases. Stress tests quarterly or before known high-traffic events (sales, launches). Soak tests quarterly to detect memory leaks and long-term degradation.

How do we load test an ERP system?

ERP load testing requires simulating concurrent users performing different tasks: generating invoices, creating purchase orders, running reports, importing data. Focus on the heaviest operations (report generation, data imports) and the most concurrent operations (order entry during peak hours). ECOSIRE provides Odoo performance testing as part of our support services.

What is a realistic think time between requests?

For eCommerce browsing: 2-5 seconds. For form filling: 10-30 seconds. For checkout: 15-60 seconds. For admin/ERP usage: 5-15 seconds. Always add randomized think time to your load tests --- constant intervals create unrealistic synchronized load patterns.


What Comes Next

Load testing reveals bottlenecks that guide your optimization efforts. Follow up with database scaling for database bottlenecks, CDN optimization for static asset delivery, and auto-scaling for elastic capacity.

Contact ECOSIRE for performance testing and optimization, or explore our DevOps guide for the complete infrastructure strategy.


Published by ECOSIRE -- helping businesses build applications that perform under pressure.

E

Written by

ECOSIRE Research and Development Team

Building enterprise-grade digital products at ECOSIRE. Sharing insights on Odoo integrations, e-commerce automation, and AI-powered business solutions.

Chat on WhatsApp