Chương 01 · Foundations

Scalability Fundamentals

Vertical vs Horizontal scaling, latency vs throughput, percentile P95/P99, availability nine-nines, bottleneck analysis, và back-of-envelope capacity math.

1. Vì sao Scalability quan trọng?

Scalability = khả năng hệ thống giữ performance khi load tăng. Một hệ scalable: load tăng 10× → throughput tăng gần 10×, latency không tăng đáng kể, chi phí tăng tuyến tính (không quadratic).

Hầu hết bug "khi prod chết" không phải bug logic — mà là bug scale. Code chạy đúng với 100 user, vỡ với 100k user vì:

  • Query SELECT * không có index — 30s với bảng 100M dòng.
  • 1 tab Excel mở 100MB JSON parse — RAM full.
  • Mỗi request gọi external API — 100k QPS → bị throttle.
  • 1 cron job lock cả bảng users — block website 5 phút.

1.1. Có 2 cách phát hiện vấn đề scale

  • Reactive — production chết. Kinh khủng. Mất tiền và uy tín.
  • Proactive — load test trước, profile, capacity planning. Đắt thời gian nhưng cứu mạng.
"Một thiết kế tốt là thiết kế hỏng theo cách dự đoán được. Một thiết kế tệ là thiết kế hỏng theo cách bất ngờ."

2. Vertical Scaling vs Horizontal Scaling

Vertical Scaling (Scale Up)

Mua server mạnh hơn — nhiều CPU core, nhiều RAM, NVMe nhanh hơn.

  • Ưu: đơn giản, không sửa app, không phải nghĩ về CAP.
  • Nhược: trần (server lớn nhất ~1TB RAM, 128 core), giá phi tuyến (server 2× mạnh có thể đắt 5×), SPOF (server chết = hệ chết).
  • Khi dùng: app nhỏ-vừa, chưa đến giới hạn 1 server, ít khi cần HA tuyệt đối.

Horizontal Scaling (Scale Out)

Thêm nhiều server, chia tải qua load balancer.

  • Ưu: scale gần vô hạn, HA tự nhiên (1 server chết, các server khác vẫn chạy), commodity hardware rẻ.
  • Nhược: app phải stateless, complexity cao (LB, distributed cache, distributed transaction), CAP trade-off.
  • Khi dùng: app cần scale lớn, downtime không chấp nhận được.

2.1. Quy tắc thực dụng

Thứ tự áp dụng Vertical → Cache → Read Replica → Sharding. Đa số app dừng ở bước 2-3. Postgres 1 node có thể xử lý 50k QPS với cấu hình tốt — đừng vội nhảy sang sharding.

2.2. Stateless vs Stateful

Horizontal scaling đòi hỏi app stateless — không lưu data trong RAM của process. Vì sao?

// ❌ Stateful — lưu session trong RAM
const sessions = new Map();
app.post('/login', (req, res) => {
  const sessionId = generateId();
  sessions.set(sessionId, req.body.userId);
  res.cookie('session', sessionId);
});

// User login → server A (sessions có entry)
// Request tiếp → load balancer route đến server B (KHÔNG có entry)
// → 401 Unauthorized 😱

Sửa: lưu session ở shared store (Redis, DB):

// ✓ Stateless — session trong Redis
app.post('/login', async (req, res) => {
  const sessionId = generateId();
  await redis.set(`session:${sessionId}`, req.body.userId, 'EX', 3600);
  res.cookie('session', sessionId);
});

Component stateful (DB, Redis, file storage) chỉ ở 1 nơi hoặc có replication explicit. App tier hoàn toàn stateless.

3. Latency vs Throughput

3.1. Định nghĩa

  • Latency — thời gian xử lý 1 request, đo bằng ms (mili-giây).
  • Throughput — số request xử lý/giây, đo bằng QPS (queries per second), RPS (requests per second), TPS (transactions per second).

3.2. Mối quan hệ — Little's Law

L = λ × W:

  • L = số request đang trong hệ (concurrency).
  • λ = throughput (request/s).
  • W = latency trung bình (s).

Ví dụ: app xử lý 100ms/request, có 50 worker → throughput tối đa = 50 / 0.1 = 500 QPS.

Để tăng throughput: hoặc giảm latency, hoặc tăng concurrency (worker, connection).

