Before you run anything
This guide is general information about how we approach sizing. It is not advice for your specific situation, and it is not a substitute for someone who knows your environment.
Every command below is intended to be read-only, and we have written them that way deliberately. You are nonetheless running them on your own systems, at your own discretion. Read each one and understand what it does before you run it — particularly on anything carrying production traffic. If you are not certain, run it somewhere that does not matter first.
Figures, thresholds and worked examples here are illustrative. They come from a constructed example, not from your application, and applying them without measuring your own workload will give you the wrong answer. MDCS accepts no liability for decisions taken on the basis of this guide.
Anything touching regulatory or contractual obligations should be checked with your own advisers.
Why guessing fails in a predictable direction
Infrastructure is usually sized from what is visible. The size of the codebase, the number of registered users, how important the application feels. None of those determine what hardware it needs.
The result is consistent enough to be a pattern: compute is over-bought because it is the number the provider asks for first and capacity headroom feels safer; the database is under-built because it is invisible until it is the bottleneck; and bandwidth is often absent from the estimate until the first bill shows an egress line.
The five numbers that actually decide it
Requests at peak · concurrent users · database working set · monthly egress · storage growth rate. Everything else follows from those. This guide is how to get each one.
Before you start
Everything below is read-only. Nothing here changes a running system, and nothing needs to be installed. If a command asks for a password, it is asking for read access to something you already own.
Two sensible precautions
Run these on a system that is under normal load. Measuring at 6am on a Sunday tells you what your application does when nobody is using it, which is not the thing you are sizing for.
Measure for a week, not an hour. Almost every application has a weekly shape — a Monday morning peak, a month-end batch, a quiet Friday. One hour of data will mislead you in whichever direction that hour happened to fall.
01 · Traffic
What you need: requests per second at peak, and the average, so you know the ratio between them.
If you run nginx
If you run Apache
Divide the busiest minute by 60. That is your peak requests per second. Divide total requests by the seconds in the period for your average.
Mind the log period. Most systems rotate logs daily, so access.log may hold hours rather than weeks. Check with ls -la /var/log/nginx/ and include the rotated files if you need a longer window. Compressed archives can be read with zcat without unpacking them.
These commands read the whole file. On a large log on a busy machine that is measurable I/O — run it outside peak hours, or copy the log elsewhere and analyse it there.
You also want the p95 response time — the figure 95% of requests come in under. Most application frameworks log it; if yours does not, the browser developer tools will give you a usable approximation for the slowest page.
Why the ratio matters more than either number
An application at 4 requests per second on average and 22 at peak needs to be sized for 22. Sizing for the average under-builds it nearly six times over. Conversely, if peak and average are close, you have a steady workload — which changes the hosting economics entirely, because you are not paying for elasticity you never use.
02 · Utilisation
What you need: actual CPU, memory and disk usage at peak on whatever you run today. This is the measurement that most often changes the specification.
A snapshot, taken during a busy period
Better: a week of history
Compare load average to core count. A load average of 2 on 8 cores is a machine doing very little. The same figure on 2 cores is a machine at its limit.
sar only has history if sysstat was already collecting it. On many systems it is installed but not enabled, in which case those commands return nothing — that is not an error, it means there is no history to read. Enabling collection now gives you data from now on, not retrospectively.
Load average also means different things on different systems. On Linux it counts processes waiting on disk as well as CPU, so a high figure can indicate an I/O problem rather than a CPU one. Read it alongside iostat, not on its own.
The finding that changes most sizing decisions
Application servers commonly sit between 5% and 20% CPU. Meanwhile the database is memory-starved and doing far more disk I/O than it should. Where that is the case, added CPU will not change the response time. The constraint is elsewhere.
03 · Database
What you need: the working set — the portion of your data actually being read regularly — not the total size on disk. This is the single most important number in the whole exercise and the one most often got wrong.
MySQL or MariaDB
PostgreSQL
Divide buffer pool reads that hit disk by total reads. If the ratio is poor, or the PostgreSQL hit ratio is below about 0.99, your working set does not fit in memory — and that is the problem, not CPU.
Estimating the working set without instrumentation: take the tables your application queries on every request, add their indexes, and add roughly 30% for headroom. For most business applications that lands well under the total database size — often a quarter of it.
These queries read system tables and statistics only. They do not touch your data and do not lock anything. Even so, run them with a read-only account rather than an administrative one — the same principle applies here as anywhere else, and nothing in this guide needs write access.
The cache-hit and buffer-pool figures are cumulative since the database last restarted. A server restarted an hour ago will show poor ratios simply because the cache is cold. Check uptime before drawing conclusions.
Why this saves real money
Sizing database memory against total data rather than the working set is the most expensive difference in this exercise. An 11 GB database with a 4 GB working set needs about 16 GB of RAM, not 32 GB or more. Getting this right roughly halves the cost of the most expensive machine you will buy.
04 · Bandwidth and egress
What you need: monthly outbound traffic in gigabytes. Inbound is almost always free; outbound is what you are billed for and what nobody budgets.
Take the log period figure and scale it to a month. If your logs cover a week, multiply by 4.3 — not by 4.
Check your log format first. The $10 above is the bytes-sent field in nginx's default combined log format. If a log_format directive in your nginx.conf has changed the column order, that field is something else entirely and the total will be meaningless. Confirm the format before trusting the number. The same applies to the Apache examples, which assume the default combined format.
Note also what each method includes. /proc/net/dev and vnstat count everything the machine sends — backups, package updates, monitoring, replication. The log figure counts only what your application served. They will not agree, and the difference is often the more interesting number.
Where the cost usually sits
Egress between availability zones, and egress from a database to an application in a different zone, are both charged on most public clouds and neither appears until the bill. So is backup traffic leaving the provider. If you are comparing hosting options, model these explicitly — they are frequently a larger line than the compute.
05 · Storage growth
What you need: gigabytes added per month, not the current total. Sizing on today's figure is what leads to expanding storage under pressure a year later.
Two backups a month apart give you a growth rate directly. Three months of backups give you a trend, which is what you want, because growth is rarely linear once an application is adopted.
Size for three years at the measured rate. Storage is the cheapest thing to over-buy and the most disruptive to expand under pressure.
Growth is rarely linear. Adoption, a new feature, or a change in what you retain can all step the rate up sharply. Three months of history tells you more than two readings, and any figure you derive is a projection rather than a forecast — revisit it once you have real operating history.
Turning the numbers into a size
These are starting points, not rules. Every one of them should move if your measurements say so.
| From | Work out | Sensible starting point |
|---|---|---|
| Peak req/s × p95 response | Requests in flight at once | 22/s × 0.24s ≈ 5 concurrent. Less compute than the figure suggests |
| Requests in flight | Application CPU | Start at 2–4 vCPU for anything under 20 in flight |
| Working set + indexes + 30% | Database memory | 4.2 GB working set → 16 GB RAM |
| Peak concurrent users | Connection pool size | Pool size, not one connection per user. Usually 20–50 is ample |
| Growth rate × 36 months | Disk | 3 GB/month from 11 GB → about 120 GB |
| Monthly egress | Bandwidth cost | Model it separately. It does not scale with compute |
These figures are from the worked example
The right-hand column comes from the worked example rather than from your application, and is included to show the shape of the calculation. The method transfers; the figures do not.
Comparing cost
We deliberately do not publish provider prices. They change constantly, they are regional, and a stale price table is worse than none. What does not change is the method.
Total what you actually pay today
- Compute, all instances, including anything running that nobody uses
- Storage, including snapshots and backups you are retaining
- Egress — the number you measured above
- Load balancer hours, static IPs, managed database premiums
- Support tier, if you pay for one
- Anything billed separately: monitoring, logging, secrets management
Then compare over four years, not per month
Owned hardware is capital spent once and used for four to five years. Rented infrastructure is an operating cost forever. Comparing a monthly bill against a purchase price is comparing two different things — amortise the hardware across its life, and add power, space, network and the engineering time to run it.
The conclusion
At low or spiky utilisation, renting wins and it is not close. At steady utilisation above roughly half, owning starts to win — and the crossover point depends entirely on your numbers. There is a calculator that will give you a first answer, and it will tell you to stay where you are if that is what the arithmetic says.
If nothing is running yet
You cannot measure an application that has no users. What you can do is stop pretending otherwise.
- Estimate from the business, not the technology. How many people will use it, how often, doing what. Three hundred people checking something twice a day is about 600 requests spread across a working day — a handful per minute, not per second.
- Load test before launch, not after. Anything that can generate synthetic requests will tell you more than an estimate. Test at ten times your expected load and find where it breaks.
- Size for the first six months and plan the step. Not for the traffic you hope for in year three. Build the smallest sensible thing and know the number that says it is time to grow.
- Write down your assumptions. When the real numbers arrive, you want to know which of your guesses were wrong and by how much. That is worth more than getting it right first time.
On building ahead of demand
An architecture built for a hundred thousand users and run for four hundred costs more, has more failure points, and takes longer to change. Scaling on measurement keeps both the cost and the complexity proportionate.