Tuần 02: Back-of-the-envelope Estimation

“Một System Architect không nói ‘nhiều lắm’. Họ nói ‘50,000 requests per second, mỗi request trung bình 2KB, tức khoảng 100MB/s bandwidth’.”

Tags: system-design estimation alex-xu Prerequisite: Tuan-01-Scale-From-Zero-To-Millions Liên quan: Tuan-05-Load-Balancer · Tuan-06-Cache-Strategy · Tuan-07-Database-Sharding-Replication · Tuan-16-Design-URL-Shortener


1. Context & Why

Analogy đời thường

Hieu, tưởng tượng em mở một quán phở. Trước khi thuê mặt bằng, em cần trả lời:

  • Mỗi ngày có bao nhiêu khách? → QPS (Queries Per Second)
  • Mỗi khách ăn bao nhiêu phở? → Payload size
  • Cần bao nhiêu bàn ghế? → Concurrent connections
  • Kho nguyên liệu cần bao lớn? → Storage
  • Cần bao nhiêu nhân viên phục vụ? → Server instances
  • Bếp chạy tối đa mấy tô/phút? → Throughput

Nếu em thuê mặt bằng 10 bàn mà ngày nào cũng 500 khách → quán sập. Nếu em thuê 500 bàn mà chỉ 10 khách/ngày → phá sản vì chi phí.

Back-of-the-envelope estimation chính là kỹ năng tính nhanh để ra quyết định kiến trúc hợp lý trước khi viết một dòng code nào.

Tại sao Alex Xu đặt nó ở Chương 2?

Vì đây là ngôn ngữ chung của System Design Interview. Interviewer không muốn nghe “dùng Redis cho nhanh”. Họ muốn nghe:

“Với 10M DAU, peak QPS khoảng 20K, mỗi query cần lookup < 1ms. Redis single node handle được 100K ops/s, nên một node là đủ cho read. Nhưng data size khoảng 50GB, cần cluster 3 nodes cho memory.”

Đó là sự khác biệt giữa Junior và Architect.


2. Deep Dive — Các khái niệm cốt lõi

2.1 Power of 2 — Bảng tra cứu bắt buộc nhớ

Đây là bảng mà mọi System Architect đều phải thuộc nằm lòng:

PowerGiá trị xấp xỉÝ nghĩa thực tế
1 Thousand (1 KB)Một đoạn text ngắn
1 Million (1 MB)Một bức ảnh chất lượng trung bình
1 Billion (1 GB)~250 bài hát MP3
1 Trillion (1 TB)Một ổ cứng lớn
1 Quadrillion (1 PB)Toàn bộ thư viện Quốc hội Mỹ x3

2.2 Latency Numbers Every Programmer Should Know

Nguồn gốc: Jeff Dean (Google), cập nhật cho phần cứng hiện đại (~2024)

OperationLatencySo sánh
L1 cache reference1 nsNhìn xuống bàn phím
L2 cache reference4 nsNhìn sang đồng nghiệp
Main memory reference100 nsĐi lấy nước ở pantry
SSD random read16 μsĐi toilet
HDD seek2 msĐi pha cà phê
Network round trip (same DC)0.5 msGọi điện cho đồng nghiệp cùng toà nhà
Network round trip (cross-region)150 msGọi quốc tế
Read 1 MB from memory3 μs
Read 1 MB from SSD49 μs
Read 1 MB from HDD825 μs
Read 1 MB from network (1 Gbps)10 ms

Aha Moment #1: SSD nhanh hơn HDD 50 lần cho random read. Nhưng memory nhanh hơn SSD 160 lần. Đó là lý do cache tồn tại → xem Tuan-06-Cache-Strategy.

Aha Moment #2: Network round trip cùng data center (0.5ms) vs cross-region (150ms) — chênh 300 lần. Đó là lý do CDN và multi-region deployment tồn tại → xem Tuan-03-Networking-DNS-CDN.

2.3 Availability Numbers

AvailabilityDowntime/nămDowntime/thángDowntime/tuần
99% (two 9s)3.65 ngày7.31 giờ1.68 giờ
99.9% (three 9s)8.77 giờ43.83 phút10.08 phút
99.99% (four 9s)52.60 phút4.38 phút1.01 phút
99.999% (five 9s)5.26 phút26.30 giây6.05 giây

Hầu hết SLA thương mại nhắm tới 99.9% – 99.99%. Five 9s cực kỳ đắt đỏ và thường chỉ dành cho hệ thống payment/healthcare.


3. Framework ước lượng — Quy trình 4 bước

