Skip to content
SparkTime, home
System Design From Zero to Interview-Ready
Lessons
1 · System Design Foundations
2 · APIs and Communication
3 · Capacity Estimation
4 · Core Building Blocks
5 · Databases
6 · Distributed Systems
7 · Reliability and Operations
8 · Architecture and Security
9 · Interview Method and Foundation Case Studies
10 · Real-World Case Studies
11 · Large-Scale Case Studies
12 · Interview Readiness
Lesson 7 of 45

Lesson 7 · Capacity Estimation

Estimating Storage, Bandwidth, Cache Size and Server Count

Turn request rates into the numbers that shape an architecture: storage per day and per year with replication and retention, ingress and egress bandwidth in Gbps, cache memory from the working set, database write limits, and a first server count with headroom and zone failure. Includes a full worked example and a calculator.

On this page

Key takeaways

  • Storage is records per day times bytes per record, times retention, times copies; separate small metadata from large blobs, because they go to different stores.
  • Bandwidth is bytes per second at peak, converted to bits; for media-heavy systems, egress usually dwarfs everything else and decides whether you need a CDN.
  • Size a cache from the hot working set, not the whole dataset, and remember the hit ratio decides how much load still reaches the database.
  • Server counts come from measured per-server capacity or Little's Law, then headroom and the loss of a zone; never from a guess.
  • Every estimate should end in a decision: one database or many, cache or not, CDN or not.

Article 06 turned users into requests per second. That tells you how often things happen, but not how big they are. A system that handles 70 uploads per second sounds easy until you notice each upload is 2 MB and they never get deleted. A feed that serves 3,500 requests per second sounds manageable until you notice each one pulls ten images.

This article finishes the estimation toolkit: storage, bandwidth, cache memory and server count. Each section ends with the design decision the number drives, because that is the only reason to compute it.

What you will learn

#
  • How to estimate the size of one record and one blob, with overheads.
  • Storage per day and per year, with retention and replication.
  • Ingress and egress bandwidth, in bytes and bits per second, average and peak.
  • How to size a cache from the working set, and what the hit ratio does to database load.
  • How to estimate server count from per-server capacity or from Little's Law, with headroom and zone failure.
  • How to check whether one database can take the write load.
  • A complete worked example for Pixly, and a calculator.

Storage

#

Step 1: size one record

#

List the fields and add up their sizes. For a Pixly photo's metadata row:

Field

Assumed size

photo_id (64-bit)

8 bytes

owner_id (64-bit)

8 bytes

caption (average, not maximum)

100 bytes

created_at

8 bytes

status, width, height

about 10 bytes

Object storage keys for the original and thumbnails

about 150 bytes

Raw total

about 300 bytes

Databases add overhead: row headers, indexes, free space in pages. Assume the stored size is roughly double the raw size unless you know better. Here that gives about 600 bytes; round up to 1 KB to keep the arithmetic easy and leave room for new fields.

Use the average size of variable fields, not the maximum. A caption may be up to 2,200 characters, but the average is far smaller. Using maximums overestimates storage by an order of magnitude.

Step 2: separate metadata from blobs

#

Small structured records (metadata, likes, follows) belong in a database. Large unstructured files (photos, videos, documents) belong in object storage (article 10), with the database holding only a pointer. Estimate them separately, because they have different costs, different growth and different stores.

For a Pixly photo, assume an average original of 2 MB after the app compresses it, plus three thumbnails totalling 300 KB: about 2.3 MB of blob data per upload.

Step 3: multiply by rate, retention and copies

#
Code
Storage per day   = items per day x bytes per itemStorage per year  = storage per day x 365Total footprint   = storage per year x years retained x number of copies

Retention matters: if data is kept forever, storage only grows. If logs are kept for 30 days, storage is capped at 30 days' worth.

Copies matter too. A database with one primary and two replicas stores three copies. Managed object storage handles redundancy internally (Amazon S3, for example, stores objects across at least three Availability Zones for most storage classes), so you normally count only the logical size when estimating object storage, but you would count each database replica.

