Bài toán định hình: một hạ tầng, nghìn khách hàng, và hai lời hứa mâu thuẫn — “dữ liệu của bạn cách ly tuyệt đối” và “giá rẻ nhờ dùng chung”. Multi-tenancy là nghệ thuật giữ cả hai lời hứa cùng lúc — và tenant lớn nhất luôn lớn hơn tenant nhỏ nhất bốn bậc độ lớn (13.2 — luật lũy thừa, lần thứ n).

1. Business Requirement & Constraint

SaaS quản lý bán hàng đa kênh cho SME Việt Nam (đơn hàng, kho, khách hàng, báo cáo — kết nối sàn TMĐT): 5.000 tenant từ shop 2 người đến chuỗi bán lẻ 200 cửa hàng. Doanh thu theo gói (freemium → enterprise). Team 12 dev. Ràng buộc sống còn của mô hình SaaS: chi phí phục vụ mỗi tenant phải giảm theo quy mô — nếu mỗi khách mới cần thêm người vận hành, đó là công ty outsourcing đội lốt SaaS.

2. FR & NFR — NFR có thêm một chiều mới: “của ai”

FR: CRUD nghiệp vụ (đơn, kho, khách), đồng bộ sàn TMĐT (webhook + poll), báo cáo, phân quyền trong tenant, API mở cho tenant enterprise.

NFR — điểm đặc thù là mọi NFR đều phải gắn hậu tố per-tenant:

  • Cách ly dữ liệu tuyệt đối: tenant A không bao giờ thấy dữ liệu tenant B — lỗi cách ly là lỗi tồn vong, không phải bug thường (11.1 — IDOR phiên bản doanh nghiệp).
  • Cách ly hiệu năng: tenant chuỗi bán lẻ chạy báo cáo cuối tháng không được làm shop 2 người chậm — noisy neighbor là khiếu nại số một của mọi SaaS dùng chung.
  • Availability 99.9% chung; enterprise ký SLA riêng cao hơn (1.2 §2 — SLA lỏng hơn SLO).
  • Chi phí hạ tầng per-tenant phải đo được — để định giá gói đúng.

3. Quyết định trung tâm: mô hình tenancy — phổ, không phải nhị phân

flowchart LR
    A["POOL<br/>chung DB, chung bảng<br/>+ cột tenant_id mọi nơi"] --> B["BRIDGE<br/>chung DB instance,<br/>schema riêng mỗi tenant"] --> C["SILO<br/>DB (hoặc cả stack)<br/>riêng mỗi tenant"]
Pool (chung bảng)Bridge (schema riêng)Silo (DB riêng)
Chi phí per-tenantThấp nhất — mục tiêu SaaSTrung bìnhCao — chỉ enterprise gánh nổi
Cách ly dữ liệuBằng kỷ luật code (mọi query có tenant_id)Bằng namespace DBBằng vật lý — mạnh nhất
Noisy neighborNặng nhất — chung mọi tài nguyênChung instance, đỡ hơn chútGần như không
Vận hành 5.000 tenantMột schema, một migration5.000 lần migration — ác mộng có thật5.000 DB — chỉ khả thi khi tự động hóa tuyệt đối
Backup/restore một tenantKhó (lọc từ bảng chung — 3.2 §3 restore từng phần)DễDễ nhất

Chọn: pool làm mặc định + silo cho enterprise trả tiền — mô hình lai theo giá trị khách hàng, cùng triết lý VIP lane đã gặp ở 13.214.2 celebrity: 95% tenant ở pool (rẻ, một đường vận hành), top enterprise được silo (cách ly + SLA + backup riêng — và tính tiền cho điều đó). Bridge (schema-per-tenant) nghe hấp dẫn nhưng chết ở migration ×5.000 — cái bẫy phổ biến nhất của SaaS non trẻ.

Cách ly dữ liệu ở pool — ba lớp, vì kỷ luật code là không đủ:

  1. tenant_id trong mọi bảng + mọi query — ép bằng tầng repository (base class tự tiêm điều kiện — 11.1 §3, đúng bài chống IDOR).
  2. Row-Level Security của PostgreSQL (5.1): policy tenant_id = current_setting(...) — DB tự chặn cả khi code sót; lớp lưới cho lớp kỷ luật.
  3. Test cách ly tự động cho mọi endpoint (user tenant A gọi id của tenant B → 404) — trong CI, không phải trong niềm tin.

