Definition
- Registered users – everyone who ever signed up; most are dormant and irrelevant to capacity.
- Daily-active users (DAU) – those who actually use the system in a day; the basis for request estimation.
- Concurrent users – those active at the same instant; what hardware must survive at peak.
- Peak requests per second – derived from DAU, per-user request rate, and a peak-to-average multiplier.
- Read-to-write ratio – shapes the data strategy: caching for reads, sharding for writes.
- Data size and growth – determines whether a single database suffices or sharding is needed.
- Latency and consistency – tight latency forces caching; strong consistency constrains distribution.
Imagine someone tells National Bank of India "we have a million customers, build a branch big enough." That tells you almost nothing about how many counters to build. A million customers on the books might mean only fifty thousand visit in a day, and of those maybe two thousand are inside at the busiest moment on salary day. If you built for a million at once, you waste a fortune. If you built for the average and ignored salary-day rush, the branch overflows. So before pouring any concrete, a good architect asks: how many are actually inside at peak, how many just read their balance versus make a transaction, how much paperwork do we store, and how fast must each customer be served?
Understanding the Concepts
Why is "1 million users" ambiguous?
Registered users are everyone who ever signed up, and most are dormant. Daily-active users are those who actually use the system in a day. Concurrent users are those active at the same instant, and peak concurrency is what hardware must survive. These three can differ by one or two orders of magnitude. A million registered users might translate to a hundred thousand daily-active and only a few thousand concurrent at peak.
package com.bank.capacity;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class CapacityCalculator {
public static void main(String[] args) {
long registeredUsers = 1_000_000;
double dauPercentage = 0.10; // 10% are daily active
long dau = (long) (registeredUsers * dauPercentage);
double concurrentPercentage = 0.05; // 5% of DAU at peak
long peakConcurrent = (long) (dau * concurrentPercentage);
System.out.println("Registered users: " + registeredUsers);
System.out.println("Daily active: " + dau);
System.out.println("Peak concurrent: " + peakConcurrent);
}
}
Registered users: 1,000,000
Daily active: 100,000
Peak concurrent: 5,000
Designing for 1,000,000 concurrent is 200x over-provisioned!
How do you derive peak requests per second?
The real engineering driver is requests per second, especially peak. You derive it from DAU, a per-user request rate, and a peak-to-average multiplier. Traffic is never flat: salary day, market open, or a marketing push can cause three to ten times the average rate.
package com.bank.capacity;
public class RequestRateEstimator {
public static void main(String[] args) {
long dau = 100_000;
int requestsPerUserPerDay = 30;
int activeHoursPerDay = 10;
double peakMultiplier = 5.0; // Salary day spike
long totalRequestsPerDay = dau * requestsPerUserPerDay;
double avgRequestsPerSecond =
totalRequestsPerDay / (activeHoursPerDay * 3600.0);
double peakRequestsPerSecond =
avgRequestsPerSecond * peakMultiplier;
System.out.printf("Total requests/day: %,d%n",
totalRequestsPerDay);
System.out.printf("Avg requests/sec: %.0f%n",
avgRequestsPerSecond);
System.out.printf("Peak requests/sec: %.0f%n",
peakRequestsPerSecond);
}
}
Total requests/day: 3,000,000
Avg requests/sec: 83
Peak requests/sec: 417
A system sized for average (83 rps) will collapse at peak (417 rps)
How does the read-to-write ratio shape the data strategy?
A read-heavy system benefits enormously from caching and read replicas. A write-heavy system needs batching, async processing, and sharding. Knowing the ratio tells you where to spend your scaling effort.
package com.bank.capacity;
public class ReadWriteAnalyzer {
public static void main(String[] args) {
// National Bank of India mobile app analysis
int balanceChecks = 2000; // reads per second at peak
int transfers = 50; // writes per second at peak
int statementViews = 300; // reads per second at peak
int totalReads = balanceChecks + statementViews;
int totalWrites = transfers;
double readPercentage =
(totalReads * 100.0) / (totalReads + totalWrites);
System.out.printf("Reads/sec: %d%n", totalReads);
System.out.printf("Writes/sec: %d%n", totalWrites);
System.out.printf("Read ratio: %.1f%%%n", readPercentage);
System.out.println("Strategy: Cache + read replicas");
}
}
Reads/sec: 2300
Writes/sec: 50
Read ratio: 97.8%
Strategy: Cache + read replicas (most traffic never touches primary DB)
| Ratio | Strategy | Example |
|---|---|---|
| 90%+ reads | Cache + read replicas | Balance checks |
| 50-50 | Write optimization + sharding | Trading platform |
| 90%+ writes | Async batching + wide-column DB | Audit logs |
How does data size drive partitioning decisions?
Ten gigabytes fits comfortably on one node. Ten terabytes growing by a terabyte a month forces you to think about sharding, archival, and tiered storage early.
package com.bank.capacity;
public class DataSizeEstimator {
public static void main(String[] args) {
long accounts = 1_000_000;
int bytesPerAccount = 500; // account record
int transactionsPerAccountPerMonth = 30;
int bytesPerTransaction = 200;
long accountDataMB = (accounts * bytesPerAccount) / (1024 * 1024);
long txPerMonth = accounts * transactionsPerAccountPerMonth;
long txDataPerMonthGB =
(txPerMonth * bytesPerTransaction) / (1024L * 1024 * 1024);
System.out.printf("Account data: %d MB (fits one node)%n",
accountDataMB);
System.out.printf("Tx data/month: %d GB%n", txDataPerMonthGB);
System.out.printf("Tx data/year: %d GB%n",
txDataPerMonthGB * 12);
System.out.println("Decision: archive old transactions, " +
"consider sharding after 1 year");
}
}
Account data: 476 MB (fits one node)
Tx data/month: 5 GB
Tx data/year: 67 GB
Decision: archive old transactions, consider sharding after 1 year
How do latency and consistency requirements drive design?
A tight latency target like P99 under 100 milliseconds forces caching and co-location. Strong consistency for bank balances constrains how aggressively you can cache, replicate, and distribute, while a social feed can tolerate eventual consistency and scale much more freely.
package com.bank.capacity;
public class ConsistencyDecision {
public enum Consistency {
STRONG, // Bank balance: must be exactly correct
EVENTUAL // Statement view: can be seconds behind
}
public static Consistency decide(String useCase) {
return switch (useCase) {
case "balance-check" -> Consistency.STRONG;
case "transfer" -> Consistency.STRONG;
case "statement" -> Consistency.EVENTUAL;
case "notification" -> Consistency.EVENTUAL;
default -> Consistency.EVENTUAL;
};
}
public static void main(String[] args) {
System.out.println("Balance: " + decide("balance-check"));
System.out.println("Transfer: " + decide("transfer"));
System.out.println("Statement: " + decide("statement"));
System.out.println("Notification: " + decide("notification"));
}
}
Balance: STRONG -> read from primary DB, no stale cache
Transfer: STRONG -> ACID transaction, no eventual consistency
Statement: EVENTUAL -> cache + read replica acceptable
Notification: EVENTUAL -> async queue, seconds of delay acceptable
| Requirement | Impact on Design |
|---|---|
| P99 < 100ms | Must cache, co-locate, use CDN |
| Strong consistency | Read from primary, limit caching TTL |
| Eventual consistency | Cache freely, use read replicas |
| High write volume | Shard, batch, async |
What are common mistakes in capacity planning?
The most common mistake is treating the headline user count as concurrent users. Another is sizing for the average instead of the peak, which causes outages on busy days.
package com.bank.capacity;
public class CapacityMistakes {
public static void main(String[] args) {
// WRONG: treating registered as concurrent
long wrongCapacity = 1_000_000; // "1M users"
System.out.println("Wrong: designing for " +
wrongCapacity + " concurrent");
System.out.println("Result: massive over-provisioning");
// WRONG: sizing for average, not peak
double avgRps = 83;
System.out.println("\nWrong: sizing for " + avgRps + " rps");
System.out.println("Result: collapses on salary day");
// CORRECT: derive the four drivers
double peakRps = 417;
System.out.println("\nCorrect: sizing for " + peakRps +
" peak rps");
System.out.println("With headroom: " + (peakRps * 1.5) +
" rps capacity");
}
}
Wrong: designing for 1000000 concurrent
Result: massive over-provisioning
Wrong: sizing for 83.0 rps
Result: collapses on salary day
Correct: sizing for 417.0 peak rps
With headroom: 625.5 rps capacity
What are the four derived drivers?
The four drivers that actually determine the architecture are peak requests per second, read-to-write ratio, data size and growth, and latency and consistency requirements.
| Driver | Question | Banking Example |
|---|---|---|
| Peak RPS | How many requests at the busiest moment? | 417 rps on salary day |
| Read/Write ratio | What percentage are reads vs writes? | 97.8% reads |
| Data size | How much data, how fast is it growing? | 67 GB/year |
| Latency + Consistency | How fast and how correct? | P99 < 100ms, strong for money |
What should a senior engineer present to stakeholders?
A senior engineer converts the vague headline into a one-page capacity brief before any design work begins.
Capacity Brief: National Bank of India Mobile App
Registered users: 1,000,000
Daily active users: 100,000 (10%)
Peak concurrent: 5,000 (5% of DAU)
Peak requests/sec: 417 (5x average)
Read/write ratio: 97.8% reads
Data growth: 67 GB/year transactions
Latency target: P99 < 100ms for balance checks
Consistency: Strong for balances and transfers
Eventual for statements and notifications
Design implications:
- Cache-heavy architecture (97.8% reads)
- 3-5 stateless app instances behind LB
- Redis cache with 30s TTL for balances
- Single primary DB + 2 read replicas
- Kafka for async statement/notification
- No sharding needed for first 2 years
Visual Overview
"1 Million Users"
│
▼
┌──────────────────────────────────────┐
│ Clarify the Number │
│ │
│ Registered: 1,000,000 (irrelevant)│
│ Daily Active: 100,000 │
│ Peak Concurrent: 5,000 │
└──────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Derive the Four Drivers │
│ │
│ 1. Peak RPS: 417 │
│ 2. Read/Write: 97.8% reads │
│ 3. Data Growth: 67 GB/year │
│ 4. Latency/Consist: P99<100ms/Strong │
└──────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Design the Architecture │
│ │
│ Cache-heavy + read replicas │
│ Stateless app + LB │
│ Kafka for async │
│ Single primary (no sharding yet) │
└──────────────────────────────────────┘
Key Takeaway
Before designing anything for scale, always clarify what a number like one million users actually means. Registered users, daily-active users, and concurrent users can differ by orders of magnitude. A million registered users might be only a hundred thousand daily-active and a few thousand concurrent at peak. Designing for the wrong one is the classic mistake that leads either to a wildly expensive over-build or to an outage on the first busy day.
The real work is converting that headline into four derived drivers: peak requests per second, derived from DAU and a peak-to-average multiplier; the read-to-write ratio, which shapes the entire data strategy; data size and growth rate, which determines partitioning needs; and latency and consistency requirements, which are often the decisive constraints. At National Bank of India, a product manager said "build for one million users," and the first instinct was to treat that as a million concurrent. Once we clarified it into about a hundred thousand DAU and a few thousand concurrent at peak, the right design was dramatically simpler.
These four drivers, not the user count, determine capacity, data architecture, and the consistency model. A 97.8% read-heavy workload with strong consistency on balances leads to a very different architecture than a write-heavy trading platform with eventual consistency.
Remember one sentence: one million users is the start of the conversation, not the answer: the real work is converting it into peak requests per second, read-to-write ratio, data size, and latency and consistency requirements.
---