Pixly storage

#

Data

Per day

Per year

With copies

Photos and thumbnails (2M x 2.3 MB)

4.6 TB

about 1.7 PB

Logical size; redundancy is inside the object store

Photo metadata (2M x 1 KB)

2 GB

about 730 GB

x3 database copies = about 2.2 TB

Likes (50M x 100 bytes including index)

5 GB

about 1.8 TB

x3 = about 5.5 TB

Design conclusions:

  • Photos are petabytes per year. They must go to object storage; no database or file server on application machines is appropriate.
  • Metadata is only a few terabytes per year. A single well-provisioned relational database can hold years of it; partitioning is a later problem, not a day-one problem.
  • The likes table grows fastest among database tables, almost three times faster than photo metadata. It is the first candidate for partitioning or for a store built for high write volume (articles 16 and 17).

Bandwidth

#

Bandwidth is bytes per second moving into the system (ingress) and out of it (egress). Compute it from the daily volume, then apply the peak factor, then convert to bits.

Code
Average bytes per second = bytes per day / 86,400Peak bytes per second    = average x peak factorBits per second          = bytes per second x 8

Pixly ingress (uploads)

#
Code
4.6 TB per day / 86,400 s = about 53 MB/s averagex 8 = about 0.43 Gbps average; x 3 peak = about 1.3 Gbps

Pixly egress (people viewing photos)

#

Assume each feed view loads 10 images of about 100 KB each (feed-sized, not originals): 1 MB per feed view.

Code
100M feed views x 1 MB = 100 TB per day100 TB / 86,400 s = about 1.16 GB/s, x 8 = about 9.3 Gbps averagePeak (x 3) = about 28 Gbps

Egress is about 20 times ingress. That is typical of media products: content is created once and viewed many times.

What a CDN changes

#

A CDN (article 10) serves repeated requests for the same files from edge locations, so they never reach your servers. If 90% of image requests are hits at the CDN (an assumption that depends heavily on how concentrated viewing is), your origin only serves the remaining 10%:

Code
Origin egress at peak = 28 Gbps x (1 - 0.9) = about 2.8 Gbps

Design conclusion: without a CDN, Pixly's own servers and network would need to push about 28 Gbps at peak, and users far away would wait on long round trips. With one, the origin sees a tenth of that. For any media-heavy design, the bandwidth estimate is what justifies the CDN.

Cache size

#

A cache keeps copies of frequently read data in memory (article 09). Memory is far more expensive per byte than disk, so you cannot usually cache everything. You cache the hot working set: the part of the data that receives most reads.

The working set

#

Many access patterns are skewed: a small fraction of items gets most of the reads. A common planning heuristic is the "80/20" assumption, that about 20% of items receive about 80% of reads. It is an assumption, not a law; check it against real access logs when you have them.

Example: Pixly caches photo metadata for photos from the last 7 days, since the feed mostly shows recent photos.

Code
2M new photos per day x 7 days x 1 KB = 14 GB

Fourteen gigabytes fits comfortably in the memory of one cache node. With a replica for availability, that is two nodes, which is a very cheap way to take most feed reads off the database.

A bigger example: suppose Pixly precomputes each active user's feed as a list of the latest 500 photo ids (article 35 discusses whether to).

Code
10M DAU x 500 ids x 8 bytes = 40 GB (plus data-structure overhead; assume x2 = 80 GB)

Still feasible, but now spread over a few nodes. If each node offers about 50 GB of usable memory (an assumption about the instance size you choose), that is 2 nodes, or 4 with replicas.

The hit ratio decides database load

#

The hit ratio is the fraction of reads served from the cache. What reaches the database is the rest:

Code
Database reads = total reads x (1 - hit ratio)

Hit ratio

Database reads at 35,000 lookups per second

0% (no cache)

35,000

90%

3,500

99%

350

