Mở rộng dọc thường chỉ là thêm CPU/RAM. Mở rộng ngang cần phối hợp, phân vùng dữ liệu, đảm bảo nhất quán và nhiều công việc vận hành—đây là lý do nó khó hơn.

Mở rộng nghĩa là “xử lý nhiều hơn mà không sập.” “Nhiều hơn” có thể là:
Khi người ta nói về mở rộng, thường họ cố gắng cải thiện một hoặc nhiều thứ sau:
Hầu hết đều xoay quanh một chủ đề: mở rộng dọc giữ cảm giác “một hệ thống duy nhất”, trong khi mở rộng ngang biến hệ thống thành một nhóm máy độc lập cần phối hợp—và chính việc phối hợp này làm độ khó bùng nổ.
Mở rộng dọc là làm cho một máy mạnh hơn. Giữ kiến trúc cơ bản, nhưng nâng cấp server (hoặc VM): thêm lõi CPU, thêm RAM, ổ đĩa nhanh hơn, băng thông mạng cao hơn.
Hãy tưởng tượng như mua một chiếc xe tải lớn hơn: vẫn một người lái và một xe, chỉ chở được nhiều hơn.
Mở rộng ngang là thêm nhiều máy hoặc instance và chia công việc giữa chúng—thường là phía sau một load balancer. Thay vì một server mạnh, bạn chạy nhiều server cùng làm việc.
Giống như dùng nhiều xe tải: vận chuyển tổng cộng nhiều hơn, nhưng giờ phải lo lịch trình, định tuyến và phối hợp.
Các kích hoạt phổ biến gồm:
Nhóm thường mở rộng dọc trước vì nhanh (nâng cấp máy), rồi mới mở rộng ngang khi một máy chạm tới giới hạn hoặc khi cần tính sẵn sàng cao hơn. Kiến trúc trưởng thành thường kết hợp cả hai: nút lớn hơn và nhiều nút, tuỳ vào điểm nghẽn.
Mở rộng dọc hấp dẫn vì giữ hệ thống ở một nơi. Với một node, bạn thường có một nguồn chân lý cho bộ nhớ và trạng thái cục bộ. Một tiến trình sở hữu cache trong RAM, job queue, session (nếu session nằm trong RAM), và file tạm.
Trên một server, hầu hết thao tác đơn giản vì gần như không cần phối hợp giữa các node:
Khi scale up, bạn kéo các cần quen thuộc: thêm CPU/RAM, dùng storage nhanh hơn, cải thiện index, tinh chỉnh query và cấu hình. Bạn không cần thiết kế lại cách dữ liệu được phân phối hay cách nhiều node đồng ý “việc gì sẽ xảy ra tiếp theo.”
Mở rộng dọc không phải “miễn phí”—nó chỉ giữ độ phức tạp trong phạm vi. Cuối cùng bạn sẽ chạm giới hạn: instance lớn nhất có thể thuê, lợi nhuận cận biên giảm, hoặc chi phí tăng mạnh ở đầu cao. Bạn cũng mang rủi ro downtime lớn hơn: nếu một máy lớn hỏng hoặc cần bảo trì, phần lớn hệ thống chịu ảnh hưởng trừ khi bạn đã thêm dự phòng.
Khi mở rộng ngang, bạn không chỉ có “nhiều server.” Bạn có nhiều tác nhân độc lập phải đồng ý ai chịu trách nhiệm cho mỗi công việc, vào thời điểm nào, và dùng dữ liệu nào.
Với một máy, phối hợp thường ngầm định: một không gian nhớ, một tiến trình, một nơi để xem trạng thái. Với nhiều máy, phối hợp trở thành một tính năng bạn phải thiết kế.
Các công cụ và mẫu hay dùng gồm:
Lỗi phối hợp hiếm khi xuất hiện như crash rõ ràng. Thường thấy:
Những lỗi này thường chỉ xuất hiện dưới tải thực, khi deploy, hoặc khi xảy ra thất bại từng phần (một node chậm, switch rớt gói, một zone chập chờn). Hệ thống có vẻ ổn—cho đến khi bị stress.
Khi mở rộng ngang, bạn thường không thể giữ toàn bộ dữ liệu ở một nơi. Bạn chia nó qua các máy (shard) để nhiều node có thể lưu và phục vụ song song. Chính việc chia này bắt đầu phát sinh độ phức tạp: mỗi đọc/ghi phụ thuộc vào “bản ghi này nằm ở shard nào?”
Range partitioning gom dữ liệu theo khoá có thứ tự (ví dụ users A–F ở shard 1, G–M ở shard 2). Trực quan và hỗ trợ truy vấn theo khoảng tốt (“hiện các đơn hàng tuần trước”). Nhược điểm là tải không đều: nếu một range nổi tiếng, shard đó sẽ nghẽn.
Hash partitioning chạy khoá qua hàm băm và phân phối kết quả qua các shard. Nó trải đều traffic hơn, nhưng làm truy vấn theo khoảng khó hơn vì các bản ghi liên quan bị phân tán.
Thêm node là muốn dùng nó—có nghĩa dữ liệu phải di chuyển. Xoá node (đã lên kế hoạch hoặc do hỏng) và các shard khác phải tiếp quản. Rebalance có thể gây truyền tải lớn, làm cache nguội lại, và tạm thời giảm hiệu năng. Trong quá trình di chuyển còn phải ngăn đọc cũ và ghi nhầm shard.
Ngay cả với hashing, traffic thực tế không đồng đều. Một tài khoản nổi tiếng, một sản phẩm phổ biến, hoặc mẫu truy cập theo thời gian có thể tập trung đọc/ghi vào một shard. Một shard nóng có thể giới hạn throughput toàn hệ thống.
Sharding dẫn đến trách nhiệm liên tục: duy trì quy tắc định tuyến, chạy migration, backfill sau thay đổi schema, và lên kế hoạch split/merge mà không phá vỡ client.
Khi mở rộng ngang, bạn không chỉ thêm server—bạn thêm nhiều bản sao của ứng dụng. Khó là phần trạng thái: bất cứ thứ gì app “nhớ” giữa các request hoặc khi công việc đang tiến hành.
Nếu người dùng đăng nhập ở Server A nhưng lần sau request đến Server B, Server B có biết họ là ai không?
Cache tăng tốc, nhưng nhiều server thì nhiều cache. Giờ bạn phải lo:
Với nhiều worker, job nền có thể chạy hai lần trừ khi bạn thiết kế để tránh. Thường cần queue, lease/khoá, hoặc logic idempotent để “gửi hoá đơn” hay “charge thẻ” không xảy ra hai lần—đặc biệt khi retry và restart xảy ra.
Với một node (hoặc DB primary đơn), thường có “nguồn chân lý” rõ ràng. Khi mở rộng ngang, dữ liệu và request phân tán, và giữ mọi thứ đồng bộ trở thành mối quan tâm liên tục.
Eventual consistency thường nhanh và rẻ hơn ở quy mô lớn, nhưng tạo ra các trường hợp cạnh gây bất ngờ.
Vấn đề phổ biến:
Bạn không thể loại trừ lỗi, nhưng có thể thiết kế giảm thiểu:
Một transaction qua nhiều dịch vụ (order + inventory + payment) cần nhiều hệ thống đồng ý. Nếu một bước thất bại giữa chừng, bạn cần hành động bù trừ và lưu trữ cẩn thận. Hành vi “tất cả hoặc không” truyền thống khó đạt khi mạng và node có thể rơi rụng riêng lẻ.
Dùng nhất quán mạnh cho những thứ phải chính xác: thanh toán, số dư tài khoản, số lượng tồn kho, đặt chỗ chỗ ngồi. Với dữ liệu ít quan trọng hơn (analytics, gợi ý), nhất quán cuối cùng thường chấp nhận được.
Khi scale up, nhiều “cuộc gọi” là gọi hàm trong cùng tiến trình: nhanh và dự đoán được. Khi scale out, cùng tương tác đó trở thành một cuộc gọi mạng—thêm độ trễ, jitter và các chế độ lỗi mà mã phải xử lí.
Cuộc gọi mạng có overhead cố định (serialization, queueing, hop) và overhead biến thiên (tắc nghẽn, định tuyến, noisy neighbors). Ngay cả khi độ trễ trung bình ổn, tail latency (1–5% chậm nhất) có thể chi phối trải nghiệm người dùng vì một dependency chậm làm toàn bộ request chờ.
Băng thông và mất gói cũng thành ràng buộc: ở tốc độ request cao, payload nhỏ cộng lại lớn, và retransmit âm thầm tăng tải.
Không có timeout, cuộc gọi chậm chồng chất và thread bị kẹt. Có timeout và retry thì phục hồi—cho tới khi retry khuếch đại tải.
Mẫu thất bại phổ biến là retry storm: backend chậm, client timeout và retry, retry làm backend nặng hơn, backend chậm hơn nữa.
Retry an toàn thường yêu cầu:
Với nhiều instance, client cần biết gửi request tới đâu—qua load balancer hoặc service discovery với cân bằng phía client. Dù cách nào, bạn thêm các thành phần chuyển động: health check, draining connection, phân phối traffic không đều, và rủi ro định tuyến tới instance nửa hỏng.
Để tránh lan truyền quá tải, bạn cần backpressure: queue có giới hạn, circuit breaker, và rate limiting. Mục tiêu là fail fast và dự đoán được thay vì để một chậm nhỏ biến thành incident toàn hệ thống.
Mở rộng dọc thường fail theo cách thẳng: một máy lớn vẫn là một điểm đơn. Nếu nó chậm hoặc crash, tác động rõ ràng.
Mở rộng ngang thay đổi bài toán. Với nhiều node, bình thường là một vài máy không khỏe trong khi những máy khác ổn. Hệ thống “up”, nhưng người dùng vẫn thấy lỗi, trang chậm, hoặc hành vi không nhất quán. Đây là lỗi từng phần, và nó trở thành trạng thái mặc định bạn phải thiết kế cho.
Trong setup mở rộng, dịch vụ phụ thuộc vào dịch vụ khác: DB, cache, queue, API downstream. Một sự cố nhỏ có thể lan:
Để sống sót qua lỗi từng phần, hệ thống thêm dự phòng:
Điều này tăng khả dụng, nhưng tạo ra các trường hợp biên: split-brain, replica lỗi thời, và phải quyết xem làm gì khi không đủ quorum.
Mẫu thường gặp bao gồm:
Với một máy, “câu chuyện hệ thống” nằm một chỗ: một set logs, một đồ thị CPU, một tiến trình để inspect. Với mở rộng ngang, câu chuyện bị phân tán.
Mỗi node thêm một luồng logs, metrics, và traces. Khó không phải là thu thập mà là liên kết chúng. Một lỗi checkout có thể bắt đầu ở web node, gọi hai service, chạm cache, đọc từ một shard cụ thể—để lại manh mối ở các chỗ và thời gian khác nhau.
Vấn đề cũng trở nên chọn lọc: một node cấu hình sai, một shard nóng, một zone có độ trễ cao. Debug có thể giống ngẫu nhiên vì “đa số thời gian hoạt động tốt.”
Tracing phân tán như gắn số theo dõi cho một request. Correlation ID là số đó. Bạn truyền nó qua dịch vụ và đưa vào logs để có thể lấy một ID và thấy toàn bộ hành trình end-to-end.
Thêm thành phần thường dẫn tới nhiều alert. Nếu không tuning, team bị mệt vì alert. Hướng tới alert có thể hành động, làm rõ:
Vấn đề dung lượng thường xuất hiện trước lỗi. Giám sát các tín hiệu bão hoà như CPU, memory, độ sâu queue, và việc sử dụng pool kết nối. Nếu bão hoà xuất hiện chỉ trên một tập con node, nghi ngờ cân bằng, sharding, hoặc drift cấu hình—không chỉ “nhiều traffic hơn.”
Khi mở rộng ngang, deploy không còn là “thay một máy.” Là phối hợp thay đổi qua nhiều máy trong khi giữ dịch vụ sẵn sàng.
Triển khai ngang thường dùng rolling update (thay node dần), canary (chỉ một phần traffic tới phiên bản mới), hoặc blue/green (chuyển traffic giữa hai môi trường đầy đủ). Chúng giảm blast radius, nhưng thêm yêu cầu: chuyển traffic, health check, draining connection, và định nghĩa “đủ tốt để tiếp tục.”
Trong deploy dần, phiên bản cũ và mới chạy song song. Sự không đồng nhất phiên bản này nghĩa hệ thống phải chịu được hành vi hỗn hợp:
API cần backward/forward compatibility, không chỉ đúng. Schema DB nên thay đổi theo cách cộng dần khi có thể (thêm cột nullable trước khi bắt buộc). Định dạng message nên version để consumer đọc cả event cũ và mới.
Rollback code dễ; rollback dữ liệu thì không. Nếu migration xóa hoặc viết lại field, code cũ có thể crash hoặc xử lý sai. Migrations theo kiểu “mở rộng/thu hẹp” giúp: deploy code hỗ trợ cả hai schema, migrate dữ liệu, rồi gỡ đường cũ sau.
Với nhiều node, quản lý cấu hình là một phần của deploy. Một node với cấu hình cũ, feature flag sai, hoặc credentials hết hạn có thể gây lỗi mập mờ và khó tái tạo.
Mở rộng ngang có vẻ rẻ hơn trên giấy: nhiều instance nhỏ, mỗi instance giá thấp. Nhưng tổng chi phí không chỉ là compute. Thêm node cũng nghĩa nhiều mạng hơn, nhiều monitoring hơn, nhiều phối hợp hơn, và nhiều thời gian để giữ mọi thứ nhất quán.
Mở rộng dọc gom chi phí vào ít máy—thường ít host để patch, ít agent chạy, ít log ship, ít metrics scrape hơn.
Với scale out, giá trên mỗi đơn vị có thể thấp, nhưng bạn thường trả cho:
Để xử lý spike an toàn, hệ phân tán thường chạy không đầy. Bạn giữ headroom ở nhiều tầng (web, worker, DB, cache), dẫn tới trả tiền cho tài nguyên nhàn rỗi trên hàng chục hay hàng trăm instance.
Scale out tăng gánh nặng on-call và đòi hỏi tooling chín muồi: tuning alert, runbook, drill sự cố, và đào tạo. Team cũng phải phân ranh giới sở hữu (ai sở hữu service nào?) và phối hợp khi incident.
Kết quả: “rẻ hơn trên mỗi đơn vị” vẫn có thể đắt hơn tổng thể khi tính thời gian con người, rủi ro vận hành, và công việc để khiến nhiều máy hành xử như một hệ thống.
Chọn giữa mở rộng dọc (máy lớn hơn) và mở rộng ngang (nhiều máy) không chỉ về giá. Nó về kiểu workload và mức phức tạp vận hành team bạn có thể chịu.
Bắt đầu từ workload:
Một con đường hợp lý:
Nhiều nhóm giữ DB theo chiều dọc (hoặc cluster nhẹ) trong khi mở rộng tầng app stateless theo ngang. Điều này hạn chế nỗi đau sharding trong khi vẫn tăng được capacity web nhanh.
Bạn gần hơn khi có monitoring và alert tốt, failover đã test, load test, và deploy lặp lại với rollback an toàn.
Nhiều nỗi đau khi scale không chỉ là “kiến trúc”—mà là vòng lặp vận hành: lặp an toàn, deploy đáng tin cậy, và rollback nhanh khi thực tế khác với kế hoạch.
Nếu bạn xây web, backend hoặc mobile và muốn tiến nhanh mà không mất kiểm soát, Koder.ai có thể giúp bạn prototype và ship nhanh hơn trong khi ra quyết định scale. Đây là một nền tảng vibe-coding nơi bạn xây ứng dụng qua chat, với kiến trúc agent-based bên dưới. Thực tế điều đó có nghĩa bạn có thể:
Vì Koder.ai chạy toàn cầu trên AWS, nó cũng hỗ trợ triển khai ở nhiều vùng để đáp ứng yêu cầu độ trễ và chuyển dữ liệu—hữu ích khi multi-zone hoặc multi-region trở thành phần câu chuyện mở rộng của bạn.
Mở rộng dọc là làm cho một máy mạnh hơn (thêm CPU/RAM/ổ nhanh hơn). Mở rộng ngang là thêm nhiều máy và phân phối công việc cho chúng.
Mở rộng dọc thường có cảm giác đơn giản hơn vì ứng dụng vẫn như “một hệ thống”, trong khi mở rộng ngang bắt buộc nhiều hệ thống phối hợp và giữ nhất quán.
Bởi vì ngay khi bạn có nhiều node, bạn cần có cơ chế phối hợp rõ ràng:
Một máy đơn tránh được nhiều vấn đề phân tán này theo mặc định.
Đó là thời gian và logic để khiến nhiều máy hoạt động như một:
Ngay cả khi mỗi node đơn giản, hành vi hệ thống trở nên khó suy đoán hơn dưới tải và khi có lỗi.
Sharding (phân vùng) tách dữ liệu qua nhiều node để không một máy nào phải lưu/đọc mọi thứ. Nó khó vì bạn phải:
Nó cũng tăng công việc vận hành (migrates, backfill, bản đồ shard).
State là bất kỳ thứ gì ứng dụng “nhớ” giữa các yêu cầu hoặc trong khi công việc đang chạy (session, cache trong RAM, file tạm, tiến độ job).
Khi mở rộng ngang, yêu cầu có thể tới các server khác nhau, nên bạn thường cần store chia sẻ (ví dụ Redis/DB) hoặc chấp nhận các đánh đổi như sticky sessions.
Nếu nhiều worker có thể lấy cùng một job (hoặc job bị retry), bạn có thể charge hai lần hoặc gửi email trùng.
Các biện pháp phổ biến:
Strong consistency nghĩa là một khi ghi thành công, mọi reader thấy giá trị mới ngay lập tức. Eventual consistency nghĩa là cập nhật sẽ lan toả dần, nên một số reader có thể thấy dữ liệu cũ trong thời gian ngắn.
Dùng strong consistency cho dữ liệu bắt buộc chính xác (thanh toán, số dư, tồn kho). Dùng eventual consistency cho dữ liệu chấp nhận trễ nhỏ (analytics, gợi ý).
Trong hệ phân tán, các cuộc gọi trở thành cuộc gọi qua mạng, mang theo độ trễ, biến thiên và lỗi.
Các điều cơ bản thường cần:
Partial failure nghĩa là một số thành phần bị lỗi hoặc chậm trong khi những phần khác vẫn ổn. Hệ thống có thể “up” nhưng vẫn sinh lỗi, timeout hoặc hành vi không nhất quán.
Các đáp ứng thường gặp: replication, quorum, triển khai đa zone, circuit breaker và giảm dần chức năng để tránh lan tỏa sự cố.
Khi chạy trên nhiều máy, bằng chứng sự cố bị phân mảnh: logs, metrics, traces nằm rải rác.
Bước thực tế: