Thứ Sáu cuối tháng 11, đúng 0 giờ Black Friday, traffic e-commerce tăng gấp 3 lần so với bình thường. Database connection pool cạn trong 4 phút đầu tiên. Redis cluster OOM sau 7 phút vì cache invalidation storm. App server CPU đạt 100 %, auto-scaler kick in nhưng instance mới cần 3 phút để pull image, warm cache, pass health check. Trong 3 phút đó, hàng nghìn request timeout, giỏ hàng mất session, khách hàng bỏ đi. Revenue mất ước tính 2 tỷ đồng. Post-mortem kết luận một dòng đau đớn: “Không ai ước lượng capacity trước sự kiện.”

Capacity planning không phải kỹ năng xa xỉ của SRE hay infrastructure team. Đó là kỹ năng mà mọi senior engineer cần, vì người viết code là người hiểu rõ nhất mỗi request tốn bao nhiêu resource, mỗi feature mới ảnh hưởng thế nào đến storage và bandwidth. Bài này đi từ back-of-envelope estimation đến load testing, từ headroom buffer đến cost model, tất cả những gì cần để trả lời câu hỏi “hệ thống chịu được bao nhiêu trước khi sập?”


Những con số mà engineer nên nhớ

Trước khi ước lượng capacity cho bất kỳ hệ thống nào, cần có bộ số tham chiếu trong đầu. Không cần chính xác tuyệt đối, chỉ cần đúng bậc độ lớn (order of magnitude) để phán đoán nhanh “con số này hợp lý hay vô lý”.

Hiểu nôm na thì bộ số tham chiếu này giống như bảng cửu chương của engineer, không cần nhớ chính xác từng chữ số, nhưng phải đủ để nhẩm ngay “con số này nghe vô lý” khi nghe ai đó pitch một kiến trúc trong meeting.

Về latency: L1 cache reference khoảng 1 ns, L2 cache khoảng 4 ns, main memory reference khoảng 100 ns, SSD random read khoảng 16 µs (16,000 ns), HDD random seek khoảng 4 ms (4,000,000 ns). Gửi packet từ Hà Nội đến Singapore khoảng 30 ms round trip, đến US West Coast khoảng 150-200 ms. Một database query đơn giản (index lookup, trả vài row) mất 1-5 ms trên cùng datacenter. Một HTTP request giữa hai service trong cùng cluster mất 1-10 ms tuỳ payload.

Về throughput: một server hiện đại có thể xử lý 10,000-50,000 HTTP request/giây cho endpoint đơn giản (đọc cache, trả JSON nhỏ). PostgreSQL single node xử lý 5,000-20,000 simple query/giây tuỳ schema và hardware. Redis single thread đạt 100,000+ GET/SET mỗi giây. Bandwidth 1 Gbps nghĩa là tối đa khoảng 125 MB/giây, một response 10 KB ở 10,000 QPS đã dùng 100 MB/giây, gần cạn 1 Gbps.

Về storage: 1 triệu row với mỗi row 1 KB chiếm khoảng 1 GB. Giữ log 100 GB/ngày trong 30 ngày cần 3 TB. Một ảnh JPEG trung bình 200-500 KB, một triệu ảnh cần 200-500 GB.

Bộ số này không phải để “thuộc lòng rồi trả bài”. Giá trị của nó nằm ở khả năng sanity check: khi ai đó nói “service cần xử lý 1 triệu QPS trên single node”, bạn biết ngay con số đó vô lý, vì ngay cả endpoint đơn giản nhất cũng khó vượt 50,000 QPS trên một máy. Khi ai đó ước lượng “database 100 GB cho 10 tỷ row × 1 KB/row”, bạn nhẩm ngay 10 tỷ × 1 KB = 10 TB, lệch 100 lần, ước lượng sai.


Từ business metrics đến technical metrics

Capacity planning bắt đầu từ con số business, không phải con số kỹ thuật. Lý do đơn giản: kỹ thuật phục vụ business, và người ra quyết định (PM, leadership) nói ngôn ngữ business, “1 triệu user mỗi ngày”, “10,000 đơn hàng/giờ”, “chiến dịch marketing sẽ tăng traffic gấp 5”. Công việc của engineer là dịch những con số đó thành QPS, storage, memory, bandwidth.

Chuỗi dịch trông thế này:

