Test how systems behave under increasing load and model capacity boundaries. Covers load profile design, stress testing, soak testing, spike testing, bottleneck identification, and the patterns that reveal scalability limits before users do.
Scalability testing evaluates how systems behave under increasing load, while load modeling predicts performance characteristics using mathematical representations of usage patterns. This process matters when capacity planning requires validation of assumptions about system behavior under stress, or when implementing changes that might alter the relationship between resource consumption and response time.
Load testing begins with defining realistic request sequences. A typical HTTP load profile specifies:
{
"requests": [
{
"method": "GET",
"path": "/api/users/123",
"headers": {
"Authorization": "Bearer token123"
}
},
{
"method": "POST",
"path": "/api/orders",
"body": {
"items": [
{"sku": "ABC123", "quantity": 2}
]
}
}
],
"ramp_up_period": "30s",
"duration": "5m"
}
This JSON structure defines a sequence of HTTP operations. Each request includes method, path, headers, and optional body. The ramp-up period controls how quickly concurrent users increase to the target level. The duration determines how long the test runs.
The key to effective scalability testing is modeling actual user workflows rather than simple request counts. For example, a shopping site might have these typical sequences:
These workflows should be weighted according to their frequency in production. The test tool must execute these sequences with appropriate delays between requests to mimic real user pacing.
Locust is a Python-based load testing tool that supports distributed execution across multiple machines. It uses Python code to define test scenarios:
from locust import HttpUser, task, between
class WebsiteUser(HttpUser):
wait_time = between(1, 5)
@task
def get_user(self):
self.client.get("/api/users/123")
@task
def create_order(self):
self.client.post("/api/orders", json={
"items": [{"sku": "ABC123", "quantity": 1}]
})
The wait_time attribute controls delays between tasks. The @task decorator marks methods that represent user actions.
To run Locust with multiple workers:
# Start master node
locust --master -f test_script.py --host=http://localhost:8000
# Start worker nodes
locust --worker -f test_script.py --master-host=192.168.1.100
Locust exposes metrics at /stats/ endpoint, which returns JSON containing request counts, response times, and failure rates.
Test duration affects the validity of results. A test that runs only 30 seconds may not capture steady-state performance characteristics. The concurrency level determines how many users are simulated simultaneously.
For a typical load test, start with 10 concurrent users and increase by factors of 2 until system degradation occurs. The key metric is response time growth rate as concurrency increases.
Response time patterns reveal system bottlenecks. A flat response time curve indicates stable performance. An exponential increase in response times suggests resource exhaustion.
The critical threshold depends on application requirements. For an e-commerce site, a 500ms response time might be acceptable for product listings, but unacceptable for checkout processing.
Test tools report failure rates for each endpoint. A failure rate above 1% indicates significant reliability issues. Common failure types include:
The test tool should log detailed error information, including request payloads and response bodies for failed requests.
Monitor system resources during tests. CPU usage above 90% indicates potential bottlenecks. Memory usage patterns show whether the application leaks resources over time.
Deterministic load modeling uses fixed request rates and timing. This approach works well for systems with predictable behavior:
{
"load_profile": {
"rps": 100,
"concurrent_users": 50,
"duration_minutes": 60
}
}
This configuration specifies 100 requests per second with 50 concurrent users for one hour.
Stochastic models account for variable user behavior patterns:
{
"user_behavior": {
"think_time_distribution": "exponential",
"request_frequency": {
"mean": 30,
"std_deviation": 10
}
}
}
The think time distribution controls delays between user actions. Exponential distribution mimics real-world behavior where users spend more time on complex tasks.
Load tests must run against identical environments to ensure reproducible results. This means:
Any environment differences introduce noise into test results.
Database connection handling affects scalability results. Ensure the test tool uses connection pooling to avoid resource exhaustion:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = requests.Session()
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
)
adapter = HTTPAdapter(
pool_connections=20,
pool_maxsize=20,
max_retries=retry_strategy
)
session.mount("http://", adapter)
session.mount("https://", adapter)
This configuration maintains 20 concurrent connections and retries failed requests.
Many load tests fail because they don't allow sufficient warm-up time. Applications may not reach steady-state performance immediately. The warm-up period should be at least 10% of the total test duration.
Load testing often focuses only on the primary service but neglects dependencies. Database queries, external API calls, and message queue operations can all become bottlenecks.
Poorly constructed test data causes artificial failures. For example, using non-existent user IDs leads to 404 errors that mask real performance problems.
Test environments may lack sufficient resources to accurately represent production conditions. This mismatch leads to false confidence in scalability results.
Metrics collection tools must be configured to capture all relevant data points. Missing CPU or memory metrics during a test makes result analysis impossible.
Establish specific response time thresholds for each endpoint:
These targets should be based on user experience requirements rather than arbitrary numbers.
Acceptable error rates depend on system importance:
Define minimum throughput for each service:
{
"throughput_requirements": {
"api_users_endpoint": "500 req/sec",
"api_orders_endpoint": "100 req/sec"
}
}
These requirements guide test design and validation.
Start tests with minimal load and gradually increase until degradation occurs. This approach identifies the system's breaking point more accurately than starting at maximum load.
Run each test configuration multiple times to account for statistical variation. The consistency of results across runs validates test reliability.
Use production-like data sets for testing. Synthetic data may not reveal real-world performance characteristics.
Integrate load test tools with existing monitoring systems to correlate test results with system metrics. This integration helps identify root causes of performance issues.
Document all test configurations, including:
Establish baseline performance characteristics for each system component. These baselines serve as reference points for future comparisons.
Include scalability tests in regression suites to prevent performance regressions from being introduced by new code changes.
The goal of scalability testing is not just to find the maximum capacity of a system, but to understand how it behaves under realistic conditions and to identify potential bottlenecks before they impact users.
This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy