Skip to content

Scalability — Thiết kế hệ thống chịu tải

Tháng 11/2020, Disney+ ra mắt tại châu Âu và sập ngay ngày đầu tiên — 10 triệu người dùng đổ vào cùng lúc, gấp 5x dự kiến. Không phải vì kỹ sư Disney kém, mà vì họ đánh giá sai capacity cần thiết và scaling strategy không kịp phản ứng.

Scalability không phải là "thêm server". Nó là nghệ thuật dự đoán bottleneck trước khi nó xảy ra, chọn đúng chiến lược mở rộng cho từng layer, và thiết kế hệ thống sao cho việc scale không đòi hỏi viết lại từ đầu. Một hệ thống scalable tốt có thể tăng 10x traffic mà chỉ cần thay đổi configuration, không phải architecture.

Bài này trang bị cho bạn tư duy capacity planning — kỹ năng mà mọi senior engineer đều cần nhưng ít người được dạy bài bản.

Bức tranh tư duy

Hãy tưởng tượng một quán phở Sài Gòn.

Khi quán đông, chủ quán có hai lựa chọn: thuê đầu bếp giỏi hơn (vertical scaling) hoặc mở thêm chi nhánh (horizontal scaling). Thuê đầu bếp giỏi hơn thì đơn giản — không cần thay đổi quy trình. Nhưng đầu bếp giỏi nhất thế giới vẫn chỉ nấu được N tô/giờ. Mở chi nhánh thì phức tạp hơn — cần chuẩn hoá công thức, chia nguyên liệu, đồng bộ menu — nhưng về lý thuyết không có giới hạn.

Nhưng analogy này có giới hạn: trong phần mềm, "mở chi nhánh" phức tạp hơn nhiều so với đời thực — bạn phải xử lý data consistency, network latency, và distributed failures. Đừng để sự đơn giản của analogy che giấu complexity thực sự.

Cốt lõi kỹ thuật

Vertical vs Horizontal Scaling

Tiêu chíVertical (Scale Up)Horizontal (Scale Out)
Cách làmNâng cấu hình máy: CPU, RAM, SSDThêm máy vào cluster
Giới hạnHardware ceiling (~256 cores, ~12TB RAM)Lý thuyết: không giới hạn
Code changesKhông cầnCần thiết kế stateless
ConsistencyĐơn giản (single node)Phức tạp (distributed)
Downtime khi scaleThường cần restartZero-downtime nếu thiết kế đúng
Chi phíTăng theo cấp số nhânTăng tuyến tính
Fault toleranceSPOF (Single Point of Failure)Built-in redundancy

Quy tắc vàng: Scale vertically cho đến khi chi phí không còn hợp lý hoặc chạm giới hạn phần cứng, sau đó mới scale horizontally. Premature horizontal scaling là complexity không cần thiết.

Stateless vs Stateful Services

Horizontal scaling chỉ hoạt động hiệu quả khi service là stateless — bất kỳ instance nào cũng xử lý được bất kỳ request nào.

Stateful (khó scale):
  Client A ──► Server 1 (giữ session của A trong memory)
  Client A ──► Server 2 ❌ Không có session!

Stateless (dễ scale):
  Client A ──► Server 1 (đọc session từ Redis)
  Client A ──► Server 2 (đọc session từ Redis) ✅

Chiến lược chuyển từ stateful sang stateless:

  • Session → External store (Redis, Memcached)
  • File uploads → Object storage (S3, MinIO)
  • In-memory cache → Distributed cache (Redis Cluster)
  • Local cron jobs → Distributed job queue (Celery, Bull)

Database Scaling Strategies

Database thường là bottleneck đầu tiên khi hệ thống scale. Có 3 chiến lược chính:

1. Read Replicas — Tách read khỏi write

Hiệu quả khi hệ thống read-heavy (>80% reads). Replication lag thường 10-100ms — chấp nhận được cho hầu hết use case trừ financial transactions.

2. Sharding — Chia dữ liệu theo chiều ngang

Sharding StrategyCơ chếƯu điểmNhược điểm
Range-baseduser_id 1-1M → Shard AQuery range hiệu quảHotspot nếu phân bố không đều
Hash-basedhash(user_id) % NPhân bố đềuRange query khó
Geo-basedTheo region/countryLatency thấp cho userCross-region queries phức tạp

3. CDN — Đẩy content ra gần user

CDN giảm latency bằng cách cache static content (images, CSS, JS) tại edge nodes gần người dùng. Một CDN tốt giảm 50-80% load cho origin server và cải thiện đáng kể trải nghiệm người dùng ở vùng địa lý xa.

Auto-Scaling

Auto-scaling tự động thêm/bớt instance dựa trên metrics. Hai loại chính:

  • Reactive: Scale khi CPU > 70% hoặc response time > 500ms. Đơn giản nhưng có lag 2-5 phút để instance mới sẵn sàng.
  • Predictive: Dùng historical data để dự đoán traffic. AWS Auto Scaling hỗ trợ predictive scaling dựa trên pattern hàng tuần.

Metrics quan trọng cho auto-scaling:

MetricNgưỡng scale-upGhi chú
CPU utilization> 70%Phổ biến nhất
Memory usage> 80%Quan trọng cho in-memory workloads
Request count> N req/s/instanceChính xác hơn CPU cho web services
Queue depth> thresholdTốt cho async workers
Response time P99> SLA targetUser-facing metric trực tiếp

Thực chiến

Tình huống: Capacity Planning cho e-commerce 10M users

Bối cảnh: Bạn đang thiết kế hệ thống e-commerce. Hiện tại 100K users, mục tiêu 10M users trong 2 năm. Flash sale có thể tạo spike 50x traffic bình thường.

Mục tiêu: Xác định infrastructure cần thiết và scaling strategy.

Bước 1: Ước lượng traffic

Daily Active Users (DAU): 10M × 20% = 2M
Requests/day: 2M × 10 requests/user = 20M
Requests/second (avg): 20M / 86400 ≈ 230 RPS
Peak (3x average): ~700 RPS
Flash sale spike (50x): ~11,500 RPS

Bước 2: Áp dụng quy tắc 10x

Quy tắc 10x nói rằng: thiết kế hệ thống để chịu được 10x traffic hiện tại mà không cần thay đổi kiến trúc. Nếu hiện tại 700 RPS peak, thiết kế cho 7,000 RPS. Flash sale 11,500 RPS nằm ngoài 10x → cần chiến lược riêng (queue, rate limiting, pre-scaling).

Bước 3: Xác định bottleneck theo layer

Phân tích:

  • App layer: 7,000 RPS ÷ 500 RPS/instance = 14 instances (thêm buffer → 20 instances)
  • Database: Bottleneck chính. 7,000 RPS mà cache hit rate 90% → 700 RPS đến DB. PostgreSQL single node xử lý được, nhưng flash sale cần read replicas + queue để throttle writes.
  • Quyết định: Chưa cần shard database ở 10M users. Read replicas + caching layer là đủ.

Tình huống: Xác định scaling bottleneck

Bối cảnh: Response time P99 tăng từ 200ms lên 2s khi traffic tăng 3x.

Quy trình điều tra:

  1. Check metrics theo layer: CDN → LB → App → Cache → DB
  2. Tìm saturation point: Layer nào đạt >80% capacity trước?
  3. Phân tích: Nếu DB CPU 95% nhưng App CPU 30% → scale DB, không phải App

Trade-off đã chấp nhận: Dùng async replication cho read replicas, chấp nhận eventual consistency (lag ~100ms) để đổi lấy read throughput cao hơn.

Sai lầm điển hình

Sai lầm 1: Premature Horizontal Scaling

Vấn đề: Team scale horizontally ngay từ đầu với 100 users.

Tại sao sai: Bạn đang trả giá complexity của distributed system (network latency, data consistency, deployment complexity) cho traffic mà một server trung bình xử lý dư sức. Một PostgreSQL instance trên máy 8-core xử lý được hàng nghìn TPS.

Đúng: Bắt đầu bằng single server mạnh. Monitor metrics. Scale khi có data chứng minh cần thiết, không phải khi "cảm thấy" sẽ cần.

Sai lầm 2: Scale sai layer

Vấn đề: Response time chậm → Team thêm app servers. Nhưng root cause là database.

❌ SAI: Traffic tăng → thêm app server → DB vẫn là bottleneck → không cải thiện gì

✅ ĐÚNG: Traffic tăng → check metrics từng layer → DB CPU 95%
         → thêm read replicas + optimize queries → response time giảm

Tại sao hay mắc: Thêm app server dễ hơn (auto-scaling group), còn database scaling phức tạp hơn nên người ta trì hoãn. Kết quả: chi phí infrastructure tăng mà performance không cải thiện.