flowchart LR A[DAU / MAU] --> B[Requests per user per day] B --> C[Total requests / day] C --> D[Average QPS] D --> E[Peak QPS = Avg × peak factor] E --> F[QPS per service / endpoint] F --> G[Resource per request: CPU, memory, DB queries, bandwidth] G --> H[Total resource needed]

Mỗi bước có hệ số nhân (multiplier) cần ước lượng. DAU × requests/user = total requests. Total requests / 86,400 giây = average QPS. Average QPS × peak factor = peak QPS. Peak QPS × resource/request = total resource. Sai ở bước đầu thì mọi bước sau đều sai, nên luôn sanity check từng bước thay vì chỉ nhìn con số cuối.

Peak factor là hệ số quan trọng nhất và cũng khó ước lượng nhất. Với hầu hết ứng dụng B2C, traffic tập trung vào buổi tối (19h-22h), peak thường gấp 2-3 lần average. Với e-commerce trong ngày sale, peak có thể gấp 5-10 lần average. Với ứng dụng B2B, traffic tập trung giờ làm việc, peak chỉ gấp 1.5-2 lần average. Nếu không có data lịch sử, dùng peak factor 3 là heuristic an toàn cho ứng dụng B2C thông thường (con số 3 này được truyền từ senior sang junior qua nhiều năm, không ai biết chắc xuất phát từ đâu nhưng nó “thường đủ” nên mọi người vẫn dùng). Nếu có data từ analytics (Google Analytics, Mixpanel), lấy tỷ lệ peak hour / average hour trong 90 ngày gần nhất, con số thực tế luôn chính xác hơn heuristic.


Sizing cho e-commerce 1 triệu DAU

Để minh hoạ, đi qua một bài toán sizing cụ thể từ đầu đến cuối. Giả sử: sàn e-commerce có 1 triệu DAU, bán hàng tiêu dùng, có flash sale định kỳ.

QPS estimation

Hành vi trung bình mỗi user mỗi ngày: mở app 3 lần, mỗi lần duyệt 5 trang sản phẩm (mỗi trang 1 API call product list + 1 API call recommendation), xem chi tiết 2 sản phẩm (2 API call), thêm giỏ hàng 0.5 lần (0.5 API call), thanh toán 0.1 lần (3 API call cho flow checkout). Tổng cộng ước tính khoảng 40 API request mỗi user mỗi ngày. Con số này nghe hợp lý cho ứng dụng mobile e-commerce, có thể thêm API call cho tracking, notification, nhưng giữ 40 làm baseline.

Tổng request mỗi ngày: 1,000,000 × 40 = 40,000,000 request/ngày. Average QPS: 40,000,000 / 86,400 ≈ 463 QPS. Peak QPS với peak factor 3: 463 × 3 ≈ 1,400 QPS vào giờ cao điểm buổi tối. Flash sale peak factor 8: 463 × 8 ≈ 3,700 QPS.

Từ 3,700 QPS tổng, phân bổ theo endpoint: product listing chiếm khoảng 40 % (1,480 QPS), product detail 20 % (740 QPS), search 15 % (555 QPS), cart operations 10 % (370 QPS), checkout 5 % (185 QPS), các endpoint khác 10 % (370 QPS). Phân bổ này giúp biết service nào cần scale nhiều nhất, product listing và search chịu tải nặng nhất.

Mỗi product listing request cần: 1 query database (hoặc cache hit), serialize JSON khoảng 5 KB response. Nếu cache hit rate 90 %, thì 10 % request đến database: 1,480 × 0.1 = 148 QPS đến DB cho listing. Tương tự cho các endpoint khác. Tổng DB QPS ước tính khoảng 500-800 QPS trong flash sale, con số mà PostgreSQL single node xử lý thoải mái nếu query đã optimize.

Storage estimation

Bảng products: 500,000 SKU × 2 KB/row (tên, mô tả ngắn, giá, metadata) = 1 GB. Bảng orders: 10,000 đơn/ngày × 1 KB/row × 365 ngày retention = 3.65 GB/năm. Bảng order_items: 10,000 đơn × 3 item/đơn × 200 bytes/row × 365 = 2.2 GB/năm. Bảng users: 5,000,000 registered users × 500 bytes/row = 2.5 GB. Bảng user_events (tracking): 1,000,000 DAU × 40 events/ngày × 200 bytes/event = 8 GB/ngày. Giữ 90 ngày = 720 GB, đây thường là bảng lớn nhất.

