Jae Moon System design

[SD] System Design Template: 2. NFR + Estimation

Step 2 after functional requirements: turn non-functional requirements into capacity estimates

Step 2: NFR + estimation

After defining the functional requirements (what the system does), estimate its non-functional requirements (NFRs) to guide storage, caching, replication, and deployment choices.

1. Scale

Estimate daily active users (DAU), relevant actions per user per day, and the read/write ratio. Then split actions/day = DAU × actions per user per day into daily reads and writes.

average QPS ≈ requests/day ÷ 86,400

For mental math, requests/day ÷ 100,000 approximates QPS. Apply it to reads and writes separately. This assumes actions roughly map to requests; account for service fan-out when relevant.

From workload mix to design decisions

Match the design to the bottleneck:

  • Read-heavy: cache repeated reads, add read replicas, and use a CDN for static, cacheable, or media content. Sharding also raises aggregate read capacity, but usually isn't the first fix for a read-only bottleneck.
  • Write-heavy or storage-bound: horizontally shard to spread writes and data across partitions.
  • Bursty async writes: queue or batch to smooth bursts and reduce per-write overhead.
  • Huge append-heavy throughput: consider Cassandra or ScyllaDB. Their LSM design writes to a WAL and in-memory memtable, then flushes large sequential batches to disk, minimizing random I/O and improving throughput.

Rule of thumb: reads → cache/replicas; writes or storage → sharding; bursty async writes → queue/batch; huge append-heavy writes → LSM store.

2. Traffic shape

Estimate peak factor, burstiness, and hotspots/skew. Default to a 3× average peak when unspecified.

peak read QPS  = average read QPS × peak factor
peak write QPS = average write QPS × peak factor

Flash crowds, scheduled jobs, or popular keys can create bursts/hotspots beyond that estimate.

High or bursty traffic

For high or bursty traffic, a load balancer spreads requests across service instances; horizontal autoscaling adds/removes instances (typically pods via Kubernetes HPA); and a queue buffers async work. Ensure workers can drain the queue.

3. Latency

Ask for the target and whether p95/p99 matters. Assume roughly under 200 ms for normal interactive requests; tail latency matters when slow calls hold up users or fan-out requests.

Low latency

Reduce repeated or distant work and keep utilization below saturation:

  • Cache database reads or computation; use a CDN to serve content closer to users.
  • Keep utilization below saturation to prevent queue buildup and p95/p99 spikes.
  • Reduce cross-region calls:
    • Route users to the nearest region and replicate read data locally.
    • Keep dependent services in the same region and use regional caches.
    • Avoid synchronous cross-region calls on the critical path.
  • Precompute expensive results when possible.
  • Move non-critical work to asynchronous processing.

4. Availability

Ask for the target (default 99.9%) and its scope: whole system or user-facing operation. It affects replication, failover, and multi-AZ deployment.

  • Run multiple service instances across availability zones; use health checks and automatic failover to route around failures. Keep services stateless where possible.
  • Replication choice depends on acceptable data loss: async replication offers lower latency and can keep writes available, but replicas may lag and failover may lose recent writes. Sync/quorum replication better protects committed writes but can add latency or reduce write availability when replicas are unreachable.
  • Use multi-region deployment for regional failures, with added routing and data-conflict complexity.
  • Define recovery objectives: RPO (acceptable data loss) and RTO (acceptable recovery time).

5. Consistency

Ask whether reads must see the latest committed write. Balances, payments, inventory, and uniqueness usually need strong consistency; feeds, likes, and analytics often tolerate eventual consistency. Choose based on the cost of stale results.

  • Strong: read from the primary or a guaranteed caught-up replica; synchronous/quorum replication or read-after-write coordination may be needed. This raises latency and can reduce availability during failures or partitions.
  • Eventual: serve replica reads with asynchronous replication for better latency/availability, accepting temporary stale reads.

6. Geography

Ask whether users are regional or global and need low latency worldwide. This informs CDN use, multi-region deployment, and replication strategy.

7. Data size

Estimate record, request/response, and relevant media sizes. Defaults: metadata ~1 KB, image ~1–5 MB; estimate video from requirements.

storage growth/day = writes/day × bytes per write
bandwidth          = QPS × payload size

Use consistent units; account separately for replicas, indexes, and backups. For large payloads, estimate read and write bandwidth separately when rates or sizes differ.

8. Storage choice

Choose primarily by access pattern and consistency/transaction needs, then check scale:

  • Joins, transactions, constraints, flexible queries → relational DB (Postgres, MySQL).
  • Simple key access, huge scale, easy partitioning → NoSQL (DynamoDB, Cassandra).
  • Very write/append-heavy, predictable partition-key queries → Cassandra or ScyllaDB.
  • Evolving document schema → MongoDB; core graph traversals → graph DB; time-series metrics/events → time-series DB.
  • Large blobs/media → object storage, not a conventional DB.

Check consistency/transactions, query patterns, dataset size, read/write QPS, partitionability, secondary indexes/ad-hoc queries, and schema flexibility.

What to carry forward

Carry forward these headline estimates:

  • Average read QPS
  • Average write QPS
  • Peak read QPS
  • Peak write QPS
  • Storage growth per day
  • Bandwidth, when payloads are large

If unspecified, state assumptions. One large-consumer starting point:

I'll assume 100M DAU, roughly 10 relevant actions per user per day, a 3× peak factor, and around 1 KB per metadata record.

Split actions by an explicit read/write ratio before deriving QPS and storage growth.