Sai lầm 3: Bỏ qua database là bottleneck

Vấn đề: Mọi thứ đều stateless, auto-scale mượt mà — nhưng database vẫn là single instance.

Tại sao sai: Database là thành phần khó scale nhất trong stack. Nếu bạn scale mọi thứ trừ database, bạn chỉ đang chuyển bottleneck về database nhanh hơn. Giống như mở thêm làn đường nhưng tất cả đều đổ vào một trạm thu phí.

Đúng: Lập capacity plan cho database song song với app layer. Sớm đưa caching layer (Redis) vào giữa để giảm load cho DB. Target: >90% cache hit rate cho read-heavy workloads.

Sai lầm 4: Scale không có metrics

Vấn đề: Scale dựa trên "cảm giác" hoặc complaint từ user.

Tại sao sai: Không có metrics nghĩa là không biết scale cái gì, bao nhiêu, và khi nào dừng. Bạn có thể đang scale đúng layer nhưng sai thời điểm, hoặc đúng thời điểm nhưng sai layer.

Đúng: Thiết lập monitoring stack (Prometheus + Grafana) ngay từ ngày đầu. Dashboard tối thiểu: CPU, Memory, Disk I/O, Network, Request Rate, Error Rate, Response Time (P50, P95, P99).

Under the Hood

Amdahl's Law — Giới hạn của Scaling

Amdahl's Law cho biết: nếu một phần P của chương trình có thể chạy song song (parallelized), speedup tối đa khi dùng N processors là:

Speedup(N) = 1 / ((1 - P) + P/N)

Ý nghĩa thực tế:

Phần parallelizable (P)2 cores8 cores64 cores∞ cores
50%1.33x1.78x1.97x2x
90%1.82x4.71x8.77x10x
95%1.90x5.93x14.28x20x
99%1.98x7.48x39.26x100x

Dù thêm bao nhiêu server, phần serial (không parallelizable) luôn là ceiling. Nếu 10% code là serial → speedup tối đa chỉ 10x, dù bạn có 1000 servers.

Universal Scalability Law (USL)

USL mở rộng Amdahl's Law bằng cách thêm yếu tố coherency penalty (κ) — chi phí đồng bộ dữ liệu giữa các node:

C(N) = N / (1 + σ(N-1) + κN(N-1))

σ = contention (serial fraction)
κ = coherency (coordination cost giữa các node)

Khi κ > 0: Throughput thực sự giảm sau một điểm nhất định. Thêm server lúc này làm hệ thống chậm hơn — vì coordination cost vượt quá benefit.

Đây là lý do tại sao distributed lock, distributed transaction, và shared state là những thứ đắt đỏ nhất trong hệ thống phân tán. Mỗi node thêm vào không chỉ cần communicate với tất cả node khác, mà còn phải chờ đồng bộ — tạo ra chi phí O(N²).

CAP Theorem Preview

Trong hệ thống phân tán, bạn chỉ có thể đảm bảo 2 trong 3 thuộc tính:

  • Consistency — Mọi read đều nhận được write gần nhất
  • Availability — Mọi request đều nhận được response (không lỗi)
  • Partition Tolerance — Hệ thống vẫn hoạt động khi network bị phân mảnh

Trong thực tế, network partition luôn xảy ra, nên lựa chọn thực sự là CP vs AP:

Chiến lượcHy sinhUse case
CPAvailability khi partition xảy raBanking, inventory, booking
APConsistency khi partition xảy raSocial feed, analytics, DNS

Trade-offs tổng hợp

Quyết địnhKhi NÊNKhi KHÔNG NÊN
Vertical scaling< 10K RPS, team nhỏ, cần đơn giảnChi phí hardware vượt budget
Horizontal scaling> 10K RPS, cần fault tolerance< 1K RPS, tăng complexity không đáng
Sharding> 1TB data hoặc write-heavyRead-heavy system có thể dùng replicas
CDNStatic content, global usersDynamic content, single region
Auto-scalingTraffic biến động lớnTraffic ổn định, có thể provision cố định

Checklist ghi nhớ

✅ Checklist triển khai

Scaling Strategy

  • [ ] Xác định bottleneck bằng metrics trước khi scale
  • [ ] Bắt đầu vertical, chuyển horizontal khi có data chứng minh
  • [ ] Thiết kế stateless services từ đầu
  • [ ] Externalize tất cả state (session, cache, files) ra external store