Index overhead thường thêm 20-50 % so với data size. WAL/binlog thêm nữa. Ước tính tổng database storage sau 1 năm: khoảng 1-1.5 TB kể cả index và overhead. Con số này ảnh hưởng trực tiếp đến việc chọn instance type (cần SSD đủ lớn), backup strategy (backup 1 TB mất bao lâu), và migration plan (ALTER TABLE trên bảng 720 GB mất hàng giờ).

Ảnh sản phẩm: 500,000 SKU × 5 ảnh/SKU × 3 size (thumbnail 20 KB, medium 100 KB, large 300 KB) = 500,000 × 5 × 420 KB ≈ 1 TB. Serve từ CDN, không từ app server, nhưng CDN origin storage vẫn cần 1 TB.

Memory estimation

App server memory: mỗi request đang xử lý chiếm khoảng 1-5 MB memory (goroutine/thread stack, request context, response buffer). Ở 3,700 QPS peak, nếu latency trung bình 50 ms, số request đồng thời (concurrent) = 3,700 × 0.05 = 185. Memory cho request processing: 185 × 5 MB ≈ 925 MB. Thêm base memory cho runtime (JVM heap, Go runtime, Node.js): 500 MB - 2 GB tuỳ ngôn ngữ. Tổng mỗi app server cần khoảng 2-4 GB memory.

Cache memory: product catalog thường cache toàn bộ hot items. 100,000 hot products × 2 KB = 200 MB. Session cache: 100,000 concurrent session × 1 KB = 100 MB. Recommendation cache: tuỳ model, ước tính 500 MB. Redis cluster cần khoảng 2-4 GB memory cho use case này.

Database memory: PostgreSQL shared_buffers thường set 25 % RAM. Working set (phần data thường xuyên truy cập) nên fit trong memory, nếu working set là product catalog + recent orders + active users ≈ 10 GB, thì database server cần ít nhất 40 GB RAM (10 GB shared_buffers + OS page cache). Thực tế nên dùng server 64-128 GB RAM để có headroom.

Connection pool: mỗi connection PostgreSQL tiêu tốn khoảng 5-10 MB memory. 200 connections = 1-2 GB. Đây là lý do cần connection pooler (PgBouncer), giới hạn actual connections xuống 50-100 trong khi app server có thể mở hàng trăm pooled connections.

Bandwidth estimation

Response trung bình 5 KB × 3,700 QPS peak = 18.5 MB/giây ≈ 148 Mbps outbound. Ảnh serve từ CDN nên không tính vào app server bandwidth. Nếu có API response lớn hơn (search trả 20 KB, product detail 10 KB), peak bandwidth có thể đạt 200-300 Mbps. Với 1 Gbps NIC thì vẫn thoải mái, nhưng cần tính cả internal traffic (app → DB, app → Redis, app → app), tổng có thể gấp 2-3 lần external bandwidth.

Infrastructure cần thiết cho 1 triệu DAU e-commerce

Từ các con số trên, ước lượng infrastructure cần thiết cho 1 triệu DAU e-commerce với flash sale:

App server: 4-6 instance (4 vCPU, 8 GB RAM mỗi cái), đặt sau load balancer. Mỗi instance xử lý khoảng 600-900 QPS, ở peak 3,700 QPS cần ít nhất 5 instance, giữ 6 để có headroom. Database: 1 primary (16 vCPU, 128 GB RAM, 2 TB SSD) + 1-2 read replica cho query nặng. Redis cluster: 3 node (8 GB RAM mỗi node), replication cho HA. CDN: cho static assets và ảnh sản phẩm. Object storage (S3): 1-2 TB cho ảnh gốc và processed.

Đây là ước lượng ban đầu, load test sẽ validate hoặc bác bỏ từng con số.


Load testing, validate ước lượng bằng thực nghiệm

Back-of-envelope estimation cho bạn điểm khởi đầu, nhưng nó dựa trên giả định, giả định về cache hit rate, giả định về query latency, giả định về memory per request. Load testing là cách duy nhất để biết giả định đúng hay sai trước khi production chịu hậu quả.

Mục đích của load test

