Scaling Microservices — 4 trục scale: vertical & horizontal (BMS)

Phong

Mở đầu

Ảnh: panumas nikhomkhai — Pexels

Scaling là chuyện hầu hết developer gặp hàng ngày mà không nhận ra: hệ thống chậm dần khi user tăng, rồi cả team lao vào "tối ưu" lung tung. Trong cuốn Building Microservices, Sam Newman dành hẳn Chapter 13 để nói về bốn trục scaling và cách kết hợp chúng trong một kiến trúc microservices. Với backend engineer, đây là chương giúp trả lời câu hỏi căn bản nhất: khi hệ thống không còn đủ nhanh, nên làm gì trước?

Theo Newman, người ta scale hệ thống vì hai lý do: hoặc để đối phó với failure (nếu lo sợ thứ gì đó sẽ hỏng, thì có nhiều bản sao hơn sẽ giúp), hoặc để cải thiện hiệu năng (xử lý nhiều tải hơn, giảm latency). Hai mục tiêu này dẫn tới những lựa chọn trục scaling rất khác nhau — và đây là chỗ nhiều team chọn sai ngay từ đầu.

Bốn trục scaling

Newman outline bốn trục (four axes) để scale một hệ thống, lấy cảm hứng từ mô hình Scale Cube trong cuốn "The Art of Scalability":

1. Vertical scaling (scale up): nâng cấp phần cứng — CPU mạnh hơn, RAM nhiều hơn, ổ cứng nhanh hơn. Trên cloud public, thao tác này cực nhanh và rủi ro thấp: đổi instance type là xong. Nhưng nó không cải thiện robustness — một con server to gấp đôi vẫn chỉ là một điểm chết duy nhất. Chi phí còn tăng theo cấp số nhân ở phân khúc cao: một con máy khủng có thể đắt hơn hai con nhỏ cộng lại với cùng tổng sức mạnh.

2. Horizontal duplication (scale out): chạy nhiều instance giống hệt nhau phía sau load balancer. Đây là trục vừa tăng khả năng xử lý vừa cải thiện robustness — một instance chết thì những instance còn lại vẫn gánh được. Điều kiện tiên quyết: service phải stateless, hoặc state phải nằm ở nơi chia sẻ được (database, Redis...).

3. Data partitioning (sharding): chia dữ liệu ra nhiều phần, mỗi phần nằm ở một nơi riêng — theo khách hàng, theo khu vực, theo ID. Trục này giảm tải cho từng node dữ liệu nhưng không cải thiện robustness: shard chết là mất luôn phần dữ liệu đó trừ khi có cơ chế khác. Kèm theo là chi phí vận hành: rebalancing, join xuyên shard, hot shard...

4. Functional decomposition (tách theo chức năng): chia hệ thống thành các service theo business capability — đây chính là bản chất của microservices. Trục này cải thiện robustness (một service lỗi không kéo sập toàn bộ) và cho phép chọn công nghệ phù hợp với tải của từng service.

Ảnh: Lukas Blazek — Pexels

Microservices — scale từng service, không scale cả hệ thống

Điểm khác biệt lớn nhất giữa monolith và microservices khi nói tới scaling: monolith thì scale cả khối (vertical hoặc horizontal duplication cho toàn bộ app), còn microservices cho phép áp dụng trục khác nhau cho từng service. Chỉ riêng service "checkout" đang nghẽn? Scale nó lên vài instance nữa là xong, không cần đụng tới service khác. Service "report" chạy nặng? Có thể chọn công nghệ phù hợp với tải batch của riêng nó nhờ functional decomposition, mà không ảnh hưởng tới phần còn lại.

Đây cũng là lúc các chương trước của cuốn sách bắt đầu "đổ bộ": container và Kubernetes (Chapter 8) giúp horizontal duplication trở nên rẻ và tự động, còn việc chia service chuẩn theo bounded context (Chapter 2) giúp functional decomposition có ý nghĩa thực sự. Scale không phải là chuyện chỉnh config vào phút cuối — nó ăn sâu vào cách hệ thống được thiết kế từ đầu.