Bước 1: Xác định số liệu đầu vào (Assumptions)

Luôn bắt đầu bằng câu hỏi:

  • DAU (Daily Active Users) — Bao nhiêu người dùng/ngày?
  • Tỉ lệ Read:Write — Hệ thống read-heavy hay write-heavy?
  • Kích thước trung bình — Mỗi object/request bao nhiêu byte?
  • Retention period — Giữ data bao lâu?

Bước 2: Tính QPS (Queries Per Second)

Rule of thumb: Peak thường gấp 2x – 5x so với average. Hệ thống e-commerce vào flash sale có thể 10x – 50x.

Bước 3: Tính Storage

Bước 4: Tính Bandwidth


4. Ví dụ thực hành: Ước lượng cho URL Shortener

Tham chiếu: Tuan-16-Design-URL-Shortener, sdi.anhvy.dev — URL Shortener

Assumptions (Đặt giả thiết)

Thông sốGiá trịGiải thích
DAU100MHệ thống quy mô lớn (Bitly-like)
URL shortens/user/day0.1Không phải ai cũng tạo link mỗi ngày
URL reads/user/day1Mỗi người click trung bình 1 short link/ngày
Read:Write ratio10:1Read-heavy system
Avg URL size500 bytesOriginal URL + metadata
Retention5 năm

Tính QPS

Nhận xét: 3,500 read QPS — một server trung bình với Redis đã handle được. Chưa cần phức tạp hoá architecture.

Tính Storage

Nhận xét: 10TB trong 5 năm — vẫn trong phạm vi single database cluster (PostgreSQL + partitioning). Chưa bắt buộc phải shard.

Tính Bandwidth

Nhận xét: Bandwidth cực kỳ nhỏ. Không phải bottleneck.

Tính Cache Memory

Theo Pareto principle (80/20 rule): 20% URL tạo ra 80% traffic.

Redis cluster 3 nodes × 16GB mỗi node = 48GB → đủ chứa hot data.

Tóm tắt ước lượng

MetricValue
Write QPS (avg)~116/s
Read QPS (peak)~3,500/s
New URLs/day10M
Storage (5 years)~10 TB
Bandwidth out (peak)~14 Mbps
Cache memory~30 GB

5. Ví dụ thực hành 2: Ước lượng cho Chat System

Tham chiếu: Tuan-17-Design-Chat-System

Assumptions

Thông sốGiá trị
DAU50M
Messages sent/user/day40
Avg message size100 bytes
Group chats avg size50 members
% messages in groups60%
Media messages10% of total, avg 200KB
RetentionVĩnh viễn

Tính nhanh

Alert: 69K write QPS — đây là write-heavy system. Cần Message Queue làm buffer → Tuan-08-Message-Queue.

Text storage/day:

Media storage/day:

Alert: 40TB media/ngày! Đây là bottleneck thực sự. Cần Object Storage (S3) + CDNTuan-03-Networking-DNS-CDN.

Text storage/năm: Media storage/năm:


6. Security First — Estimation cũng cần bảo mật

6.1 Ước lượng cho Rate Limiting

Khi tính QPS, ngay lập tức phải nghĩ tới abuse scenario:

Nếu hệ thống chỉ handle 3,500 QPS → DDoS chỉ cần 0.35% capacity của botnet là đã quá tải.

Giải pháp ước lượng rate limit:

Xem chi tiết: Tuan-09-Rate-Limiter

6.2 Ước lượng Storage cho Audit Log

Trong hệ thống cần compliance (PCI-DSS, HIPAA, SOX):

Audit log thường lớn hơn data chính! Cần compression + tiered storage (hot/warm/cold) → xem Tuan-15-Data-Security-Encryption.

6.3 Key Rotation Estimation

Nếu dùng encryption at rest với key rotation mỗi 90 ngày:

Phải quản lý 35 encryption keys + decryption backward compatibility → cần KMS (Key Management Service) → Tuan-15-Data-Security-Encryption.


7. DevOps/Ops-Light — Monitoring Capacity

7.1 Capacity Alert Thresholds

Từ estimation, đặt alert:

# prometheus-alerts.yml
groups:
  - name: capacity_planning
    rules:
      - alert: QPSNearCapacity
        expr: rate(http_requests_total[5m]) > 2800  # 80% of 3,500 peak
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "QPS approaching peak capacity ({{ $value }}/s)"
 
      - alert: StorageGrowthAnomaly
        expr: predict_linear(disk_used_bytes[7d], 30*24*3600) > disk_total_bytes * 0.9
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "Storage predicted to reach 90% in 30 days"
 
      - alert: CacheHitRateDropped
        expr: redis_keyspace_hits_total / (redis_keyspace_hits_total + redis_keyspace_misses_total) < 0.8
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "Cache hit rate dropped below 80%"

