Scalability Testing¶
Scalability testing measures how the system's capacity responds to added resources, and how it behaves as data and users grow. The question is not "is it fast enough now", which is load testing, but "does adding capacity actually buy capacity".
graph LR
N1["1 node: 1000 requests per second"] --> N2["2 nodes: how many?"]
N2 --> N4["4 nodes: how many?"]
N4 --> N8["8 nodes: how many?"]
N8 --> CURVE{Near-linear, flattening, or falling?}
Two directions of growth¶
| Vertical scaling | Horizontal scaling | |
|---|---|---|
| Add | A bigger machine | More machines |
| Limit | The largest machine available | Coordination and shared bottlenecks |
| Test | Capacity at each instance size | Capacity at each node count |
| Typical wall | Single-thread performance, memory bandwidth | Database, locks, shared cache, leader nodes |
Reading the scaling curve¶
graph TD
A[Linear: doubling nodes doubles throughput] --> GOOD[The ideal, rarely reached]
B[Sub-linear: gains shrink with each addition] --> NORM[Normal, quantify the loss]
C[Flat: extra nodes add nothing] --> BOT[A shared bottleneck saturated]
D[Negative: throughput falls as nodes increase] --> CO[Coordination cost exceeds the gain]
The flat and negative cases both point at something shared: a single database writer, a global lock, a chatty cache invalidation protocol, or coordination between nodes growing faster than the work does. Adding hardware there makes the bill grow and nothing else.
The practical output of a scaling test is a number: "throughput grows at roughly 0.8 per added node up to 8 nodes, then flattens". That number is what capacity planning and cost forecasting need, and it cannot be derived from a single-node measurement.
Data growth, the other axis¶
Users are not the only thing that grows.
| Growth | Failure it produces |
|---|---|
| Table row count | Queries without a suitable index degrade from milliseconds to seconds |
| Rows per tenant | A query fine for the average customer times out for the largest one |
| Object or file count | Directory listings and full scans become the bottleneck |
| History retention | Backups exceed their window, restores exceed the recovery target |
| Index size | Working set no longer fits in memory, and IO becomes the limit |
Test with realistic future volumes, not current ones. A system tested at today's data size has been tested for the least demanding day of its life.
Designing the test¶
- Establish a single-node baseline: throughput at the saturation point, with realistic data volume and workload mix.
- Add capacity in steps, re-measuring at each step with the same workload model.
- Record throughput, latency percentiles and resource use per node at each step.
- Identify the first non-linearity and find what it is: usually the shared component whose utilisation keeps climbing while per-node utilisation falls.
- Repeat at larger data volumes, since a system that scales well at 1 million rows may not at 100 million.
Autoscaling deserves its own check. It has two properties worth measuring separately from capacity: how fast it reacts to a surge, and whether it scales down without dropping in-flight work.
Check Your Understanding¶
Throughput stops improving after the fourth application node although each node is only 40% utilised. What does this indicate?
Why must scalability testing use realistic future data volumes rather than current ones?