Caching — càng ít nơi cache càng tốt

Newman dành sự chú ý đặc biệt cho caching và nhấn mạnh một câu nghe ngược đời: số nơi lý tưởng để cache là zero. Cache giúp cải thiện robustness lẫn latency, nhưng mỗi tầng cache thêm vào là một tầng phức tạp về invalidation và consistency.

Client-side cache: latency thấp nhất vì dữ liệu nằm ngay trên máy client, nhưng vấn đề chính là invalidation — làm sao client biết dữ liệu đã cũ? Server-side cache: cải thiện latency cho tất cả client, nhưng client vẫn phải đi một vòng network tới server. Kèm theo là các kỹ thuật quen thuộc: trả TTL trong response, conditional GET với ETag để client chỉ tải lại khi dữ liệu thực sự thay đổi, và notification qua pub/sub — nhớ kèm heartbeat để client biết kênh thông báo còn sống.

CQRS — một trong những hình thức scaling khó nhất

CQRS (Command Query Responsibility Segregation) tách luồng đọc và luồng ghi thành hai mô hình riêng, mỗi bên scale độc lập: đọc nhiều thì scale query side, ghi nhiều thì scale command side. Newman gọi đây là một trong những hình thức scaling khó nhất, bởi vì bạn phải đối mặt với toàn bộ phức tạp của consistency giữa hai phía — dữ liệu ghi ở chỗ này, đọc ở chỗ khác, độ trễ đồng bộ, xung đột. Nếu chưa thực sự cần, đừng vội nhảy vào CQRS.

Ảnh: panumas nikhomkhai — Pexels

Key Takeaways

  • Bốn trục scaling: vertical, horizontal duplication, data partitioning, functional decomposition — mỗi trục có mục đích và đánh đổi riêng.
  • Vertical scaling nhanh, rủi ro thấp nhưng không cải thiện robustness; horizontal duplication và functional decomposition thì có.
  • Microservices cho phép scale từng service riêng lẻ — lợi thế lớn nhất so với monolith.
  • Số nơi lý tưởng để cache là zero; nếu phải cache, hãy dùng TTL, conditional GET và notification kèm heartbeat.
  • CQRS là hình thức scaling khó, chỉ nên dùng khi thực sự cần tách đọc/ghi.
  • Đến một ngưỡng nhất định, scale không còn đủ — bạn phải re-architect lại hệ thống.

Glossary

Thuật ngữÝ nghĩa
Vertical scalingScale up — nâng cấp phần cứng của một máy
Horizontal duplicationScale out — thêm nhiều instance giống nhau phía sau load balancer
Data partitioningChia dữ liệu thành nhiều phần (shard) nằm ở các nơi khác nhau
Functional decompositionTách hệ thống thành các service theo chức năng nghiệp vụ
RobustnessKhả năng hệ thống tiếp tục hoạt động khi có thành phần bị lỗi
CachingLưu kết quả đã tính để tái sử dụng, giảm tải và latency
Conditional GETClient gửi ETag để server chỉ trả dữ liệu khi nó thay đổi
CQRSTách luồng đọc và luồng ghi thành hai mô hình scale độc lập

Kết

Scaling không phải là "thêm máy" theo nghĩa đen. Nó là việc chọn đúng trục cho đúng vấn đề: cần chịu lỗi thì nhân bản, cần tách tải thì partition dữ liệu, cần độc lập thì tách service, còn nếu chỉ cần thêm sức mạnh tạm thời thì scale up. Với microservices, lợi thế nằm ở chỗ bốn trục này có thể kết hợp linh hoạt cho từng service riêng lẻ.

Chương tiếp theo của cuốn sách chuyển hẳn sang Part III — bắt đầu với Chapter 14 về User Interfaces: khi backend đã scale tốt, làm sao để UI theo kịp? Hẹn gặp lại ở bài sau.