(35,000 lookups per second is the 3,500 feed reads per second at peak, each needing metadata for 10 photos.)

Going from 90% to 99% cuts database load by ten times. Small improvements in hit ratio are worth a lot. The reverse is also true, and dangerous: if the cache is lost and the hit ratio falls to zero, the database suddenly faces ten or a hundred times its usual load. Articles 09 and 23 deal with that failure.

Server count

#

There are two sound ways to estimate how many application servers you need. Both depend on knowing what one server can do, which you measure with a load test. In an interview, state it as an assumption.

Method 1: throughput per server

#

Assume load testing shows one API server handles 500 feed requests per second while keeping p99 latency within target.

Code
Servers for peak = 3,500 / 500 = 7

Method 2: Little's Law

#

From article 02: requests in progress = throughput x latency. Assume an average latency of 100 ms and that one server handles 50 concurrent requests well.

Code
Concurrent requests at peak = 3,500 x 0.1 s = 350Servers = 350 / 50 = 7

Both methods give 7, which is a good sign that the assumptions are consistent.

Add headroom and plan for failure

#

Seven servers running at 100% at peak is a fragile system. Add:

  1. Utilisation headroom. Aim for at most about 60% utilisation at peak, so latency stays low and small spikes are absorbed: 7 / 0.6 = about 12 servers.
  2. Zone failure. Spread servers across 3 availability zones (separate data centres in one region). If one zone fails, the other two must carry the whole peak. Each zone must therefore hold half the requirement: 12 / 2 = 6 per zone, 18 servers in total.
Code
Raw need:            7With 60% target:    12Survive 1 of 3 zones lost: 18 (6 per zone)

The jump from 7 to 18 surprises people, but it is the real cost of availability. Autoscaling can reduce the off-peak count, but you still need enough capacity to survive a zone failure at the peak.

Can one database take the writes?

#

Writes are often the first database limit, because they cannot be spread over replicas. Pixly's peak writes are about 1,700 likes plus 70 uploads per second, about 1,800 writes per second.

Whether one primary handles that depends on the hardware, the schema, the indexes and the size of each write, so it must be measured. If a load test shows the primary can sustain, say, 5,000 writes per second of this shape at the target latency, you are at about 36% of capacity: comfortable today, with room to grow about 2.5 times before you need batching, a different store for likes, or partitioning. If the test shows 2,000, you are already near the limit, and the design must change now.

This is the pattern: the estimate tells you which question to ask a load test.

Connections are a resource too

#

Each application server keeps a pool of database connections. 18 servers x 20 connections = 360 connections. Some databases handle very large numbers of connections poorly (PostgreSQL, for example, uses a process per connection), so systems often put a connection pooler between applications and the database. Article 12 covers this.

A calculator for the Pixly numbers

#

This Java program reproduces the storage and bandwidth figures above. As before, it models the arithmetic; a spreadsheet does the same job.

