Chapter 9: Performance Test Planning
Workload modeling is where planning gets real. It is the bridge between "we have 3,000 concurrent users" and the actual JMeter Thread Group configuration. Think of it like a movie director planning crowd scenes -- you do not just say "put some people in the background." You decide exactly how many extras, what they wear, where they walk, and how fast they move. A workload model is your crowd choreography.
Not all users do the same thing. On a banking app during peak hours, some users are checking balances, some are transferring money, and some are paying bills. The transaction mix defines what percentage of your virtual users perform each action. Getting this right is crucial -- if 60% of real users check balances but your test has 60% doing fund transfers, you are stress-testing the wrong part of the system.
| User Flow | Percentage of Users | Virtual Users (of 3000) | Think Time | Requests per Session |
|---|---|---|---|---|
| Login + View Balance | 40% | 1200 | 8 seconds | 7 |
| Login + Transaction History | 25% | 750 | 10 seconds | 9 |
| Login + Fund Transfer | 15% | 450 | 15 seconds | 12 |
| Login + Bill Payment | 10% | 300 | 12 seconds | 10 |
| Login + Statement Download | 5% | 150 | 5 seconds | 5 |
| Login + Profile Update | 5% | 150 | 20 seconds | 6 |
Here is something that trips up even experienced testers. Without think time, your 3,000 virtual users behave like 30,000 real users. Why? Because real users pause between actions -- they read the page, fill out a form, scroll through results. A real user might make one request every 10 seconds. A virtual user without think time makes one request every 100 milliseconds. That is 100x more aggressive.
import { sleep } from "k6";
import { randomIntBetween } from "https://jslib.k6.io/k6-utils/1.2.0/index.js";
export default function () {
// BAD: Fixed think time -- all users click at exactly the same moment
// sleep(5);
// BETTER: Random uniform think time
// sleep(randomIntBetween(3, 12));
// BEST: Gaussian distribution around the mean
// Most users think for 5-10 seconds, a few are faster, a few slower
const mean = 8;
const stddev = 3;
const thinkTime = Math.max(1, mean + stddev * gaussianRandom());
sleep(thinkTime);
}
function gaussianRandom() {
// Box-Muller transform for normal distribution
const u1 = Math.random();
const u2 = Math.random();
return Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
}Real users do not all arrive at the same millisecond. Your ramp-up pattern should mimic how users actually show up. There are three common patterns:
Linear Ramp -- Users arrive at a constant rate (e.g., 10 users/second for 5 minutes to reach 3,000). Good for steady load tests. Most common and simplest to configure.
Stepped Ramp -- Users arrive in batches with pauses between steps (e.g., add 500 users, hold for 2 minutes, add 500 more). Good for finding the breaking point -- you can see exactly when response times degrade.
Spike Pattern -- Sudden burst of users (e.g., 0 to 3,000 in 10 seconds). Simulates a flash sale, breaking news event, or marketing email blast. Tests auto-scaling and connection pool behavior.
Production-Replay Pattern -- Replay actual production traffic patterns from access logs. The most realistic but most complex to set up. Tools like GoReplay can capture and replay production traffic.
Once you have the transaction mix and think times, you can calculate expected throughput. This is the number your load generator needs to achieve. If it cannot hit this number, either your load generator is the bottleneck or your test environment is undersized.
Formula: TPS = (Concurrent Users × Requests per Session) / (Session Duration in seconds)
Example: Banking Portal Peak Hour
Flow 1: Balance Check
Users: 1,200
Requests per session: 7
Session duration: 56 sec (7 requests × 8 sec think time)
TPS = (1,200 × 7) / 56 = 150 TPS
Flow 2: Transaction History
Users: 750
Requests per session: 9
Session duration: 90 sec (9 requests × 10 sec think time)
TPS = (750 × 9) / 90 = 75 TPS
Flow 3: Fund Transfer
Users: 450
Requests per session: 12
Session duration: 180 sec (12 requests × 15 sec think time)
TPS = (450 × 12) / 180 = 30 TPS
Flow 4: Bill Payment
Users: 300
Requests per session: 10
Session duration: 120 sec (10 requests × 12 sec think time)
TPS = (300 × 10) / 120 = 25 TPS
Flow 5 + 6: Statement + Profile
Users: 300
Combined TPS ≈ 20 TPS
---------------------------------------
Total Expected TPS: ~300 TPS
Target TPS with buffer (1.5x): ~450 TPSQ: Explain workload modeling. How do you translate real user behavior into a performance test?
A: Workload modeling is the process of creating a realistic simulation of production traffic. I start by analyzing production data to understand three things: user distribution (what percentage does what), timing (think times, session duration, peak hours), and volume (concurrent users, data sizes). For example, on a banking app, I found from access logs that 40% of users only check balances, 25% view transaction history, 15% do transfers, and the rest do other things. I then set up separate Thread Groups in JMeter -- one for each flow -- with the correct user percentages. I add realistic think times (8-15 seconds between actions) based on analytics data. I configure a ramp-up that matches real traffic patterns -- gradual increase in the morning, peak at 9 AM, steady through lunch, tapering off by evening. The key metric I validate is throughput: if the model predicts 300 TPS at peak and my test generates 300 TPS, the model is working correctly.
A common trap: testers divide total concurrent users equally across all flows (500 per flow for 5 flows with 2,500 users). This is almost never correct. Real traffic is heavily skewed -- one or two flows dominate. Always check access logs for actual distribution.
Key Point: A workload model defines who does what, how fast, and how many -- get the transaction mix, think times, and ramp-up pattern from real production data, never from guesswork.