3.3. Tăng concurrency có thể TĂNG latency (queueing)

Khi worker bận hết, request mới phải chờ trong queue — latency = queue_time + processing_time. Đây là lý do tại sao "máy chủ overload" thường thấy latency tăng vọt 10-100×, không chỉ chậm tuyến tính.

Latency ▲ │ ╱ │ ╱ ← exponential growth │ ╱ khi system gần bão hòa │ ╱ │ ╱ │ ╱ │ ───── ← stable region │ └────────────────────────────► Throughput 80% capacity

Quy tắc: không bao giờ vận hành > 80% capacity. Để dư cho burst traffic và GC pause.

4. Percentile Metrics — P50, P95, P99

Latency trung bình (mean) là metric tệ — bị skew bởi outlier. Phân phối latency thường long-tail: đa số request nhanh, vài cái rất chậm.

4.1. Tại sao mean lừa

10 request: 9 cái 50ms, 1 cái 5000ms.

  • Mean = (9×50 + 5000) / 10 = 545ms — sai lệch hoàn toàn.
  • P50 = 50ms — phản ánh median user experience.
  • P90 = 50ms — 90% nhanh.
  • P99 hoặc max = 5000ms — đây mới là worst-case.

4.2. Định nghĩa

PercentileÝ nghĩaKhi quan tâm
P50 (median)50% request nhanh hơn giá trị nàyAverage user experience
P9090% nhanh hơnMost users
P9595% nhanh hơnStandard SLO
P9999% nhanh hơnWorst-case acceptable; 1/100 user hit slowness
P99.999.9% nhanh hơnSaaS lớn — 1/1000 = nhiều user

4.3. Tail latency amplification

Vấn đề "P99 lan ra": nếu mỗi service có P99 = 10ms, request tổng đụng 10 service tuần tự → xác suất 1 service hit P99 ~ 10% — P99 tổng tăng phi tuyến tính.

Jeff Dean (Google) tóm tắt:

"Even the rare slow request becomes common when the request fans out to many services."

Cách giảm tail latency:

  • Hedged requests: gửi request đến 2 replica, lấy cái về sớm hơn.
  • Tied requests: gửi tới 1 replica, sau timeout T thì gửi thêm replica 2.
  • Batching + concurrency: gọi parallel thay tuần tự.

4.4. SLA / SLO / SLI

  • SLI (Service Level Indicator) — metric đo được: P99 latency, error rate, uptime.
  • SLO (Service Level Objective) — target nội bộ: "P99 < 500ms 99.9% thời gian".
  • SLA (Service Level Agreement) — cam kết với khách hàng có hậu quả pháp lý: "uptime > 99.9% hoặc refund".

SLO < SLA — luôn để buffer.

5. Availability — Nine Nines

AvailabilityDowntime/nămDowntime/thángDowntime/tuần
99% (2 nines)3.65 ngày7.2 giờ1.68 giờ
99.9% (3 nines)8.76 giờ43.8 phút10.1 phút
99.95%4.38 giờ21.9 phút5 phút
99.99% (4 nines)52.6 phút4.32 phút1.01 phút
99.999% (5 nines)5.26 phút25.9 giây6 giây

5.1. Compose availability

Hệ thống có nhiều component nối tiếp (sequential): availability tổng = tích availability từng cái.

  • Frontend (99.9%) × Backend (99.9%) × DB (99.9%) = 99.7% — chỉ 2.6 nines!
  • Mỗi component thêm giảm availability tổng.

Component song song (parallel, redundant): availability tổng = 1 - (1 - a)^n.

  • 2 backend instance 99.9% → 1 - (0.001)² = 99.9999% — 6 nines!
  • Đó là sức mạnh của redundancy.

5.2. Mỗi "9" thêm tốn 10×

Đi từ 99% → 99.9% có thể đơn giản (load balancer + 2 instance). 99.9% → 99.99% cần multi-AZ. 99.99% → 99.999% cần multi-region với replication phức tạp + automated failover dưới phút.

Cân nhắc thực tế: SaaS B2B thường target 99.9%. Banking/healthcare: 99.99%. Ít app cần 99.999% — chỉ telco / hệ thống critical.

5.3. Error budget

