SaaS single-tenant cấp cho mỗi khách hàng một bản ứng dụng và cơ sở dữ liệu riêng. SaaS multi-tenant cho tất cả khách hàng dùng chung một ứng dụng và một cơ sở dữ liệu, phân biệt nhau bằng mã tenant (tenant ID), và mã này phải được kiểm tra trong mọi truy vấn. Single-tenant mang lại sự cô lập, quyền kiểm soát riêng cho từng khách hàng và giúp việc đáp ứng các yêu cầu tuân thủ (compliance) dễ hơn, đổi lại bạn phải vận hành cả một loạt bản sao. Multi-tenant rẻ hơn và vận hành đơn giản hơn nhiều, nhưng việc giữ dữ liệu của các khách hàng tách biệt nhau trở thành trách nhiệm của code. Phần lớn sản phẩm bắt đầu với multi-tenant, rồi chỉ cung cấp instance riêng cho số ít khách hàng thật sự cần.
Sản phẩm SaaS B2B nào sớm muộn cũng phải đối mặt với câu hỏi về tenancy, tức các khách hàng (tenant) dùng chung hay tách riêng hạ tầng, thường là vào cái ngày có một khách hàng lớn tiềm năng hỏi xem dữ liệu của họ có được lưu tách riêng khỏi các khách hàng khác không. Tôi đã chứng kiến cuộc thảo luận này ở hơn một công ty, và lần nào câu trả lời cũng quy về hai lựa chọn: cho mỗi khách hàng một instance riêng (single-tenant, multi-instance), hoặc cho tất cả chạy chung trên một instance (multi-tenant, single-instance). Không có lựa chọn nào là “đúng”. Mỗi cái chỉ gặp sự cố theo một kiểu khác nhau.
Thiết kế hạ tầng#
Single tenant, multi-instance#
Điểm hấp dẫn nhất ở đây là tính cô lập, và gần như mọi lợi ích khác đều xuất phát từ đó. Dữ liệu của mỗi khách hàng nằm trong một môi trường riêng, nên các đợt đánh giá bảo mật hay những buổi làm việc về tuân thủ quy định (compliance) dễ thở hơn hẳn. Một lần triển khai lỗi hay một tải xử lý (workload) quá nặng chỉ làm sập một khách hàng, chứ không kéo sập tất cả. Bạn có thể mở rộng quy mô, tinh chỉnh và tùy biến từng instance độc lập với nhau, và vì tài nguyên được cấp riêng nên hiệu năng của khách này không phụ thuộc vào việc tenant bên cạnh đang làm gì. Lịch bảo trì cũng có thể xếp theo từng khách hàng, nên thời gian gián đoạn dịch vụ (downtime) vừa ngắn vừa đúng chỗ. Và nếu định giá hợp lý, bạn có thể tính chi phí hạ tầng sát với mức sử dụng thực tế của từng tenant.
Cái giá phải trả là giờ bạn đang vận hành N bản sao của mọi thứ. Chi phí tài nguyên cộng dồn lại, cấu hình giữa các instance dần dần lệch nhau, và riêng việc giữ cho phiên bản phần mềm lẫn dữ liệu đồng bộ trên cả dàn instance đã thành một dự án riêng. Chuyện mở rộng quy mô, cô lập và quản lý chi phí vẫn cần được lên kế hoạch nghiêm túc. Các vấn đề không hề biến mất, chúng chỉ nhân lên.
Multi tenant, single instance#
Đây là lựa chọn mặc định cũng có lý do của nó: một lần triển khai, một cơ sở dữ liệu cần sao lưu, một phiên bản phần mềm duy nhất trên môi trường production. Mô hình này tiết kiệm tài nguyên, vận hành đơn giản, và mọi khách hàng đều nhận bản cập nhật cũng như bản vá bảo mật cùng một lúc.
Những phần khó lại chính là mặt trái của việc dùng chung. Chỉ một tenant dùng nặng cũng có thể kéo hiệu năng của tất cả mọi người đi xuống. Khả năng tùy biến cho từng khách hàng thì bị hạn chế. Việc cô lập dữ liệu giờ nằm trong mã ứng dụng chứ không còn nằm ở tầng hạ tầng, nên chỉ một lỗi nhỏ cũng có thể dẫn tới rò rỉ dữ liệu giữa các tenant. Khi gặp điểm nghẽn (bottleneck) lúc mở rộng quy mô thì mọi khách hàng cùng chịu ảnh hưởng, và việc bảo trì phải cẩn thận hơn, vì không còn kiểu sự cố “chỉ ảnh hưởng mỗi khách này” nữa.
Tìm hiểu thêm về giải pháp SaaS - Kiến trúc Multi-instance và Multi-tenant
Multi-tenant hay multi-instance#
Bạn sẽ thường gặp lựa chọn này dưới cái tên multi-tenant và multi-instance, nhưng thực ra vẫn là cùng một lựa chọn, chỉ nhìn từ phía bên kia. “Tenant” nói về khách hàng: bao nhiêu khách hàng dùng chung một bản phần mềm. “Instance” nói về việc triển khai: bạn chạy bao nhiêu bản phần mềm. Một sản phẩm single-tenant chính là một hệ thống multi-instance, mỗi khách hàng một bản, còn sản phẩm multi-tenant thường là một instance duy nhất mà mọi người dùng chung. Vậy nên khi ai đó đòi multi-instance, thực chất họ đang đòi single-tenant.
Trên thực tế, đây giống một dải lựa chọn hơn là hai ô tách biệt, và phần lớn quyết định quan trọng nằm ở cơ sở dữ liệu. Ở đầu dùng chung nhiều nhất, dữ liệu của mọi khách hàng nằm trong cùng các bảng, mỗi dòng có một cột tenant_id, và mọi truy vấn đều phải lọc theo cột đó. Cách này rẻ nhất để vận hành và cũng dễ sai nhất. Tiến thêm một bước, mỗi khách hàng có một schema riêng bên trong một cơ sở dữ liệu dùng chung: tách biệt gọn gàng hơn, sao lưu được theo từng khách hàng, nhưng mỗi lần chuyển đổi cấu trúc dữ liệu (migration) phải chạy lại cho từng schema. Xa hơn nữa, ứng dụng vẫn dùng chung nhưng mỗi khách hàng có cơ sở dữ liệu riêng: đây là điểm cân bằng được nhiều đội chọn, vì dữ liệu được cô lập chắc chắn mà không phải chạy cả một bộ hạ tầng cho từng khách hàng. Ở đầu bên kia là single-tenant hoàn toàn, cả ứng dụng lẫn cơ sở dữ liệu đều dành riêng. Tài liệu hướng dẫn SaaS của AWS gọi hai đầu này là silo và pool, còn kiểu kết hợp cả hai là bridge. Đây là bộ từ vựng khá tiện khi bạn cần giải thích kiến trúc của mình cho đội bảo mật của khách hàng.
So sánh nhanh single-tenant và multi-tenant#
| Single-tenant (multi-instance) | Multi-tenant (single instance) | |
|---|---|---|
| Cô lập dữ liệu | Hạ tầng riêng cho từng khách hàng | Do code hoặc chính sách của cơ sở dữ liệu đảm bảo |
| Chi phí cho mỗi khách hàng | Cao: mỗi khách hàng một bộ hạ tầng đầy đủ | Thấp: mọi người dùng chung một bộ |
| Vận hành | Triển khai, nâng cấp, bảng giám sát nhân theo số khách hàng | Mỗi thứ chỉ một |
| Hàng xóm ồn ào (noisy neighbour) | Không có | Một tenant dùng nặng có thể làm chậm tất cả |
| Tùy biến | Theo từng khách hàng, thậm chí khác phiên bản | Chủ yếu dừng ở cấu hình và feature flag |
| Phát hành | Lần lượt từng khách hàng | Tất cả cùng lúc |
| Một lần triển khai lỗi ảnh hưởng tới | Một khách hàng | Mọi khách hàng |
| Đánh giá bảo mật và nơi lưu trữ dữ liệu | Dễ trả lời | Phải giải thích nhiều hơn |
Khi nào nên chọn cái nào#
Hãy chọn multi-tenant, trừ khi có lý do cụ thể khiến bạn phải làm khác. Đây là lựa chọn mặc định đúng khi bạn có nhiều khách hàng, mỗi khách trả không nhiều, khi người dùng tự đăng ký, và khi đội của bạn đủ nhỏ để ba mươi môi trường là quá sức.
Single-tenant chỉ đáng với chi phí vận hành của nó trong vài trường hợp. Khách hàng trong ngành y tế, tài chính và cơ quan nhà nước thường yêu cầu dữ liệu phải được tách biệt về mặt vật lý, hoặc phải lưu ở một quốc gia nhất định, và trong buổi đánh giá bảo mật, một instance riêng là câu trả lời đơn giản và trung thực nhất. Nếu chỉ có vài khách hàng lớn ký hợp đồng doanh nghiệp, họ đủ sức gánh chi phí cho một bộ hạ tầng riêng, và nhiều khi họ còn mặc định là phải có. Còn khi khách hàng cần lịch phát hành riêng, cần giữ cố định một phiên bản, hay cần tùy biến sâu, thì việc tách instance không còn là gánh nặng nữa mà trở thành một phần của thứ bạn đang bán.
Cuối cùng thì phần lớn các đội dùng cả hai: một sản phẩm multi-tenant cho gần như tất cả khách hàng, và instance riêng như một gói doanh nghiệp cho số ít khách hàng cần và sẵn sàng trả tiền. Nếu ngay từ ngày đầu bản multi-tenant đã gắn tenant ID ở mọi nơi, thì sau này tách một khách hàng sang cơ sở dữ liệu riêng chỉ là một lần migration, không phải viết lại từ đầu.
Làm cho việc cô lập trong multi-tenant khó bị phá vỡ hơn#
Lỗi đáng sợ nhất trong multi-tenant là một truy vấn quên lọc theo tenant. Nó không báo lỗi. Nó chỉ lặng lẽ trả về dữ liệu của khách hàng khác, và code review không phải lúc nào cũng bắt được. Nếu bạn dùng Postgres, tính năng bảo mật mức dòng (row-level security, RLS) sẽ chuyển việc kiểm tra này xuống cơ sở dữ liệu, nên kể cả truy vấn quên lọc thì cũng chỉ thấy được các dòng của khách hàng hiện tại:
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.tenant_id')::uuid);Sau đó, mỗi request đặt tenant của mình bên trong một transaction bằng SET LOCAL app.tenant_id = '…'. Nhờ vậy, một kết nối trong connection pool sẽ không mang ID của khách hàng trước sang request tiếp theo, và request nào quên đặt tenant sẽ nhận về lỗi thay vì dữ liệu. Có hai điểm cần lưu ý: ứng dụng phải kết nối bằng một role không sở hữu bảng, vì chủ sở hữu bảng sẽ bỏ qua các chính sách này trừ khi bạn thêm FORCE ROW LEVEL SECURITY; và tuyệt đối không kết nối bằng superuser, vì superuser luôn bỏ qua chúng.
Nếu chọn single-tenant thì bắt đầu từ đâu?#
Nếu bạn quyết định đi theo single-tenant, công cụ sẽ quyết định mọi thứ còn nằm trong tầm kiểm soát, hay trở thành một thảm họa diễn ra từ từ. Không ai quản lý thủ công nổi ba mươi môi trường cả.
Phần cốt lõi là container kết hợp với hạ tầng dưới dạng mã (infrastructure as code, IaC). Docker đóng gói ứng dụng giống hệt nhau cho mọi tenant, Kubernetes lo việc triển khai và co giãn cả đống instance đó, còn Terraform, Pulumi hay AWS CloudFormation cho phép bạn định nghĩa toàn bộ hạ tầng của một tenant bằng mã. Nhờ vậy, dựng thêm khách hàng thứ 31 chỉ tốn một pull request, chứ không phải mất trắng một cuối tuần. Nếu hệ thống của bạn chủ yếu chạy trên máy ảo (VM), các công cụ quản lý cấu hình như Ansible, Chef hay Puppet cũng đóng vai trò tương tự. Ở mô hình này, một quy trình CI/CD vững chắc (Jenkins, GitLab CI/CD) còn quan trọng hơn bình thường, vì mỗi bản phát hành phải được triển khai tới mọi instance mà không cần ai ngồi canh.
Ngoài những thứ đó ra thì tùy vào đặc thù sản phẩm của bạn. Kiến trúc phi máy chủ (serverless) như AWS Lambda hay Azure Functions có thể loại bỏ hẳn tầng máy chủ cho một số tải xử lý. Các dịch vụ cơ sở dữ liệu được quản lý sẵn (managed) như Amazon RDS hay Azure SQL Database giúp bạn khỏi phải tự tay quản trị từng cơ sở dữ liệu cho từng tenant. Giám sát bằng Prometheus, Grafana hay New Relic thì không còn là tùy chọn khi bạn đã có cả một dàn instance, vì bạn muốn biết instance nào đang trục trặc trước khi khách hàng gọi tới báo. Về định danh, một dịch vụ như Auth0 hay Okta sẽ lo phần quản lý người dùng và phân quyền truy cập, để bạn khỏi phải làm lại cho từng tenant. Dù bạn dùng ngôn ngữ nào thì chắc cũng có sẵn framework hay thư viện hướng SaaS giúp bạn bớt được phần nào đống việc “đi dây” này, và nhà cung cấp đám mây lớn nào (AWS, Azure, Google Cloud Platform) cũng sẵn lòng vận hành hết cho bạn.
Đừng dùng hết cả danh sách này ngay từ ngày đầu. Hãy bắt đầu với container và IaC, bổ sung giám sát và quản lý định danh, truy cập (IAM) khi số tenant tăng dần, rồi để những khó khăn thực tế quyết định phần còn lại.
Nói ngắn gọn#
Single-tenant, hay multi-instance, nghĩa là mỗi khách hàng có một bản ứng dụng và cơ sở dữ liệu riêng: cô lập tốt, kiểm soát được theo từng khách hàng, đổi lại bạn phải vận hành cả một loạt instance. Multi-tenant nghĩa là mọi người dùng chung một bản: rẻ và đơn giản, còn việc cô lập do code đảm bảo. Phần lớn sản phẩm nên bắt đầu với multi-tenant, gắn tenant ID ở mọi nơi và để cơ sở dữ liệu thực thi việc lọc, rồi cung cấp instance riêng cho số ít khách hàng cần và sẵn sàng trả tiền. Nếu bạn thật sự chạy single-tenant, thứ giúp mọi việc còn trong tầm kiểm soát là container và hạ tầng dưới dạng code (IaC).