4. Cách ly hiệu năng — chống noisy neighbor theo tầng

  • Tầng API: rate limit + quota theo tenant (11.3 §3 — khóa theo tenant), ngưỡng theo gói — hàng rào đầu tiên và rẻ nhất.
  • Tầng tác vụ nặng: báo cáo, export, đồng bộ sàn — tất cả qua queue với fair scheduling (round-robin theo tenant, không FIFO thuần: FIFO nghĩa là tenant to xếp 10.000 job thì tenant nhỏ chờ sau — 14.4 §4, bài phân hạng quen thuộc nay theo chiều tenant) + giới hạn concurrent job per-tenant.
  • Tầng DB: query timeout per-request; báo cáo đọc từ replica/read model (12.8 CQRS) — OLTP của mọi tenant không gánh analytics của tenant nào; statement timeout chặn query cào của API mở.
  • Tầng đo: cost attribution — gắn tenant_id vào mọi metric/log (10.1 — nhưng cẩn thận cardinality: tenant là label chấp nhận được ở 5K, phải sample/gộp ở 500K) → biết tenant nào ăn bao nhiêu → định giá và ra quyết định “mời lên silo” bằng số liệu.

5. Trade-off trung tâm

Quyết địnhChọnGiá
Pool + silo laiRẻ cho số đông, cách ly cho VIPHai đường vận hành; di cư tenant pool→silo phải là quy trình đã tập (8.3 — dual-write/cutover, phiên bản tenant)
RLS + repository + testCách ly ba lớpRLS thêm ~vài % overhead query; đáng từng đồng
Fair queue thay FIFOTenant nhỏ không bao giờ đóiScheduler tự viết phức tạp hơn — hoặc dùng queue-per-tenant-class (3 hạng gói = 3 queue, đủ tốt và đơn giản hơn nhiều)
Một schema chung, feature flag theo góiMột codebase, một migrationBảng phải chứa mọi tính năng mọi gói — kỷ luật nullable/module (12.5); tùy biến sâu per-tenant là lời từ chối có giá trị (SaaS chết vì gật đầu mọi yêu cầu enterprise)
Tenant_id là shard key tương laiĐường scale sạch sẽ đã mở sẵnKhông có gì — đây là quyết định miễn phí hôm nay, vô giá năm sau (8.1 §3.2 — tenant là “chủ sở hữu tự nhiên” mẫu mực)

6. Production & Evolution

  • Metric đặc thù: mọi SLI cắt theo tenant (một tenant đau đớn chìm nghỉm trong trung bình toàn hệ — 1.2 §7); phân bố tài nguyên theo tenant (top 10 tenant ăn bao nhiêu %?); queue wait theo hạng gói; và onboarding time (tenant mới tự vận hành được sau bao lâu — chỉ số sản phẩm quyết định tăng trưởng SaaS).
  • Ngày xấu đặc thù: một tenant bị chiếm API key → cào/bơm dữ liệu điên cuồng (rate limit per-key cứu cả pool — 11.3); webhook từ sàn TMĐT dồn cục (Lazada/Shopee bắn lại sau sự cố của họ13.5 3rd-party + 13.3 backlog: queue + idempotency, nguyên bộ); migration schema trên bảng pool 500GB (5.2 §6 online migration).
  • Evolution: 50K tenant → shard pool theo tenant_id (đường đã mở); enterprise đòi region riêng (12.9 — data residency per-tenant); API mở lớn dần thành platform (webhook ra, app marketplace — chính mình thành “sàn” mà tenant tích hợp: vòng lặp đẹp của SaaS trưởng thành).

7. Bài học rút ra

  1. Tenancy là quyết định phổ, chọn theo giá trị khách hàng — pool cho số đông, silo bán như tính năng; và tránh cái bẫy ở giữa (schema-per-tenant) trông cân bằng nhưng gánh nhược của cả hai đầu.
  2. Cách ly phải nhiều lớp vì con người sót — repository + RLS + test là cùng một nguyên lý defense-in-depth của Phần 11, áp vào ranh giới quan trọng nhất của SaaS.
  3. Tenant_id là trục của mọi thứ — cách ly, đo lường, định giá, rate limit, sharding, residency: một cột dữ liệu mang cả mô hình kinh doanh. Thiết kế SaaS = thiết kế mọi tầng đều ý thức về tenant.

Tiếp theo: 14.9. AI Platform — GPU đắt và hai chế độ phục vụ