Nếu SLO = 99.9% (downtime 43 phút/tháng), bạn có "budget" 43 phút mỗi tháng để:

  • Deploy có rủi ro (canary, A/B test).
  • Maintenance.
  • Migration.

Hết budget → freeze deploy đến hết tháng. Đây là cách Google SRE quản lý risk.

6. Reliability vs Availability vs Durability

Khái niệmĐịnh nghĩaVí dụ metric
ReliabilityHệ thống hoạt động đúng (correct) liên tụcMTBF (mean time between failures)
Availability% thời gian hệ thống có thể truy cập đượcUptime % (99.9%)
DurabilityData không bị mất qua thời gian/lỗiS3: 11 nines (99.999999999%)

Có thể có hệ availability cao nhưng reliability thấp (lúc nào cũng up, nhưng trả wrong data). Có hệ durability cao nhưng availability thấp (data an toàn nhưng đôi khi không truy cập được).

Thiết kế tốt cân bằng cả 3.

7. Bottleneck Analysis & Universal Scalability Law

7.1. Bottleneck — định nghĩa

Bottleneck = thành phần chậm nhất / capacity nhỏ nhất quyết định throughput tổng. Không quan trọng các phần khác mạnh mức nào, hệ chạy bằng tốc độ bottleneck.

Phép ẩn dụ: ống dẫn nước với nhiều đoạn — đoạn hẹp nhất quyết định flow rate.

7.2. Phương pháp tìm bottleneck

  1. Đo CPU, RAM, disk I/O, network của mỗi component.
  2. Cái nào đầu tiên đạt 80%+ utilization → đó là bottleneck.
  3. Tăng capacity của bottleneck — không có ý nghĩa tăng phần khác.
  4. Sau khi fix, bottleneck dịch chuyển sang chỗ khác. Lặp lại.

7.3. Amdahl's Law

Speedup tối đa khi parallelize: S = 1 / ((1-p) + p/n), với p = phần parallelize được, n = số core.

Nếu 90% code parallelize được, dùng 100 core: S = 1 / (0.1 + 0.9/100) = 9.17×. KHÔNG phải 100×.

Hệ quả: phần tuần tự (serial) là tường giới hạn. Đó là lý do "scale ngang vô hạn" trong thực tế bị giới hạn bởi phần coordination, sync, lock.

7.4. Universal Scalability Law (USL) — Neil Gunther

Mở rộng Amdahl's bằng cách thêm coherency cost — overhead cross-coordination tăng quadratic với số node:

C(N) = N / (1 + α(N-1) + βN(N-1))

  • α = contention (queueing).
  • β = coherency (synchronize between nodes).

Khi N tăng đủ lớn, βN² thắng → throughput thực ra giảm. Đây là lý do thêm node có thể không tăng throughput mà còn giảm.

7.5. Bài học thực tế

  • Không phải lúc nào "thêm máy" = "nhanh hơn".
  • Distributed system có overhead — đôi khi 1 node mạnh nhanh hơn 10 node yếu.
  • Đo trước, scale sau.

8. Capacity Planning — Back-of-the-envelope Math

Kỹ năng phỏng vấn quan trọng: tính nhanh requirement của hệ. Quy trình 5 bước:

  1. DAU — daily active users. Nhân với 5-10 = peak concurrent.
  2. Mỗi user làm gì — read/write/upload/search QPS.
  3. Tổng QPS — DAU × actions/user / 86400 (giây/ngày). Peak = 3-5× average.
  4. Storage — size mỗi record × số record × growth.
  5. Bandwidth — request size × QPS.

8.1. Ví dụ: Twitter

  • 500M users, 20% DAU = 100M.
  • Mỗi user post 1 tweet, đọc 100 tweet → 100M write/day, 10B read/day.
  • Write QPS avg: 100M/86400 ≈ 1160 → peak ~5000.
  • Read QPS avg: 10B/86400 ≈ 116k → peak ~580k.
  • 1 tweet ~ 280 char + metadata = 1KB → 100M × 1KB = 100GB/day, 36TB/year.
  • Hot data (recent 30 days) = 3TB → fit RAM cluster cache.

8.2. Số liệu nên thuộc

