Pick a depth. Each prompt opens in your AI pre-loaded with the lesson. Click a row to preview the prompt.
A million users is a marketing number. It tells you nothing about what to build until you convert it, and the conversion is nearly always surprising: a million registered users on a normal consumer app works out to roughly one hundred reads per second, which one well-tuned database server handles without breaking a sweat. Builders who never do this arithmetic reach for sharding, event sourcing and multi-region replication for a workload that fits on a laptop, and then spend two years operating complexity they never needed. The arithmetic also relocates the real problem. On the photo app below, compute is trivial and the bill is almost entirely bytes leaving the building — so the interesting design decision is a CDN and a thumbnail size, not a database. You cannot see that without doing the maths, and the maths is four multiplications. Estimation is not about being right; it is about being right to within one order of magnitude, fast enough that the number arrives before the design does.
The chain is always the same. Users → daily actives → actions per day → requests per day → average rps → peak rps → bytes per day → bytes per year → money. Nine steps, all multiplication, none of it clever.
Run the script below and read three results in particular.
Result one: 104 reads per second. A million registered users, 30 percent daily active, ten app opens each, three reads per open. That is 9 million reads a day, which is 104 per second on average and about 313 at peak. A single modest server does that. Every distributed-systems reflex you were about to reach for is, at this scale, premature — and now you can say so with a number instead of an opinion.
Result two: 0.35 writes per second. Thirty thousand posts a day. That is one write every three seconds. The read-to-write ratio is 300 to 1, which is the single most important line in the whole output, because it tells you this is a caching problem and not a write-throughput problem. A 300:1 ratio means a cache in front of the read path is worth more than any amount of database tuning.
Result three: the bill is egress. Storing a year of photos costs about 554 dollars a month, roughly 49 thousand rupees. Serving them without a CDN costs about 4,590 dollars a month, roughly 4 lakh rupees — eight times the storage line. Put a CDN in front and the same traffic costs about 1,256 dollars, roughly 1.1 lakh, saving around 3,334 dollars or 2.9 lakh every month. That CDN is not a performance optimisation. It is the largest single line item on the invoice.
One unit trap worth internalising: cloud bills are decimal, so a billed GB is 10 to the ninth bytes, not 2 to the thirtieth. Mixing the two gives you a 7 percent error, which is inside the noise of your assumptions — but only if you know you did it.
# envelope.py — turn "a million users" into requests, bytes and money.
# Every number below is an ASSUMPTION. Say them out loud, change one,
# re-run, and watch what moves. Cloud bills are decimal: GB = 10**9 bytes.
SEC_PER_DAY = 86_400
KB, MB, GB = 10**3, 10**6, 10**9
# ---- assumptions -------------------------------------------------------
registered_users = 1_000_000
dau_fraction = 0.30 # open the app on a given day
sessions_per_dau = 10 # app opens per active user per day
reads_per_session = 3 # feed page + 2 profile fetches
posting_fraction = 0.10 # share of DAU that posts
posts_per_poster = 1
photo_bytes = 2 * MB # original upload, kept forever
thumb_bytes = 200 * KB # what the feed actually delivers
row_bytes = 1 * KB # metadata row per post
peak_multiplier = 3 # busiest hour vs 24h average
db_replicas = 3
# ---- prices (list-price order of magnitude; check today's rate card) ----
usd_per_gb_month_blob = 0.023
usd_per_gb_origin_out = 0.085
usd_per_gb_cdn_out = 0.020
cdn_hit_rate = 0.95
inr_per_usd = 88 # set this to today's rate
# ---- traffic -----------------------------------------------------------
dau = registered_users * dau_fraction
reads_day = dau * sessions_per_dau * reads_per_session
writes_day = dau * posting_fraction * posts_per_poster
read_rps = reads_day / SEC_PER_DAY
write_rps = writes_day / SEC_PER_DAY
rw_ratio = reads_day / writes_day
# ---- bytes -------------------------------------------------------------
blob_day_gb = writes_day * (photo_bytes + thumb_bytes) / GB
blob_yr_gb = blob_day_gb * 365
meta_yr_gb = writes_day * row_bytes * 365 / GB * db_replicas
egress_day_gb = reads_day * thumb_bytes / GB
egress_mo_gb = egress_day_gb * 30
# ---- money -------------------------------------------------------------
blob_usd_mo = blob_yr_gb * usd_per_gb_month_blob # after 12 months
naive_out_usd = egress_mo_gb * usd_per_gb_origin_out
cdn_out_usd = (egress_mo_gb * cdn_hit_rate * usd_per_gb_cdn_out
+ egress_mo_gb * (1 - cdn_hit_rate) * usd_per_gb_origin_out)
def money(usd):
return f"${usd:,.0f} / Rs {usd * inr_per_usd:,.0f}"
print(f"DAU {dau:>12,.0f}")
print(f"reads/day {reads_day:>12,.0f}")
print(f"writes/day {writes_day:>12,.0f}")
print(f"read:write {rw_ratio:>12,.0f} : 1")
print(f"avg read rps {read_rps:>12,.1f}")
print(f"peak read rps {read_rps * peak_multiplier:>12,.1f}")
print(f"avg write rps {write_rps:>12,.2f}")
print(f"peak write rps {write_rps * peak_multiplier:>12,.2f}")
print(f"blob GB/day {blob_day_gb:>12,.1f}")
print(f"blob GB after 1 yr {blob_yr_gb:>12,.0f}")
print(f"metadata GB after 1yr {meta_yr_gb:>12,.1f} (x{db_replicas} replicas)")
print(f"egress GB/month {egress_mo_gb:>12,.0f}")
print("-" * 48)
print(f"blob storage / month {money(blob_usd_mo)}")
print(f"egress, no CDN / month {money(naive_out_usd)}")
print(f"egress, with CDN {money(cdn_out_usd)}")
print(f"CDN saves / month {money(naive_out_usd - cdn_out_usd)}")python3 main.py