Load test trả lời ba câu hỏi cốt lõi. Thứ nhất: hệ thống hiện tại xử lý được bao nhiêu QPS trước khi latency vượt ngưỡng chấp nhận được? Đây là capacity ceiling hiện tại. Thứ hai: bottleneck nằm ở đâu, CPU, memory, database connections, network, hay application logic? Biết bottleneck thì biết scale cái gì. Thứ ba: hệ thống degrade gracefully hay collapse đột ngột khi vượt capacity? Degrade gracefully (latency tăng dần, một số request timeout nhưng hệ thống vẫn phục vụ được) tốt hơn nhiều so với collapse (OOM kill, connection pool cạn, cascading failure khiến toàn bộ service chết).

Phương pháp

Ba loại load test phổ biến, mỗi loại trả lời câu hỏi khác nhau.

Ramp-up test tăng dần load từ 0 đến target QPS trong khoảng thời gian (ví dụ 10 phút). Mục đích là tìm inflection point, QPS mà tại đó latency bắt đầu tăng đáng kể hoặc error rate xuất hiện. Inflection point chính là capacity ceiling thực tế. Nếu ước lượng back-of-envelope nói ceiling là 3,700 QPS nhưng ramp-up test thấy latency tăng vọt ở 2,500 QPS, thì ước lượng sai, có thể cache hit rate thực tế thấp hơn giả định, hoặc query nặng hơn dự kiến.

Sustained load test (soak test) giữ load ở mức target (ví dụ 70 % capacity ceiling) trong thời gian dài, 2-4 giờ, hoặc thậm chí 24 giờ. Mục đích là phát hiện memory leak, connection leak, log rotation issue, hoặc bất kỳ vấn đề nào chỉ lộ sau thời gian dài chạy. Memory tăng dần 10 MB/giờ nghe nhỏ, nhưng sau 24 giờ là 240 MB, với container 512 MB memory limit thì OOM kill sau nửa ngày.

Spike test tăng load đột ngột, từ baseline lên 5-10 lần trong vài giây. Mô phỏng flash sale, viral event, hoặc retry storm. Mục đích là kiểm tra auto-scaler phản ứng kịp không, circuit breaker hoạt động đúng không, và hệ thống recover sau spike hay tiếp tục degrade.

Công cụ

k6 (Grafana Labs) viết script bằng JavaScript, nhẹ, CLI-based, tích hợp tốt với Grafana dashboard. Phù hợp cho team đã dùng Grafana stack. Locust viết bằng Python, distributed load generation dễ setup, UI web đẹp hiển thị real-time stats. Phù hợp khi team quen Python và cần distributed test từ nhiều machine. Gatling viết bằng Scala, report HTML chi tiết, phù hợp cho tổ chức lớn cần report formal.

Cá nhân thì k6 là lựa chọn mặc định vì script đơn giản, chạy nhanh, và output dạng metric dễ đẩy vào Prometheus/Grafana để so sánh với metric production.

Anti-patterns trong load testing

Test từ cùng region với server. Nếu server ở Singapore mà load generator cũng ở Singapore, latency mạng gần bằng 0, không phản ánh trải nghiệm user ở Việt Nam (30 ms RTT) hay Nhật Bản (50 ms RTT). Chạy load generator từ region gần user thực tế, hoặc ít nhất thêm artificial latency để mô phỏng.

Dùng data không thực tế. Test với 100 sản phẩm trong khi production có 500,000 sản phẩm, database query plan hoàn toàn khác vì data volume ảnh hưởng index scan, page cache, join cost. Load test phải dùng dataset có volume tương đương production, hoặc ít nhất cùng order of magnitude.

Không test write path. Nhiều team chỉ test đọc (product listing, search) mà quên test ghi (đặt hàng, thanh toán). Write path thường là bottleneck thực sự vì database lock, transaction isolation, và write amplification. Flash sale traffic không chỉ đọc, hàng nghìn người đặt hàng cùng lúc, ghi đồng thời vào bảng orders và inventory.

Bỏ qua warm-up. Server mới start có cold cache, JIT compiler chưa optimize, page cache trống, connection pool chưa đầy. Nếu load test bắn ngay full traffic vào server cold, kết quả sẽ tệ hơn thực tế (production server đã warm). Ngược lại, nếu load test chỉ chạy sau khi server đã warm hoàn toàn, bạn không biết cold start behavior, quan trọng khi auto-scaler thêm instance mới trong lúc spike.