OperationLatency
L1 cache reference1 ns
L2 cache reference4 ns
RAM access100 ns
Mutex lock/unlock17 ns
Compress 1KB with Snappy2 µs
SSD random read 4KB50 µs
Read 1MB sequentially from RAM250 µs
Read 1MB sequentially from SSD1 ms
Network roundtrip same DC0.5 ms
Read 1MB sequentially from HDD20 ms
Roundtrip US east ↔ US west~70 ms
Roundtrip US ↔ EU~100 ms
Roundtrip US ↔ APAC~150 ms

Nguồn: Jeff Dean (Google) — "Numbers Every Programmer Should Know".

8.3. Common QPS / Storage benchmark

  • 1 Postgres node tốt: 50k QPS read, 10-20k QPS write.
  • 1 Redis node: 100k+ QPS với latency < 1ms.
  • 1 Kafka broker: 1M messages/s.
  • nginx 1 node: 50k+ HTTP/s.
  • 1 modern CPU core: ~10k function calls/ms (1ns mỗi cái).
  • 1 server modern: 100GB+ RAM, 32-64 core, hàng TB SSD.

8.4. Quy tắc đơn vị nhanh

2^10 ≈ 10^3   (1KB ≈ 1000)
2^20 ≈ 10^6   (1MB ≈ 1M)
2^30 ≈ 10^9   (1GB ≈ 1B)
2^40 ≈ 10^12  (1TB ≈ 1T)

86400 s/day ≈ 10^5
86400 × 30 ≈ 2.6 × 10^6 (1 month)
86400 × 365 ≈ 3.15 × 10^7 (1 year)

9. Load Testing — đo trước khi vỡ

9.1. Các loại test

  • Load test — load expected, đo latency/error stable.
  • Stress test — load tăng dần đến khi vỡ → tìm capacity limit.
  • Spike test — load tăng đột ngột → kiểm tra autoscale.
  • Endurance test — load steady trong vài giờ-ngày → tìm memory leak, GC issue.
  • Capacity planning — đo từng config khác nhau, project khi nào hết capacity.

9.2. Tools

  • k6 — JS scripting, modern, dễ dùng.
  • Locust — Python, distributed.
  • JMeter — Java, GUI, classic.
  • wrk / wrk2 — đơn giản, throughput cao.
  • vegeta — Go, command-line.

9.3. Ví dụ k6

// k6-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 100 },   // ramp up
    { duration: '5m', target: 100 },   // stay
    { duration: '2m', target: 200 },   // increase
    { duration: '5m', target: 200 },
    { duration: '2m', target: 0 },     // ramp down
  ],
  thresholds: {
    'http_req_duration{status:200}': ['p(95)<500'],   // P95 < 500ms
    'http_req_failed': ['rate<0.01'],                  // <1% errors
  },
};

export default function () {
  const res = http.get('https://api.example.com/users/1');
  check(res, { 'status 200': (r) => r.status === 200 });
  sleep(1);
}

// Run:
// k6 run k6-test.js

9.4. Best practice

  • Test trên environment giống prod (hardware, config).
  • Đừng test trên localhost — không meaningful.
  • Đo nhiều metric cùng lúc: latency P50/P95/P99, error rate, CPU/RAM/IO.
  • Lặp lại test nhiều lần — variance lớn.
  • Cẩn thận với rate limit — đừng tự DDOS production.

10. Bài tập

  1. Tính capacity cho ứng dụng đặt vé phim:
    • 10M user, 1M DAU, peak hour = 8-10pm.
    • Mỗi user xem 5 phim/tháng, mỗi phim 30 lần search trước khi đặt.
    • Mỗi search trả 100 movie, mỗi movie 5KB.
    • Tính: read QPS, write QPS, storage/year, bandwidth.
  2. So sánh availability:
    • Hệ A: 1 server 99.9%.
    • Hệ B: 2 server song song mỗi cái 99.9%.
    • Hệ C: 3 server tuần tự (LB → app → DB) mỗi cái 99.9%.
    • Tính availability mỗi hệ.
  3. Latency P99 = 1000ms, P50 = 50ms. Vấn đề gì? Cách tìm root cause?
  4. Setup k6 local. Test 1 endpoint của project bạn. Tìm capacity limit của 1 node.
  5. Tính: 100k user concurrent, mỗi user xem video 1080p (5 Mbps). Tổng bandwidth bao nhiêu? Cần CDN không?
  6. Khi nào nên scale vertical thay horizontal? Cho 2 ví dụ thực tế.
  7. Theo USL, vì sao thêm node có thể không tăng (thậm chí giảm) throughput?

