Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng vào 20 giờ tối thứ Bảy — cao điểm mua sắm — website thương mại điện tử của bạn bỗng dưng "sập". Không phải vì bị tấn công, không phải vì code lỗi, mà chỉ đơn giản vì một máy chủ duy nhất chạy toàn bộ hệ thống bị hỏng ổ đĩa. Trong 45 phút khôi phục, công ty mất hàng trăm đơn hàng, hàng nghìn khách hàng bực bội, và cả một buổi tối của đội kỹ thuật. Câu chuyện này lặp đi lặp lại ở rất nhiều doanh nghiệp Việt Nam, và gần như tất cả đều có chung một gốc rễ: hệ thống được thiết kế mà không tính đến chuyện "khi một thứ hỏng thì sao".
High Availability (HA — tính sẵn sàng cao) chính là câu trả lời cho câu hỏi đó. Đây không phải là một công cụ bạn cài đặt, mà là một triết lý thiết kế: giả định rằng mọi thành phần đều CÓ THỂ hỏng, và xây dựng hệ thống sao cho khi một phần hỏng, phần còn lại vẫn tiếp tục phục vụ người dùng. Trong bài này, chúng ta sẽ đi sâu vào các mô hình (pattern) HA phổ biến, và quan trọng hơn — những đánh đổi (trade-off) mà bạn buộc phải cân nhắc. Bởi vì HA không bao giờ miễn phí: nó tốn tiền, tốn độ phức tạp, và đôi khi tốn cả hiệu năng.
Lưu ý phạm vi: bài này tập trung vào thiết kế HA và các đánh đổi của nó. Những chủ đề liên quan như load balancing chi tiết (Bài 29), auto-scaling (Bài 30), disaster recovery với RTO/RPO (Bài 26) hay kiến trúc multi-region (Bài 57) sẽ được bàn riêng — ở đây ta chỉ chạm đến khi cần bối cảnh.
Khái niệm cốt lõi
HA là gì và đo bằng con số nào?
High Availability là khả năng của hệ thống tiếp tục hoạt động bình thường ngay cả khi một số thành phần gặp sự cố. Người ta đo HA bằng phần trăm uptime (thời gian hệ thống sẵn sàng) trong một khoảng thời gian, thường tính theo năm. Cách nói quen thuộc là "số 9":
- 99% (two nines): cho phép downtime khoảng 3,65 ngày/năm — thực ra khá tệ.
- 99,9% (three nines): khoảng 8,76 giờ/năm — mức phổ biến cho nhiều dịch vụ nội bộ.
- 99,99% (four nines): khoảng 52,6 phút/năm — mức các dịch vụ nghiêm túc nhắm tới.
- 99,999% (five nines): khoảng 5,26 phút/năm — cực kỳ khó và đắt, dành cho hệ thống tài chính, viễn thông.
Kẻ thù số một: Single Point of Failure (SPOF)
Trái tim của mọi thiết kế HA là việc săn tìm và loại bỏ Single Point of Failure — bất kỳ thành phần nào mà khi nó hỏng, toàn bộ hệ thống ngừng hoạt động. SPOF có thể ở khắp nơi: một máy chủ duy nhất, một database duy nhất, một load balancer duy nhất, một đường mạng duy nhất, thậm chí một nguồn điện duy nhất trong data center. Nhiều đội chỉ nhân đôi máy chủ ứng dụng rồi tự tin rằng mình "đã HA", trong khi database vẫn chỉ có một node — SPOF vẫn nằm nguyên đó.
Kỹ thuật thực hành rất đơn giản: vẽ sơ đồ kiến trúc, rồi với từng ô, tự hỏi "nếu ô này chết lúc này, chuyện gì xảy ra?". Ô nào mà câu trả lời là "cả hệ thống sập" thì đó chính là SPOF cần xử lý.
Các pattern HA chính
1. Redundancy (Dự phòng) — nền tảng của mọi thứ
Ý tưởng cốt lõi: có nhiều bản sao của cùng một thành phần, để khi một bản hỏng thì bản khác gánh thay. Ta thường nói đến mô hình N+1 (cần N máy để phục vụ tải, chạy N+1 máy — dư ra một máy để chịu được một hỏng hóc) hoặc N+2 (chịu được hai hỏng đồng thời, hoặc một hỏng trong lúc một máy khác đang bảo trì).
Redundancy chia làm hai kiểu vận hành, và đây là đánh đổi quan trọng bạn phải hiểu:
- Active-Active: tất cả các bản sao đều đang phục vụ tải cùng lúc. Ưu điểm: tận dụng hết tài nguyên, chuyển đổi khi hỏng gần như tức thì (vì máy còn lại đã đang chạy). Nhược điểm: phức tạp hơn khi cần đồng bộ trạng thái, và bạn phải đảm bảo mỗi máy chỉ được tải tối đa ~50% (với 2 máy) để khi một máy chết, máy kia không bị quá tải.
- Active-Passive (hay standby): một bản chạy chính, bản kia "ngồi chờ", chỉ nhảy vào khi bản chính hỏng (failover). Ưu điểm: đơn giản hơn, ít lo xung đột trạng thái. Nhược điểm: máy passive "ngồi không" tốn tiền, và quá trình failover mất một khoảng thời gian (vài giây đến vài phút).
Có dự phòng thôi chưa đủ — hệ thống phải biết khi nào một thành phần hỏng và tự động chuyển hướng. Đây là vai trò của health check: liên tục "hỏi thăm" từng thành phần (ví dụ gọi một endpoint /health mỗi 5 giây), và nếu nó không trả lời sau vài lần liên tiếp thì đánh dấu là "unhealthy" và ngừng gửi traffic tới. Cơ chế failover là quá trình chuyển tải sang thành phần khỏe mạnh.
Một cạm bẫy kinh điển ở đây là split-brain: trong cụm active-passive, nếu node passive tưởng nhầm node active đã chết (do mạng chập chờn) và tự đứng lên làm chính, trong khi node active thật ra vẫn sống — bạn có HAI node cùng nghĩ mình là chính, ghi dữ liệu mâu thuẫn nhau. Đây là lý do các hệ thống nghiêm túc dùng cơ chế quorum (đa số biểu quyết) với số node lẻ (3, 5) để tránh tình huống "hai phe bằng nhau không ai thắng".
3. Loại bỏ trạng thái — Stateless design
Một hệ thống dễ HA nhất là hệ thống mà bất kỳ request nào cũng có thể được xử lý bởi bất kỳ máy nào. Điều này chỉ đạt được khi các máy ứng dụng không lưu trạng thái cục bộ (stateless) — session, giỏ hàng, dữ liệu tạm... đều đẩy ra ngoài (vào Redis, database chung). Khi một máy chết, người dùng chỉ cần được định tuyến sang máy khác mà không mất gì. Ngược lại, nếu session lưu trong bộ nhớ của từng máy (sticky session dựa trên bộ nhớ cục bộ), máy chết đồng nghĩa toàn bộ người dùng trên máy đó bị đăng xuất.
Trade-off: HA không phải lúc nào cũng "đúng"
Đây là phần mà các mentor giỏi luôn nhấn mạnh. HA có ba cái giá phải trả:
- Chi phí: nhân đôi (hoặc hơn) hạ tầng. Máy passive ngồi không vẫn tính tiền.
- Độ phức tạp: nhiều thành phần hơn, nhiều thứ có thể hỏng hơn, khó debug hơn. Trớ trêu thay, đôi khi chính cơ chế HA lại gây ra sự cố (health check cấu hình sai làm rớt máy khỏe).
- Định lý CAP: khi có sự cố mạng chia cắt (partition), một hệ thống phân tán buộc phải chọn giữa Consistency (dữ liệu luôn nhất quán) và Availability (luôn trả lời). Bạn không thể có cả hai cùng lúc trong lúc bị chia cắt. Một ngân hàng sẽ chọn Consistency (thà từ chối giao dịch còn hơn trừ tiền sai); một mạng xã hội thường chọn Availability (hiển thị dữ liệu hơi cũ còn hơn báo lỗi).
Tình huống thực tế
Ví dụ 1: Sàn TMĐT Việt Nam và cú sập ngày sale 12/12
Một sàn thương mại điện tử tầm trung tại TP.HCM (gọi là "ShopViet") có kiến trúc: 4 web server đứng sau một load balancer, tất cả kết nối tới một database MySQL duy nhất. Đội kỹ thuật tự tin rằng họ "đã HA" vì có 4 web server. Đến ngày sale lớn 12/12, đúng lúc 21 giờ, database chính bị đầy disk và crash. Bốn web server vẫn "khỏe" theo health check ở tầng web, nhưng không cái nào phục vụ được đơn hàng vì database — cái SPOF thật sự — đã chết. Website "sống mà như chết" trong 38 phút.
Bài học rút ra: HA phải được đánh giá theo toàn bộ chuỗi, không phải từng tầng riêng lẻ. Sau sự cố, ShopViet chuyển sang MySQL với cấu hình primary–replica: một replica luôn đồng bộ, và họ cấu hình failover tự động (dùng công cụ như Orchestrator). Họ cũng đặt N+1 ở tầng database. Đây là ví dụ điển hình của việc "nhân đôi nhầm chỗ" — dự phòng ở nơi không phải điểm yếu nhất.
Ví dụ 2: Grab và bài toán active-active đa vùng
Grab, siêu ứng dụng lớn ở Đông Nam Á, phục vụ hàng triệu chuyến xe mỗi ngày. Với quy mô này, downtime vài phút giờ cao điểm nghĩa là hàng chục nghìn chuyến xe không đặt được. Grab (theo các bài chia sẻ kỹ thuật công khai của họ) áp dụng kiến trúc active-active: dịch vụ chạy song song ở nhiều availability zone, mọi zone đều phục vụ traffic thật. Khi một zone gặp sự cố, traffic tự động dồn sang zone còn lại. Điểm tinh tế là họ phải luôn duy trì mỗi zone ở mức tải "an toàn" — nếu bình thường mỗi zone chạy 45% công suất, thì khi một trong hai zone chết, zone còn lại nhảy lên 90%, vẫn trong ngưỡng chịu được.
Bài học rút ra: Active-active cho khả năng failover gần như tức thời và tận dụng tài nguyên tốt, nhưng đòi hỏi kỷ luật về headroom (dung lượng dư). Nếu bạn để mỗi zone chạy 80% "cho tiết kiệm", thì khi một zone chết, zone kia sẽ vượt 100% và cũng sập theo — hiệu ứng domino còn tệ hơn cả không HA.
Ví dụ 3: Startup fintech và quyết định chọn Consistency
Một startup fintech ở Hà Nội xây dịch vụ ví điện tử. Ban đầu, vì muốn "luôn sẵn sàng", họ thiết kế database phân tán ưu tiên Availability — khi mạng giữa các node chập chờn, mọi node vẫn nhận ghi. Kết quả: trong một lần partition mạng kéo dài 90 giây, hai node cùng xử lý cho một tài khoản có số dư 500.000đ, và cả hai đều cho phép rút 500.000đ — tổng cộng rút 1 triệu từ một tài khoản chỉ có 500 nghìn. Một lỗi "double spend" kinh điển.
Bài học rút ra: Với dữ liệu tài chính, Consistency quan trọng hơn Availability. Họ chuyển sang cấu hình yêu cầu quorum (đa số node phải đồng ý) trước khi xác nhận giao dịch. Hệ quả là khi bị partition, một số giao dịch bị từ chối tạm thời — availability giảm nhẹ — nhưng không bao giờ có chuyện tiền sai. Đây chính là định lý CAP trong đời thực: bạn phải chọn, và với tiền bạc thì luôn chọn đúng hơn là chọn nhanh.
Hướng dẫn từng bước
Đây là quy trình thực tế để thiết kế HA cho một hệ thống:
- Xác định mục tiêu uptime cần thiết. Đừng mặc định "càng cao càng tốt". Hỏi rõ: dịch vụ này downtime 1 giờ thì thiệt hại bao nhiêu tiền/uy tín? Từ đó chọn mức số 9 hợp lý (đa số dịch vụ web thương mại nhắm 99,9–99,99%).
- Vẽ sơ đồ kiến trúc và săn SPOF. Liệt kê mọi thành phần: load balancer, web server, database, cache, message queue, DNS, đường mạng, nguồn điện. Với mỗi cái, hỏi "nếu nó chết thì sao?". Đánh dấu mọi SPOF.
- Thêm redundancy cho từng SPOF theo thứ tự ưu tiên. Bắt đầu từ thành phần có xác suất hỏng cao và ảnh hưởng lớn nhất (thường là database và load balancer). Chọn N+1 hay N+2 tùy mức uptime mục tiêu.
- Quyết định active-active hay active-passive cho từng tầng. Tầng stateless (web/app) thường active-active dễ dàng. Tầng có trạng thái (database) thường active-passive với replica đồng bộ, hoặc dùng cụm quorum.
- Cấu hình health check và failover tự động. Định nghĩa endpoint
/healthphản ánh đúng "sức khỏe thật" (kiểm tra cả kết nối database, không chỉ trả về 200 OK vô nghĩa). Đặt ngưỡng: bao nhiêu lần fail liên tiếp thì loại máy ra.
- Làm stateless hết mức có thể. Đẩy session, cache ra store dùng chung. Đảm bảo bất kỳ request nào cũng chạy được trên bất kỳ máy nào.
- Kiểm tra định lý CAP cho tầng dữ liệu. Xác định rõ: khi partition xảy ra, hệ thống này nên ưu tiên Consistency hay Availability? Cấu hình database cho đúng lựa chọn đó.
- Thử nghiệm bằng cách... cố tình làm hỏng. Đây là bước bị bỏ qua nhiều nhất. Hãy tắt thử một máy chủ trong giờ thấp điểm và quan sát: failover có xảy ra không? Mất bao lâu? Người dùng có bị ảnh hưởng không? (Chủ đề này được đào sâu ở Bài 24 — Chaos Engineering.)
Lỗi thường gặp & mẹo
- Nhân đôi nhầm chỗ. Như ShopViet — thêm 10 web server nhưng vẫn một database. Luôn HA theo toàn chuỗi, không phải từng tầng.
- Quên load balancer cũng là SPOF. Bạn có 4 web server nhưng chỉ một load balancer đứng trước? Nếu load balancer chết, cả 4 server thành vô dụng. Cần cặp load balancer với floating IP hoặc dùng load balancer managed của cloud (vốn đã HA sẵn).
- Health check "dối trá". Endpoint
/healthchỉ trả về 200 mà không thực sự kiểm tra database. Máy vẫn "khỏe" trên giấy nhưng thực tế không phục vụ được. Health check phải kiểm tra các phụ thuộc quan trọng.
- Không tính headroom cho failover. Với active-active 2 node, đừng để mỗi node chạy quá ~50% công suất. Node còn lại phải gánh được toàn bộ tải khi node kia chết.
- Bỏ qua split-brain. Trong active-passive, không có cơ chế quorum/fencing thì rủi ro hai node cùng làm chính. Luôn dùng số node lẻ và cơ chế đa số biểu quyết cho cụm dữ liệu.
- Chính HA gây ra sự cố. Cơ chế failover cấu hình sai, health check quá nhạy làm rớt máy khỏe rồi tạo hiệu ứng domino. Mẹo: cấu hình health check có độ trễ hợp lý (vài lần fail liên tiếp mới loại), và luôn test cơ chế HA trong môi trường staging.
- Theo đuổi số 9 không cần thiết. Một dashboard nội bộ không cần 99,999%. Đừng đốt tiền và công sức vào mức uptime mà nghiệp vụ không đòi hỏi.
Bài tập thực hành
- Săn SPOF. Vẽ sơ đồ kiến trúc một hệ thống bạn đang biết (dự án cá nhân, hệ thống ở công ty, hoặc một hệ thống giả định gồm: người dùng → DNS → load balancer → web server → database → cache). Với mỗi thành phần, ghi "nếu chết thì sao". Liệt kê ít nhất 3 SPOF và đề xuất cách loại bỏ từng cái.
- Tính số 9. Một dịch vụ trong tháng vừa rồi có tổng downtime là 26 phút. Hỏi: dịch vụ đạt mức mấy số 9? (Gợi ý: một tháng ~30 ngày = 43.200 phút; tính uptime %.) Nếu mục tiêu là 99,99%/năm, họ đang vượt hay chưa đạt?
- Chọn Active-Active hay Active-Passive. Cho 3 hệ thống: (a) web server bán hàng stateless, (b) database giao dịch ngân hàng, (c) máy chủ báo cáo nội bộ chạy vài lần/ngày. Với mỗi cái, chọn mô hình redundancy phù hợp và giải thích đánh đổi.
- Áp dụng CAP. Với ứng dụng đặt vé xem phim (không được bán trùng ghế), khi xảy ra partition mạng, bạn chọn ưu tiên Consistency hay Availability? Vì sao? Viết 3–4 câu lập luận.
Tóm tắt
High Availability là triết lý thiết kế giả định mọi thứ đều có thể hỏng, và đảm bảo hệ thống vẫn phục vụ khi một phần gặp sự cố. Ta đo HA bằng "số 9" uptime, và nhớ rằng mỗi số 9 tăng thêm khiến chi phí và độ phức tạp tăng theo cấp số nhân — nên câu hỏi đầu tiên luôn là "cần bao nhiêu số 9 cho đủ".
Trái tim của HA là săn và loại bỏ Single Point of Failure. Các pattern nền tảng gồm: redundancy (N+1, N+2) với hai kiểu vận hành active-active (nhanh, tận dụng tài nguyên, cần headroom) và active-passive (đơn giản, nhưng máy dự phòng tốn tiền và failover có độ trễ); health check + failover tự động (cẩn thận split-brain, dùng quorum số lẻ); và thiết kế stateless để mọi máy đều thay thế được cho nhau.
Trên hết, hãy luôn nhớ HA không miễn phí — nó đánh đổi chi phí, độ phức tạp, và buộc bạn chọn giữa Consistency và Availability khi mạng bị chia cắt (định lý CAP). Ngân hàng chọn đúng (Consistency), mạng xã hội chọn nhanh (Availability). Thiết kế HA giỏi không phải là nhồi càng nhiều dự phòng càng tốt, mà là hiểu rõ nghiệp vụ cần gì rồi đầu tư đúng chỗ, đúng mức. Và đừng quên: một thiết kế HA chỉ thực sự đáng tin khi bạn đã cố tình làm nó hỏng và thấy nó vẫn đứng vững.