Không monitor đầy đủ trong khi test. Load test mà chỉ nhìn QPS và latency tổng thì bỏ lỡ bottleneck thật. Cần monitor đồng thời: CPU/memory/disk IO từng server, database connections active/idle, cache hit rate, error rate theo endpoint, GC pause (nếu JVM/Go), network bandwidth. Bottleneck có thể không phải ở nơi bạn nghĩ, CPU app server thoải mái nhưng database connection pool cạn, hoặc bandwidth NIC saturated.


Headroom, đừng bao giờ chạy sát trần

Một sai lầm phổ biến trong capacity planning là size vừa khít cho peak QPS ước tính. “Peak 3,700 QPS, mỗi server chịu 1,000 QPS, vậy 4 server là đủ.” Đây là sai lầm nguy hiểm, không có chỗ cho bất kỳ điều gì ngoài kịch bản lý tưởng.

Headroom buffer tồn tại vì ba lý do. Thứ nhất, ước lượng peak không hoàn hảo, flash sale có thể viral hơn dự kiến, marketing campaign hit đúng thời điểm, hoặc đơn giản là user behavior thay đổi. Peak thực tế có thể vượt ước lượng 20-50 %. Thứ hai, khi server chạy gần 100 % capacity, latency tăng phi tuyến, không phải tuyến tính. Ở 70 % CPU, latency có thể vẫn ổn. Ở 90 % CPU, context switch tăng, queue dài ra, latency P99 tăng gấp 3-5 lần. Ở 95 %, hệ thống bắt đầu thrash. Thứ ba, khi một node trong cluster fail (hardware issue, deployment gone wrong), load re-distribute lên các node còn lại. Nếu 4 node đang chạy 90 % mỗi cái, mất 1 node nghĩa là 3 node còn lại phải gánh 120 %, quá tải và cascade failure.

Target utilization hợp lý: 60-70 % cho stateless service (app server, API gateway), 50-60 % cho stateful service (database, cache) vì stateful service khó scale nhanh và failure impact lớn hơn. Nghĩa là nếu peak QPS là 3,700 và mỗi server chịu 1,000 QPS, cần 3,700 / (1,000 × 0.65) ≈ 6 server, không phải 4.

Với database, headroom còn quan trọng hơn. PostgreSQL chạy ổn ở 60 % CPU, nhưng ở 85 % CPU thì vacuum autovacuum cạnh tranh resource với query, lock contention tăng, replication lag tăng. Giữ database ở mức 40-50 % CPU peak là best practice, nghe “lãng phí” nhưng chi phí overprovisioning database thấp hơn nhiều so với chi phí downtime.


Auto-scaling không thay thế capacity planning

Auto-scaling là công cụ tuyệt vời, nhưng nó không phải phép thuật giải quyết mọi vấn đề capacity. Hiểu giới hạn của auto-scaling giúp tránh cảm giác an toàn giả tạo.

Scale-up latency. Từ lúc auto-scaler detect load tăng đến lúc instance mới sẵn sàng nhận traffic, có khoảng trễ đáng kể. Với container trên Kubernetes: detect (15-30 giây metric scrape interval) + HPA decision (15-30 giây) + pod scheduling (vài giây) + image pull (30 giây - 3 phút tuỳ image size và registry) + app startup + health check (10-60 giây) = tổng 1-5 phút. Với VM (AWS EC2): thêm thời gian provision machine, tổng 3-10 phút. Trong 1-5 phút đó, traffic spike đã có thể làm sập hệ thống. Spike test trong load testing phải kiểm tra khoảng trễ này.

Cold start. Instance mới có cache trống, JIT compiler chưa optimize, connection pool chưa warm. Request đầu tiên vào instance mới chậm hơn đáng kể so với instance đã chạy lâu. Nếu auto-scaler thêm 5 instance cùng lúc và load balancer route traffic đều, 5 instance cold có thể trả latency cao, ảnh hưởng trải nghiệm user. Giải pháp: warm-up period trong health check (instance chỉ nhận traffic sau khi đã xử lý vài hundred request warm-up), hoặc gradual traffic shift.