11. Quiz

Quiz cuối Chương 1

Vì sao mean (average) latency là metric tệ?

  • Khó tính
  • Mean = median trong mọi trường hợp
  • Bị skew bởi outlier — phân phối latency long-tail, vài request cực chậm kéo mean lên cao và che giấu trải nghiệm thật
  • Không lưu được
9 request 50ms + 1 request 5000ms → mean 545ms (không có user thực nào trải nghiệm con số này). P50 = 50, P99 = 5000ms — cho cái nhìn thực. Luôn dùng percentile, không dùng mean.

Horizontal scaling đòi hỏi app:

  • Phải dùng Java
  • Stateless — không lưu data trong RAM của process; session state ở shared store (Redis, DB)
  • Có ≥ 100 server
  • Không có database
Stateful app + horizontal scale → user log in server A, request kế tiếp đến server B → mất session. Stateless: state ở Redis/DB shared. App tier hoàn toàn stateless, chỉ DB/cache là stateful component.

99.9% availability tương đương downtime/tháng:

  • 5 phút
  • 1 phút
  • 10 giờ
  • ~43 phút
99.9% = 0.1% downtime = 0.001 × 30 ngày × 24 giờ × 60 phút ≈ 43.2 phút/tháng. 99.99% = 4.3 phút. 99.999% = 26 giây. Mỗi "9" thêm tốn 10× chi phí và phức tạp.

Hệ có 3 component nối tiếp 99.9% mỗi cái → availability tổng:

  • ~99.7% (giảm xuống do nhân tích)
  • 99.9% (giữ nguyên)
  • 99.99%
  • 100%
Sequential: A × B × C = 0.999 × 0.999 × 0.999 = 0.997 = 99.7%. Mỗi component thêm GIẢM availability tổng. Parallel ngược lại: 1 - (0.001)^n tăng nhanh. Đây là sức mạnh của redundancy.

Little's Law L = λ × W:

  • L = latency, λ = lock, W = weight
  • Liên quan database transaction
  • L (concurrency in system) = λ (throughput) × W (latency); biết 2 cái suy ra cái thứ 3
  • Chỉ áp dụng cho queue
App 100ms latency, 50 worker → max throughput = 50/0.1 = 500 QPS. Để tăng throughput: giảm latency hoặc tăng concurrency. Nguyên lý cơ bản trong queueing theory, áp dụng cho mọi hệ thống.

"Tail latency amplification" (Jeff Dean):

  • Latency tăng theo log
  • Khi 1 request fan-out qua nhiều service, P99 nhỏ ở mỗi service trở thành P99 lớn ở request tổng
  • Network always slow
  • Caching không có tác dụng
10 service mỗi cái 1% chậm → request hit 10 service có 1-(0.99)^10 ≈ 10% gặp slow. Giải: hedged request (gửi 2 replica), tied request, batch concurrent. Đây là vấn đề lớn ở microservice architecture.

"Universal Scalability Law" (USL) cho thấy:

  • Mọi hệ scale tuyến tính
  • Thêm node luôn tăng throughput
  • Hệ phân tán không có overhead
  • Vượt N nhất định, coherency cost (sync between nodes) tăng quadratic → throughput thực có thể giảm
Amdahl's Law: phần serial giới hạn speedup. USL thêm coherency cost: distributed system có overhead sync. Đỉnh throughput xảy ra ở N cụ thể; thêm N nữa giảm throughput. Đó là lý do "thêm máy" không phải lúc nào cũng = nhanh hơn.

Quy tắc thực dụng vận hành server:

  • Không vận hành quá 80% capacity — để dư cho burst, GC pause, autoscale lag
  • Vận hành 100% để tối ưu chi phí
  • Luôn dưới 10%
  • Tùy ý
Khi gần 100%, mọi spike traffic → queue overflow → latency tăng vọt. 80% là sweet spot: đủ utilization tốt, đủ buffer cho burst. Autoscale theo metric này: scale up khi avg > 70%, scale down khi < 30%.

Hoàn thành Chương 1. Tiếp theo: Chương 2 — Load Balancer & Reverse Proxy →