Java
// File: CapacityPlan.javaimport java.util.Locale;public class CapacityPlan {    static final double DAY = 86_400;    static final double KB = 1e3, MB = 1e6, GB = 1e9, TB = 1e12, PB = 1e15;    static String bytes(double b) {        if (b >= PB) return String.format(Locale.US, "%.1f PB", b / PB);        if (b >= TB) return String.format(Locale.US, "%.1f TB", b / TB);        if (b >= GB) return String.format(Locale.US, "%.1f GB", b / GB);        if (b >= MB) return String.format(Locale.US, "%.1f MB", b / MB);        return String.format(Locale.US, "%.1f KB", b / KB);    }    static String gbps(double bytesPerSecond) {        return String.format(Locale.US, "%.1f Gbps", bytesPerSecond * 8 / 1e9);    }    public static void main(String[] args) {        double uploadsPerDay = 2_000_000;        double photoBytes = 2 * MB + 300 * KB;   // original plus three thumbnails        double metaBytes = 1 * KB;        double feedViewsPerDay = 100_000_000;        double bytesPerFeedView = 10 * 100 * KB; // ten images of about 100 KB        double peak = 3;        double photoPerDay = uploadsPerDay * photoBytes;        System.out.println("Photo storage per day:  " + bytes(photoPerDay));        System.out.println("Photo storage per year: " + bytes(photoPerDay * 365));        double metaPerYear = uploadsPerDay * metaBytes * 365;        System.out.println("Metadata per year, 3 copies: " + bytes(metaPerYear * 3));        double ingress = photoPerDay / DAY;        System.out.println("Upload ingress avg / peak: " + gbps(ingress) + " / " + gbps(ingress * peak));        double egress = feedViewsPerDay * bytesPerFeedView / DAY;        System.out.println("Image egress avg / peak:   " + gbps(egress) + " / " + gbps(egress * peak));        double cdnHitRatio = 0.9;        System.out.println("Origin egress at peak with 90% CDN hits: " + gbps(egress * peak * (1 - cdnHitRatio)));    }}
Code
Photo storage per day:  4.6 TBPhoto storage per year: 1.7 PBMetadata per year, 3 copies: 2.2 TBUpload ingress avg / peak: 0.4 Gbps / 1.3 GbpsImage egress avg / peak:   9.3 Gbps / 27.8 GbpsOrigin egress at peak with 90% CDN hits: 2.8 Gbps

Putting it together: Pixly's capacity summary

#

Quantity

Estimate

Design consequence

Feed reads at peak

about 3,500 per second

Cache recent metadata; consider precomputed feeds

Writes at peak

about 1,800 per second

One primary is plausible; measure; likes are the growth risk

Photo storage

about 1.7 PB per year

Object storage, lifecycle rules for old originals

Metadata storage

about 2.2 TB per year with copies

One relational database for years

Egress at peak

about 28 Gbps

CDN is essential

Cache for 7 days of metadata

about 14 GB

Two small cache nodes

API servers

18 across 3 zones

Autoscale down off-peak

Seven numbers, and the shape of the architecture is already clear: stateless API servers behind a load balancer, a cache, a relational database with replicas, object storage behind a CDN, and queues for thumbnails and fan-out. The next module builds each of those blocks.

Trade-offs

#

Decision

Trade-off

Keep everything forever versus retention limits

Simplicity and history versus unbounded cost; tiered storage moves old data to cheaper classes

More database replicas

More read capacity and availability versus more storage, more connections and replication lag

Larger cache

Higher hit ratio and lower database load versus memory cost; returns diminish once the hot set fits

More headroom

Survives spikes and zone loss versus paying for idle capacity

Estimating from averages versus measuring

Fast versus accurate; estimates guide design, load tests confirm it

Failure modes and what the numbers warn you about

#
  • Disk fills up: storage was estimated without retention or growth. Monitor free space and alert well before it runs out.
  • Bandwidth saturates: a viral item or a CDN misconfiguration (a sudden fall in hit ratio) sends far more traffic to the origin than estimated.
  • Cache loss overloads the database: the hit ratio drops from 99% to 0% and database reads rise a hundredfold.
  • Zone loss overloads the survivors: capacity was planned for the peak but not for the peak minus a zone.
  • Connections exhausted: many servers each with large pools exceed the database's connection limit after a scale-out.

Common mistakes

#
  • Using maximum field sizes instead of averages.
  • Putting large files in the database "because it is simpler".
  • Forgetting replicas when estimating database storage.
  • Quoting bandwidth in bytes when links are sized in bits.
  • Sizing the cache for the whole dataset instead of the hot set.
  • Computing servers from the average instead of the peak, then forgetting headroom and zone loss.
  • Treating per-server capacity as a known fact instead of something to measure.

In an interview

#

After traffic, spend two or three minutes on:

  1. Storage: bytes per item, items per day, per year, with copies. Separate blobs from metadata.
  2. Bandwidth: egress at peak, especially for media. Decide on a CDN.
  3. Cache: the hot set in GB, and whether it fits on a few nodes.
  4. Servers and database: only if asked, or if writes look close to a single-machine limit.