Stateful service không auto-scale được dễ dàng. Database, Redis cluster, Elasticsearch, không thể “thêm node trong 2 phút” như stateless app. Thêm read replica PostgreSQL cần thời gian sync data (tỷ lệ thuận với database size). Thêm node Redis cluster cần resharding. Đây là lý do database phải được size đúng từ đầu với headroom đủ lớn, auto-scaling cho database layer gần như không khả thi trong kịch bản spike đột ngột.

Cost surprise. Auto-scaling mà không có giới hạn trên (max instances) là recipe cho bill shock. Retry storm hoặc DDoS trigger auto-scaler thêm hàng chục instance, mỗi instance là tiền. Luôn set max instances hợp lý và alert khi đạt 80 % max, nếu auto-scaler chạm max, bạn cần intervention thủ công chứ không phải tăng max một cách mù quáng.

Auto-scaling là lớp bảo vệ bổ sung, không phải thay thế cho capacity planning. Plan cho peak ước tính, auto-scale cho phần vượt ngoài dự kiến, đây là mental model đúng.


Cost model, dịch capacity thành tiền

Capacity planning mà không tính cost thì chưa hoàn chỉnh. Leadership không hỏi “cần bao nhiêu QPS”, họ hỏi “tốn bao nhiêu tiền mỗi tháng và ROI thế nào”. Engineer cần biết dịch technical sizing thành cloud cost.

Với ví dụ e-commerce 1 triệu DAU ở trên, ước lượng chi phí hàng tháng trên AWS (giá tham khảo, thay đổi theo region và commitment):

App server: 6 × c6g.xlarge (4 vCPU, 8 GB RAM) ≈ 6 × $120/tháng = $720/tháng (on-demand). Reserved Instance 1 năm giảm khoảng 30-40 %: ≈ $450/tháng. Database: 1 × r6g.4xlarge (16 vCPU, 128 GB RAM) primary ≈ $1,400/tháng on-demand + 2 TB gp3 storage ≈ $160/tháng. 1 read replica tương tự: $1,560/tháng. Redis: 3 × cache.r6g.large (13 GB) ≈ 3 × $250/tháng = $750/tháng. Load balancer (ALB): $20/tháng base + LCU usage ≈ $50-100/tháng. CDN (CloudFront): tuỳ traffic, ước tính $200-500/tháng cho 10 TB transfer. S3: 2 TB ≈ $46/tháng. Monitoring (CloudWatch, hoặc self-host Grafana stack): $100-300/tháng.

Tổng ước tính: $4,500-5,500/tháng on-demand, khoảng $3,000-4,000/tháng với reserved instances. Đây là chi phí cho 1 triệu DAU, tức khoảng $0.004/user/tháng hay 100 đồng/user/tháng. Con số này giúp business đánh giá: nếu mỗi user tạo ra 500 đồng revenue/tháng, infrastructure cost chiếm 20 %, hợp lý hay cần tối ưu?

Quan trọng: cost model phải review khi architecture thay đổi. Thêm Elasticsearch cho full-text search? Cộng thêm $500-1,500/tháng. Thêm Kafka cho event streaming? $300-800/tháng. Mỗi component mới trong architecture là dòng mới trong cost model.

Một tip thực dụng: dùng AWS Pricing Calculator hoặc GCP Pricing Calculator để estimate trước khi provision. Và luôn set billing alert, $5,000/tháng ước tính mà tháng đầu tiên bill $8,000 thì cần tìm hiểu ngay, đừng chờ đến cuối quý.


Growth projection, nhìn xa 6-12 tháng

Capacity planning không phải làm một lần rồi quên. Business grow, traffic tăng, data tích luỹ, hệ thống cần grow theo. Nếu chỉ size cho hiện tại mà không tính growth, 6 tháng sau lại lặp lại vòng xoáy “sập rồi mới scale”.

Growth projection bắt đầu từ historical data. Nếu DAU tăng 10 % mỗi tháng trong 6 tháng qua, dự kiến 6 tháng tới DAU tăng khoảng 1.1^6 ≈ 1.77 lần. Từ 1 triệu DAU lên 1.77 triệu DAU, QPS, storage, memory đều tăng tỷ lệ. Capacity plan phải cover mức 1.77 triệu DAU, không chỉ 1 triệu hiện tại.

Nhưng growth không phải lúc nào cũng linear. Feature mới có thể thay đổi usage pattern, thêm chat feature thì connection count tăng vọt (WebSocket connections persistent, khác hẳn HTTP request-response). Thêm video upload thì storage và bandwidth tăng đột biến. Capacity plan phải tính cả feature roadmap, không chỉ user growth.