7.2 Grafana Dashboard Essentials

Từ estimation, cần dashboard theo dõi:

PanelQuery (PromQL)Threshold
QPS Realtimerate(http_requests_total[1m])Warning: 80% peak, Critical: 95%
P99 Latencyhistogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))< 200ms
Error Raterate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m])< 0.1%
Storage Growthdisk_used_bytesPredict 90 days
Cache Hit Rateredis_keyspace_hits / (hits + misses)> 80%

Chi tiết: Tuan-13-Monitoring-Observability


8. Code Example — Estimation Calculator

Python: Quick Estimation Tool

"""
Back-of-the-envelope Estimation Calculator
Sử dụng trong System Design Interview prep
"""
 
from dataclasses import dataclass
from enum import Enum
 
class TimeUnit(Enum):
    SECOND = 1
    MINUTE = 60
    HOUR = 3600
    DAY = 86400
    MONTH = 2592000  # 30 days
    YEAR = 31536000  # 365 days
 
@dataclass
class SystemEstimation:
    name: str
    dau: int
    reads_per_user_per_day: float
    writes_per_user_per_day: float
    avg_read_size_bytes: int
    avg_write_size_bytes: int
    retention_days: int
    peak_multiplier: float = 3.0
    cache_hot_data_pct: float = 0.20  # Pareto 80/20
 
    @property
    def write_qps_avg(self) -> float:
        return (self.dau * self.writes_per_user_per_day) / TimeUnit.DAY.value
 
    @property
    def read_qps_avg(self) -> float:
        return (self.dau * self.reads_per_user_per_day) / TimeUnit.DAY.value
 
    @property
    def read_qps_peak(self) -> float:
        return self.read_qps_avg * self.peak_multiplier
 
    @property
    def write_qps_peak(self) -> float:
        return self.write_qps_avg * self.peak_multiplier
 
    @property
    def storage_per_day_gb(self) -> float:
        return (self.dau * self.writes_per_user_per_day * self.avg_write_size_bytes) / (1024**3)
 
    @property
    def storage_total_tb(self) -> float:
        return (self.storage_per_day_gb * self.retention_days) / 1024
 
    @property
    def bandwidth_in_mbps(self) -> float:
        return (self.write_qps_peak * self.avg_write_size_bytes * 8) / (1024**2)
 
    @property
    def bandwidth_out_mbps(self) -> float:
        return (self.read_qps_peak * self.avg_read_size_bytes * 8) / (1024**2)
 
    @property
    def cache_size_gb(self) -> float:
        daily_read_data = self.read_qps_avg * TimeUnit.DAY.value * self.avg_read_size_bytes
        return (daily_read_data * self.cache_hot_data_pct) / (1024**3)
 
    def report(self) -> str:
        def fmt(n: float) -> str:
            if n >= 1_000_000:
                return f"{n/1_000_000:.1f}M"
            if n >= 1_000:
                return f"{n/1_000:.1f}K"
            return f"{n:.1f}"
 
        return f"""
╔══════════════════════════════════════════════════╗
║  ESTIMATION REPORT: {self.name:<28}
╠══════════════════════════════════════════════════╣
║  DAU:              {fmt(self.dau):>12}
║  Read:Write ratio: {self.reads_per_user_per_day/max(self.writes_per_user_per_day,0.001):>10.0f}:1                 ║
╠──────────────────────────────────────────────────╣
║  QPS (avg)                                       ║
║    Read:           {fmt(self.read_qps_avg):>12} /s              ║
║    Write:          {fmt(self.write_qps_avg):>12} /s              ║
║  QPS (peak, x{self.peak_multiplier:.0f})                                  ║
║    Read:           {fmt(self.read_qps_peak):>12} /s              ║
║    Write:          {fmt(self.write_qps_peak):>12} /s              ║
╠──────────────────────────────────────────────────╣
║  Storage                                         ║
║    Per day:        {self.storage_per_day_gb:>10.2f} GB              ║
║    Total ({self.retention_days//365}yr):     {self.storage_total_tb:>10.2f} TB              ║
╠──────────────────────────────────────────────────╣
║  Bandwidth (peak)                                ║
║    Inbound:        {self.bandwidth_in_mbps:>10.2f} Mbps            ║
║    Outbound:       {self.bandwidth_out_mbps:>10.2f} Mbps            ║
╠──────────────────────────────────────────────────╣
║  Cache (20% hot data)                            ║
║    Size:           {self.cache_size_gb:>10.2f} GB              ║
╚══════════════════════════════════════════════════╝
"""
 
 
# === Ví dụ sử dụng ===
 