Then say the conclusions in one sentence each, and move on to the API.

Exercises

#

Exercise 1

Storage for a messaging app

#

A messaging app stores 4 billion messages a day. The average message is 100 bytes of text plus 100 bytes of metadata, and the database roughly doubles the raw size with overheads. Messages are kept for 2 years, with 3 database copies. Estimate the total storage. What does this tell you about the database design?

Exercise 2: Bandwidth for a music app

#

A music streaming app has 20 million DAU, each listening for 60 minutes a day at 160 kbps. The peak factor is 2. Compute average and peak egress in Gbps. If the CDN hit ratio is 95%, what is the origin egress at peak?

Exercise 3: Cache sizing

#

An e-commerce site has 5 million products. Each product page needs 4 KB of data. Analytics show that about 2% of products receive about 90% of views. How much memory caches that hot set? If the site gets 20,000 product-page requests per second at peak and the cache serves the hot 90%, how many requests per second still reach the database?

Exercise 4: How many servers?

#

An API must serve 12,000 requests per second at peak. Load tests show one server handles 800 requests per second at acceptable p99 latency. You want at most 65% utilisation at peak, and you deploy across 3 zones that must survive the loss of one. How many servers do you need, and how many per zone?

Show solutionHide solution
Code
Raw size per message = 200 bytes; stored about 400 bytesPer day = 4 x 10^9 x 400 bytes = 1.6 x 10^12 bytes = 1.6 TBPer year = 1.6 TB x 365 = about 584 TB, about 0.6 PBTwo years = about 1.2 PBThree copies = about 3.5 PB

Three and a half petabytes cannot sit on one database server. The message store must be partitioned across many machines, probably by conversation id so that one conversation's messages stay together and can be read in order (articles 16 and 34). Old messages are read far less than new ones, so tiering older data to cheaper storage is also worth considering.

Exercise 2

Show solutionHide solution
Code
Bytes per second per listener = 160,000 bits / 8 = 20,000 bytes = 20 KB/sPer user per day = 3,600 s x 20 KB = 72 MBPer day = 20M x 72 MB = 1.44 x 10^15 bytes = 1.44 PBAverage egress = 1.44 x 10^15 / 86,400 = about 16.7 GB/s = about 133 GbpsPeak (x2) = about 267 GbpsOrigin at peak with 95% hits = 267 x 0.05 = about 13 Gbps

A quicker route to the same answer: if listening were spread evenly through the day, average concurrent listeners = 20M x 60 / 1,440 minutes = about 833,000. Multiply by 160 kbps: about 133 Gbps. Two methods agreeing is a good sanity check.

Exercise 3

Show solutionHide solution
Code
Hot products = 2% of 5M = 100,000Memory = 100,000 x 4 KB = 400 MB (assume x2 for cache overhead = 800 MB)

Under a gigabyte: one small cache node, plus a replica for availability.

Database load: the hot 2% get 90% of the 20,000 requests per second, so the cache serves 18,000 and the remaining 2,000 requests per second go to the database (assuming the long tail of 98% of products is not cached). If that is still too much, add a modest TTL cache for the tail too, or read replicas. Notice how skewed access makes a tiny cache extremely effective.

Exercise 4

Show solutionHide solution
Code
Raw: 12,000 / 800 = 15 serversAt 65% utilisation: 15 / 0.65 = about 23.1, round up to 24Survive 1 of 3 zones: the remaining 2 zones must hold 24, so 12 per zoneTotal: 36 servers (12 per zone)

Off-peak, autoscaling can reduce the count, but keep enough minimum capacity per zone to absorb a zone loss while scaling up.

Where to go next

#
  • Guide · Technology

    Start Here: Your Roadmap from Zero to Job-Ready DSA

  • Guide · Technology

    Heaps and Priority Queues

  • Guide · Career

    A LinkedIn Profile That Gets Recruiter Messages