Quy trình review mà nhiều team áp dụng hiệu quả: mỗi quý (3 tháng) review capacity dashboard, so sánh actual usage với forecast, điều chỉnh projection. Nếu actual growth nhanh hơn forecast, tăng capacity sớm. Nếu chậm hơn, có thể downsize để tiết kiệm cost. Review quarterly đủ thường xuyên để không bị bất ngờ, đủ thưa để không tốn quá nhiều effort.

Một sai lầm phổ biến trong growth projection: chỉ project user growth mà quên data growth. DAU tăng 2 lần nhưng data có thể tăng nhanh hơn vì data tích luỹ, user mới tạo data mới, user cũ vẫn giữ data cũ. Database 500 GB hôm nay có thể là 2 TB sau 12 tháng nếu không có retention policy. Và 2 TB database ảnh hưởng đến backup time, migration time, query performance, không chỉ đơn thuần là thêm disk.


Monitoring capacity, dashboard cho tương lai

Hầu hết dashboard monitoring hiển thị trạng thái hiện tại, CPU đang bao nhiêu phần trăm, QPS đang bao nhiêu, disk đang dùng bao nhiêu. Capacity dashboard khác: nó hiển thị hiện tại so với trần, để biết còn bao nhiêu headroom trước khi gặp vấn đề.

Metrics cần có trên capacity dashboard: CPU utilization hiện tại vs capacity ceiling (ví dụ 45 % / 70 % target → còn 25 % headroom). Memory usage vs limit. Database connections active vs max_connections. Disk usage vs total disk (và tốc độ tăng, bao nhiêu ngày nữa thì đầy). QPS hiện tại vs tested capacity ceiling. Cache hit rate, giảm hit rate nghĩa là cần tăng cache size hoặc review eviction policy.

Quan trọng nhất là projection line: nếu disk tăng 2 GB/ngày và còn 200 GB trống, dashboard nên show “100 ngày nữa đầy disk”, cho bạn thời gian plan trước thay vì phát hiện khi đã đầy. Prometheus có predict_linear() function cho việc này, rất hữu ích cho alert kiểu “disk sẽ đầy trong 7 ngày” thay vì “disk đã đầy 95 %”.

Alert cho capacity nên fire sớm, không phải khi đã vượt ngưỡng mà khi đang tiến đến ngưỡng. “Database connections đạt 80 % max” là lúc nên investigate và plan tăng, không phải khi 100 % connections used và request bắt đầu fail.


Anti-patterns trong capacity planning

Không load test trước launch

“Chạy lên production xem thế nào, có vấn đề thì scale.” Đây là chiến lược phổ biến nhất ở startup giai đoạn đầu, và nó hoạt động khi traffic thấp. Nhưng khi sản phẩm có user thật, mỗi lần “có vấn đề thì scale” là downtime thật, mất user thật, mất revenue thật. Load test trước launch không tốn nhiều effort, một buổi chiều setup k6 script, chạy ramp-up test, tìm capacity ceiling. Giá trị mang lại: biết giới hạn hệ thống trước khi user khám phá giùm bạn.

Scale app tier mà bỏ qua database

Thêm 10 app server khi traffic tăng, QPS capacity tăng 10 lần. Nhưng mỗi app server mở connection pool 20 connections tới database. 10 server × 20 = 200 connections. PostgreSQL mặc định max_connections = 100. Connection cạn, mọi request queue, latency tăng vọt, rồi timeout. Scale app tier mà không scale database tier (hoặc ít nhất thêm connection pooler) là anti-pattern cổ điển.

Tệ hơn: thêm app server tăng read QPS đến database. Nếu database CPU đã 70 %, thêm app server có thể đẩy database CPU lên 100 %, app server thì idle chờ database response. Bottleneck chuyển từ app sang database, nhưng triệu chứng nhìn từ ngoài vẫn là “hệ thống chậm”. Scale phải scale đồng bộ mọi tier trong request path, không chỉ tier dễ scale nhất.

Bỏ qua network capacity

Ít ai nghĩ tới bandwidth cho đến khi NIC saturated. 1 Gbps NIC trên database server chỉ chịu được 125 MB/giây. Nếu query trả result set lớn (report, batch export, full-text search kết quả nhiều), bandwidth có thể là bottleneck trước CPU hay memory. Monitoring network throughput per interface là metric hay bị bỏ qua trong capacity dashboard.