if __name__ == "__main__":
    # Case 1: URL Shortener
    url_shortener = SystemEstimation(
        name="URL Shortener",
        dau=100_000_000,
        reads_per_user_per_day=1.0,
        writes_per_user_per_day=0.1,
        avg_read_size_bytes=500,
        avg_write_size_bytes=500,
        retention_days=365 * 5,
        peak_multiplier=3.0,
    )
    print(url_shortener.report())
 
    # Case 2: Chat System
    chat_system = SystemEstimation(
        name="Chat System (text only)",
        dau=50_000_000,
        reads_per_user_per_day=200.0,  # read nhiều hơn gửi
        writes_per_user_per_day=40.0,
        avg_read_size_bytes=100,
        avg_write_size_bytes=100,
        retention_days=365 * 7,
        peak_multiplier=3.0,
    )
    print(chat_system.report())
 
    # Case 3: Instagram-like (media heavy)
    instagram = SystemEstimation(
        name="Photo Sharing App",
        dau=500_000_000,
        reads_per_user_per_day=50.0,
        writes_per_user_per_day=0.5,
        avg_read_size_bytes=200_000,   # 200KB avg image thumbnail
        avg_write_size_bytes=2_000_000, # 2MB avg uploaded image
        retention_days=365 * 10,
        peak_multiplier=3.0,
    )
    print(instagram.report())

Node.js: Express Middleware — Request Counting cho Estimation Validation

// middleware/estimation-validator.js
// Đặt vào production để validate estimation vs thực tế
 
const prometheus = require('prom-client');
 
const requestCounter = new prometheus.Counter({
  name: 'http_requests_total',
  help: 'Total HTTP requests',
  labelNames: ['method', 'path', 'status'],
});
 
const requestDuration = new prometheus.Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP request duration in seconds',
  labelNames: ['method', 'path'],
  buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5],
});
 
const requestSizeBytes = new prometheus.Histogram({
  name: 'http_request_size_bytes',
  help: 'HTTP request payload size',
  labelNames: ['method', 'path'],
  buckets: [100, 500, 1000, 5000, 10000, 50000, 100000],
});
 
function estimationValidator(req, res, next) {
  const start = process.hrtime.bigint();
 
  res.on('finish', () => {
    const duration = Number(process.hrtime.bigint() - start) / 1e9;
    const path = req.route?.path || req.path;
 
    requestCounter.inc({ method: req.method, path, status: res.statusCode });
    requestDuration.observe({ method: req.method, path }, duration);
    requestSizeBytes.observe(
      { method: req.method, path },
      parseInt(req.headers['content-length'] || '0', 10)
    );
  });
 
  next();
}
 
module.exports = { estimationValidator, requestCounter, requestDuration };

9. System Design Diagram — Estimation trong context

flowchart TD
    subgraph "Step 1: Gather Requirements"
        A[Functional Requirements] --> B[Non-functional Requirements]
        B --> C["Assumptions<br/>(DAU, Read:Write, Size)"]
    end

    subgraph "Step 2: Estimation"
        C --> D["QPS Calculation<br/>$$QPS = DAU × actions / 86400$$"]
        C --> E["Storage Calculation<br/>$$Storage = writes/day × size × days$$"]
        C --> F["Bandwidth Calculation<br/>$$BW = QPS × payload\_size$$"]
        D --> G["Cache Sizing<br/>$$Cache = daily\_reads × 20\%$$"]
    end

    subgraph "Step 3: Architecture Decision"
        D --> H{QPS < 10K?}
        H -->|Yes| I["Single Server<br/>+ Read Replica"]
        H -->|No| J["Distributed System<br/>+ Load Balancer<br/>+ Sharding"]
        E --> K{Storage < 1TB?}
        K -->|Yes| L["Single DB<br/>+ Partitioning"]
        K -->|No| M["Sharded DB<br/>or NoSQL"]
        G --> N{Cache < 64GB?}
        N -->|Yes| O["Single Redis Node"]
        N -->|No| P["Redis Cluster"]
    end

    subgraph "Step 4: Ops Validation"
        I --> Q["Prometheus Alerts<br/>based on estimated thresholds"]
        J --> Q
        Q --> R["Dashboard:<br/>Actual vs Estimated"]
    end

    style D fill:#f9a825,stroke:#333,stroke-width:2px
    style E fill:#f9a825,stroke:#333,stroke-width:2px
    style F fill:#f9a825,stroke:#333,stroke-width:2px