Capacity Planning

  • [ ] Ước lượng traffic: DAU → RPS → Peak → Spike
  • [ ] Áp dụng quy tắc 10x cho current peak
  • [ ] Lập capacity plan cho từng layer: CDN → LB → App → Cache → DB
  • [ ] Database capacity plan riêng (thường là bottleneck đầu tiên)

Monitoring

  • [ ] Dashboard: CPU, Memory, Disk I/O, Network, Request Rate, Error Rate
  • [ ] Response time: P50, P95, P99
  • [ ] Alert khi bất kỳ layer nào > 70% capacity
  • [ ] Load test định kỳ để validate capacity plan

Bài tập luyện tập

Bài 1: Capacity Planning — Foundation

Đề bài: Một ứng dụng chat có 5M registered users, 500K DAU, mỗi user gửi trung bình 20 messages/ngày. Peak traffic gấp 5x average. Tính RPS cho message service.

🧠 Quiz

Câu hỏi: Với thông số trên, average RPS cho message service xấp xỉ bao nhiêu?

  • [ ] A. 58 RPS
  • [x] B. 116 RPS
  • [ ] C. 580 RPS
  • [ ] D. 1,160 RPS Giải thích: 500K × 20 = 10M messages/day. 10M / 86,400 ≈ 116 RPS average. Peak = 116 × 5 = 580 RPS. Option C là peak, không phải average.
💡 Gợi ý
  • Tính tổng messages/day trước
  • 1 ngày = 86,400 giây
  • Phân biệt rõ average vs peak

Bài 2: Bottleneck Identification — Intermediate

Đề bài: Hệ thống e-commerce có metrics sau khi traffic tăng 2x:

  • App server CPU: 45%
  • Redis hit rate: 95%
  • PostgreSQL CPU: 92%, connection pool: 98% utilized
  • Response time P99: 3.2s (SLA: 500ms)

Xác định bottleneck và đề xuất 3 giải pháp theo thứ tự ưu tiên.

💡 Gợi ý
  • Layer nào đang ở mức saturation (>80%)?
  • Connection pool exhaustion gây gì?
  • Short-term fix vs long-term fix
✅ Lời giải

Bottleneck: PostgreSQL — CPU 92% và connection pool 98% saturated.

Giải pháp theo ưu tiên:

  1. Ngay lập tức: Optimize slow queries (EXPLAIN ANALYZE), thêm missing indexes
  2. Ngắn hạn: Thêm read replicas, route SELECT queries sang replicas
  3. Trung hạn: Deploy connection pooler (PgBouncer), tăng pool capacity

Phân tích: App server chỉ 45% → không cần scale. Redis hit rate 95% → đang hoạt động hiệu quả. Database là single point of saturation.

Bài 3: Scaling Strategy Design — Advanced

Đề bài: Thiết kế scaling strategy cho video streaming platform với 50M users, 2M concurrent viewers peak, mỗi viewer consume trung bình 5 Mbps. Hệ thống hiện tại là monolith trên 3 servers.

Viết capacity plan bao gồm: bandwidth requirement, số lượng origin servers, CDN strategy, và database scaling approach.

💡 Gợi ý
  • Bandwidth = concurrent viewers × bitrate
  • CDN nên serve >95% video traffic
  • Origin chỉ handle cache miss + upload
  • Tách metadata DB ra khỏi video storage
✅ Lời giải

Bandwidth: 2M × 5 Mbps = 10 Tbps peak — không thể tự host, bắt buộc dùng CDN.

CDN Strategy: Target 98% cache hit → Origin chỉ handle 2% = 200 Gbps → khoảng 50 origin servers (4 Gbps each).

Database: Metadata (PostgreSQL) + Object Storage (S3) cho video files. Metadata DB: 50M users × 1KB = 50GB — single primary + read replicas đủ, sharding chưa cần thiết.

Auto-scaling: Transcoding workers scale theo upload queue depth, không theo CPU.

Liên kết học tiếp

Từ khóa glossary: Scalability, Horizontal Scaling, Vertical Scaling, Sharding, Read Replica, CDN, Auto-Scaling, Capacity Planning, Amdahl's Law, Universal Scalability Law, CAP Theorem

Tìm kiếm liên quan: thiết kế hệ thống chịu tải, mở rộng quy mô, database sharding, capacity estimation, quy tắc 10x