Không tính chi phí observability

Log 100 GB/ngày × 30 ngày retention = 3 TB log storage. Metric với 100,000 active series trên Prometheus cần 10-20 GB RAM. Trace ở 1 % sampling rate với 3,700 QPS = 37 trace/giây, mỗi trace 10 span × 500 bytes = 5 KB, tổng 185 KB/giây ≈ 16 GB/ngày. Observability infrastructure có thể tốn 20-30 % tổng chi phí hệ thống, nhưng ít khi được đưa vào capacity plan ban đầu.


War room readiness, checklist trước sự kiện lớn

Trước Black Friday, product launch lớn, hoặc marketing campaign dự kiến tăng traffic đáng kể, cần một capacity review có cấu trúc. Không phải “mọi người chắc OK chứ?” trong standup, mà là checklist cụ thể.

Bước đầu tiên: ước lượng traffic dự kiến. Marketing campaign này target bao nhiêu impression? Conversion rate từ impression sang visit dự kiến bao nhiêu? Từ visit sang transaction? Dịch thành QPS peak. So sánh với capacity ceiling hiện tại, nếu peak dự kiến vượt 70 % ceiling, cần scale trước sự kiện.

Bước hai: load test ở mức traffic dự kiến. Không phải load test generic, mà test đúng endpoint sẽ chịu tải (landing page của campaign, product page đang promotion, checkout flow). Với flash sale, test concurrent write vào inventory (stock deduction đồng thời) vì đây thường là bottleneck mà load test read-heavy không phát hiện.

Bước ba: pre-scale. Đừng dựa vào auto-scaler cho sự kiện đã biết trước. Thêm app server, warm cache, tăng database connection pool trước sự kiện. Auto-scaler là lưới an toàn cho phần vượt dự kiến, không phải chiến lược chính cho sự kiện đã lên kế hoạch.

Bước bốn: kiểm tra kill-switch và fallback. Nếu payment gateway chậm, có fallback không? Nếu recommendation service sập, product page có hiển thị được không hay crash cả trang? Nếu traffic vượt capacity quá xa, có cách rate limit gracefully không, trả “vui lòng thử lại sau 30 giây” thay vì timeout 60 giây rồi error 500?

Bước năm: on-call roster và escalation path rõ ràng. Ai trực trong thời gian sự kiện? Ai có quyền scale infrastructure? Ai có quyền bật kill-switch? Runbook cho top 5 kịch bản lỗi có sẵn chưa? Communication channel (Slack channel riêng, bridge call) đã setup chưa?

Checklist nghe bureaucratic, nhưng mỗi mục tồn tại vì có team từng thiếu mục đó và trả giá. Incident lúc 2 giờ sáng Black Friday không phải lúc để nghĩ “ai có quyền scale database”.


Capacity planning là kỹ năng lặp lại

Capacity planning không phải bài toán giải một lần. Nó là vòng lặp liên tục: ước lượng → validate bằng load test → deploy → monitor actual usage → so sánh với ước lượng → điều chỉnh → ước lượng lại khi có thay đổi (feature mới, growth, architecture change).

flowchart LR A[Business metrics forecast] --> B[Back-of-envelope estimation] B --> C[Load test validation] C --> D[Provision infrastructure] D --> E[Monitor actual usage] E --> F[Compare actual vs estimate] F --> G[Quarterly review & adjust] G --> A

Lần đầu làm capacity planning sẽ sai, ước lượng lệch 2-3 lần là bình thường vì thiếu data và thiếu kinh nghiệm. Nhưng mỗi lần lặp lại, ước lượng chính xác hơn vì có historical data, có benchmark từ load test, và hiểu rõ hơn hành vi thực tế của hệ thống. Đó là lý do capacity planning là kỹ năng, cần luyện tập và cải thiện qua thời gian, không phải công thức áp dụng máy móc.


Capacity planning không cần chính xác, cần đúng bậc độ lớn và có load test để validate giả định trước khi production dạy bạn bài học đắt giá. Nguyên tắc đơn giản nhất: size cho peak có headroom 30–40%, và đừng để sự kiện lớn là lần đầu tiên hệ thống chịu traffic thực.