10. Quick Reference — Các con số cần nhớ

QPS Benchmarks (single instance)

ComponentCapacityGhi chú
Nginx (static)50,000+ req/sReverse proxy
Node.js (Express)5,000–15,000 req/sTuỳ logic complexity
Java (Spring Boot)10,000–30,000 req/sVới thread pool tuned
PostgreSQL5,000–20,000 QPSSimple queries, indexing tốt
MySQL5,000–20,000 QPSTương tự PostgreSQL
Redis100,000+ ops/sIn-memory, single thread
Kafka (single broker)100,000+ msg/sThroughput-oriented
Elasticsearch5,000–10,000 QPSSearch queries

Storage Quick Math

Loại dataAvg size1M records1B records
Short text (tweet)300 bytes300 MB300 GB
JSON document2 KB2 GB2 TB
Image thumbnail50 KB50 GB50 TB
Full image2 MB2 TB2 PB
Video (1 min, 720p)50 MB50 TB50 PB

11. Bài tập tự luyện

Bài 1: Design Twitter — Estimation

Assumptions: 300M MAU, 50% DAU, avg 5 tweets/day/active user, avg 2 photos/tweet (100KB each), 100 reads/user/day.

Tính:

  • Write QPS (avg & peak)
  • Read QPS (avg & peak)
  • Storage/year (text + media)
  • Bandwidth (in + out)
  • Cache requirement

Bài 2: Design YouTube — Estimation

Assumptions: 2B MAU, 30% DAU, avg 5 videos watched/day, avg video = 50MB (3 min, 720p), 0.01% users upload 1 video/day.

Tính:

  • Upload QPS
  • Streaming bandwidth
  • Storage/year
  • CDN cache sizing

Bài 3: Design Payment System — Estimation + Security

Assumptions: 50M DAU, 2 transactions/user/day, avg transaction payload = 1KB, cần audit log cho 7 năm.

Tính:

  • Transaction QPS
  • Storage (transaction + audit log)
  • Encryption overhead (AES-256 adds ~16 bytes/block)
  • Key rotation schedule

12. Common Pitfalls — Sai lầm thường gặp

Pitfall 1: Quên tính Peak

Sai: “100M DAU ÷ 86,400 = 1,157 QPS, server dư sức.” Đúng: Peak = 3x–5x average. Nếu có event (Black Friday, Tết) → 10x–50x. Luôn design cho peak, pay cho average.

Pitfall 2: Nhầm lẫn đơn vị

Mbps (megabits) vs MBps (megabytes). 1 MBps = 8 Mbps. Nhiều người tính bandwidth sai vì quên convert.

Pitfall 3: Quên tính metadata & overhead

Mỗi row trong DB không chỉ có data. Cần thêm: index (~30% data size), WAL logs, replication overhead, backup. Rule of thumb: Nhân storage estimate với 3x cho production.

Pitfall 4: Over-engineer dựa trên estimation

Estimation cho thấy 1,000 QPS mà lập tức dựng Kubernetes cluster 50 nodes → lãng phí. Start simple, scale khi cầnTuan-01-Scale-From-Zero-To-Millions.

Pitfall 5: Không validate estimation với thực tế

Estimation chỉ là giả thuyết. Sau khi deploy, phải đo thực tế bằng monitoring → Tuan-13-Monitoring-Observability. Nếu estimation sai > 10x → phải xem lại assumptions.


13. Aha Moments — Đúc kết

#1: Estimation không cần chính xác. Cần đúng order of magnitude (bậc đại lượng). 1,000 vs 1,200 QPS không quan trọng. Nhưng 1,000 vs 100,000 QPS → kiến trúc hoàn toàn khác.

#2: Hai con số quan trọng nhất là QPSStorage. Từ đó suy ra mọi thứ khác.

#3: Estimation là vũ khí giao tiếp. Khi nói “hệ thống cần handle 50K QPS”, cả team đều hiểu scale. Khi nói “hệ thống cần nhanh” → không ai hiểu gì.

#4: Security estimation thường bị bỏ quên. Audit log, encryption overhead, rate limiting — tất cả đều tiêu tốn resource và phải được tính vào.


Tham khảo


Tuần tới: Tuan-03-Networking-DNS-CDN — Hiểu luồng request từ browser đến server