Kiến trúc · nhiều khách hàng, một appArchitecture · many customers, one app
App luôn dùng chung. Câu hỏi là đặt bức tường ở đâu.The app is always shared. The question is where to put the wall.
Multi-tenancy là nhiều công ty (tenant) dùng chung một ứng dụng đang chạy, nhưng không ai thấy được dữ liệu của ai. Ba mô hình chỉ khác nhau ở chỗ bức tường giữa các tenant nằm ở từng dòng, từng database, hay từng bản triển khai.Multi-tenancy means many companies (tenants) share one running application, yet none can see another's data. The three models differ only in where the wall between tenants sits: on each row, each database, or each deployment.
Cùng hai tenant: Acme và Beta. Nét đen đậm là bức tường thật; nét đứt là bức tường chỉ tồn tại trong code của app.Same two tenants: Acme and Beta. A thick black line is a real wall; a dashed line is a wall that exists only in the app's code.
01 · DB dùng chung01 · Shared DBDòng của hai tenant nằm xen kẽ trong cùng bảng. Tường là WHERE tenant_id = ?.Both tenants' rows interleave in the same table. The wall is WHERE tenant_id = ?.02 · Database-per-tenantApp dùng chung, mỗi tenant một database. Tường là ranh giới database.Shared app, one database per tenant. The wall is the database boundary.03 · Instance-per-tenantMỗi tenant một bản app và một database. Tường chạy suốt từ trên xuống dưới.Each tenant gets its own app copy and database. The wall runs top to bottom.
Thử làm rò rỉ dữ liệuTry to leak some data
Chọn mô hình, đăng nhập bằng một tenant, rồi bật một bug. Mỗi mô hình có đúng một kiểu lỗi chết người — và mức độ thiết kế chặn được lỗi đó cũng khác nhau.Pick a model, sign in as a tenant, then switch on a bug. Each model has one deadly mistake — and a different amount of protection against it by design.
student
class
thuộc tenantowner
Chọn mô hình nàoWhich model to pick
Nguyên tắc kinh nghiệm: dữ liệu càng nhạy cảm và số tenant càng ít, càng dịch về phía tách riêng (01 → 02 → 03).Rule of thumb: the more sensitive the data and the fewer the tenants, the further you move toward separation (01 → 02 → 03).
Q1
Luật hoặc hợp đồng bắt buộc hạ tầng tách biệt vật lý, và khách hàng chịu trả tiền?Do law or contract require physically separate infrastructure, and will the customer pay for it?
→ 03 Instance
Q2
Dữ liệu nhạy cảm (sức khoẻ, tài chính) và số tenant ở mức hàng chục–hàng trăm?Sensitive data (health, finance) and tens to hundreds of tenants?
→ 02 DB-per-tenant
Q3
Hàng nghìn tenant nhỏ, chi phí là ưu tiên số một, dữ liệu rủi ro thấp?Thousands of small tenants, cost comes first, low-risk data?
→ 01 DB chung (+ RLS)→ 01 Shared DB (+ RLS)
Không bắt buộc chọn một: nhiều nền tảng để phần lớn khách ở mô hình rẻ, gói cao cấp có DB hoặc instance riêng.You don't have to pick just one: many platforms keep most customers on the cheap model and sell a dedicated DB or instance as a premium tier.
Tra cứu từng mô hìnhEach model in detail
Ví dụ xuyên suốt: một app quản lý trường học phục vụ nhiều trường, mỗi trường là một tenant.Running example: a school-management app serving many schools, each school a tenant.
01
Database dùng chung (theo dòng)Shared database (row-level)
rẻ nhấtcheapest
Dữ liệu của mọi tenant nằm trong cùng các bảng. Cột tenant_id đánh dấu ai sở hữu từng dòng, và app lọc theo nó ở mọi truy vấn.Every tenant's data sits in the same tables. A tenant_id column marks each row's owner, and the app filters on it in every query.
CREATE TABLE students (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL
REFERENCES tenants(id),
full_name TEXT NOT NULL
);
CREATE INDEX idx_students_tenant
ON students(tenant_id);
-- Safety net: the DB enforces the filter
ALTER TABLE students
ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON students
USING (tenant_id =
current_setting('app.current_tenant')::BIGINT);
Hình dung: một phòng lớn chung, đồ đạc dán nhãn. Quản lý quên dán nhãn một lần là đồ của bạn lẫn vào đống của người khác.Analogy: one big shared room, everything labelled. Forget one label and your things end up in someone else's pile.
ĐượcGains
Rẻ nhất: một database cho tất cảCheapest: one database for everyone
Một schema, một lần migration, một bản sao lưuOne schema, one migration, one backup
Mở rộng tới hàng nghìn tenant trở lênScales to thousands of tenants and more
MấtCosts
Cô lập yếu nhất: một bug = một vụ rò rỉ dữ liệuWeakest isolation: one bug = a data breach
Noisy neighbour: một tenant nặng làm chậm tất cảNoisy neighbour: one heavy tenant slows everyone
Sao lưu/khôi phục riêng một tenant khá rắc rốiPer-tenant backup/restore is awkward
Đánh đổiTrade-offChọn DB dùng chung là đổi mức cô lập lấy chi phí thấp nhất và quy mô lớn nhất.Choosing a shared DB trades isolation for the lowest cost and the largest scale.
Phù hợp choGood forNền tảng blog miễn phí, SaaS freemiumFree blogging platforms, freemium SaaS
Rủi ro chínhMain riskQuên điều kiện lọcForgetting the filter
02
Database-per-tenant
app trường học dùng mô hình nàythe school app's choice
App dùng chung, nhưng mỗi tenant có database riêng. Một database danh bạ nhỏ (control plane) ánh xạ địa chỉ web → database; mỗi request, app tra danh bạ rồi chỉ kết nối tới database của tenant đó.The app is shared, but each tenant gets its own database. A small directory database (the control plane) maps web address → database; per request, the app looks it up and connects only to that tenant's database.
acme.schoolapp.example.com → school_acme
beta.schoolapp.example.com → school_beta
-- schoolapp_control (the only shared data)
CREATE TABLE tenant_hostnames (
hostname TEXT PRIMARY KEY,
tenant_id UUID NOT NULL REFERENCES tenants(id)
);
-- school_acme: no tenant_id anywhere
CREATE TABLE students (
id BIGSERIAL PRIMARY KEY,
full_name TEXT NOT NULL
);
SELECT * FROM students; -- nothing to forget
Hình dung: các căn hộ khoá riêng trong một toà nhà chung. Đi nhầm phòng là không thể về mặt vật lý.Analogy: separate locked apartments in one shared building. Wandering into the wrong room is physically impossible.
ĐượcGains
Cô lập chặt từ thiết kế: quên lọc không thể làm lộ dữ liệu tenant khácStrong isolation by design: a forgotten filter cannot expose another tenant
Sao lưu, khôi phục, chuyển server từng tenant dễEasy per-tenant backup, restore, or move to a bigger server
Tải nặng của một tenant được khoanh vùng tốt hơnOne tenant's heavy load is better contained
MấtCosts
Migration chạy một lần cho mỗi tenant — cần tự động hoá bằng CLIMigrations run once per tenant — automate with a CLI
Mỗi DB tốn kết nối/bộ nhớ: một server chứa có hạn, phải tính dung lượng (sizing)Each DB costs connections/memory: a server holds a limited number, so do the sizing
Nhiều thành phần phải vận hành hơn mô hình 01More moving parts than model 01
Đánh đổiTrade-offChọn database-per-tenant là đổi công vận hành nhiều database lấy việc rò rỉ không thể xảy ra từ thiết kế, chứ không chỉ ngăn được nhờ code cẩn thận.Choosing database-per-tenant trades the work of running many databases for leaks being impossible by design, not merely prevented by careful code.
Phù hợp choGood forDữ liệu nhạy cảm, hàng chục–hàng trăm tenantSensitive data, tens to hundreds of tenants
Rủi ro chínhMain riskKết nối nhầm databaseConnecting to the wrong database
03
Instance-per-tenant (silo)
đắt nhấtmost expensive
Mỗi tenant có một bản sao hoàn toàn riêng của cả app lẫn database — đôi khi cả server riêng. Gần như không còn "multi". Không cần schema đặc biệt: mỗi tenant chạy schema bình thường cho một khách hàng.Each tenant gets a completely separate copy of the app and the database — sometimes separate servers too. Barely "multi" at all. No special schema: each tenant runs an ordinary single-customer schema.
Hình dung: mỗi tenant một ngôi nhà riêng. Riêng tư tối đa, chi phí tối đa.Analogy: each tenant gets their own house. Maximum privacy, maximum cost.
ĐượcGains
Cô lập tuyệt đối, tách biệt vật lýTotal, physical isolation
Tuỳ biến sâu; đáp ứng quy định chính phủ/đấu thầuHeavy customisation; meets strict government/procurement rules
MấtCosts
Đắt nhất: chi phí nhân lên theo số tenantMost expensive: cost multiplies per tenant
Mỗi bản cập nhật phải triển khai lên mọi bản saoEvery update must be deployed to every copy
Đánh đổiTrade-offChọn instance-per-tenant là đổi chi phí và công bảo trì nhân theo số tenant lấy cô lập vật lý tuyệt đối.Choosing instance-per-tenant trades cost and maintenance that multiply per tenant for total physical isolation.
Phù hợp choGood forVài khách hàng doanh nghiệp lớn/chính phủ chịu trả tiềnA few large enterprise/government customers who will pay
Rủi ro chínhMain riskCác bản sao lệch version nhauCopies drifting apart in version
So sánh song songSide-by-side comparison
Yếu tốFactor
01 DB chung01 Shared DB
02 DB-per-tenant
03 Instance-per-tenant
Mức cô lậpIsolation
Yếu (logic trong app)Weak (app logic)
Chặt (DB riêng)Strong (separate DBs)
Chặt nhất (tách mọi thứ)Strongest (separate everything)
Chi phíCost
Thấp nhấtLowest
Trung bìnhMedium
Cao nhấtHighest
Rủi ro rò rỉLeak risk
Cao (một bug là lộ)High (one bug leaks)
Rất thấpVery low
Gần như khôngAlmost none
Số tenant tối đaMax tenants
Hàng nghìnThousands
Hàng chục–hàng trămTens–hundreds
Vài tenantA handful
Sao lưu từng tenantPer-tenant backup
KhóHard
DễEasy
DễEasy
Công bảo trìMaintenance effort
Thấp nhấtLowest
Trung bìnhMedium
Cao nhấtHighest
Tuỳ biến theo tenantPer-tenant customisation
KhóHard
Một phầnSome
Toàn bộTotal
tenant_id trên bảng nghiệp vụtenant_id on business tables
Có, ở mọi bảngYes, everywhere
KhôngNo
KhôngNo
Migration
Chạy một lầnRun once
Một lần cho mỗi tenantOnce per tenant
Một lần cho mỗi bản triển khaiOnce per deployment
Phù hợp nhất choBest for
Nhiều tenant nhỏ/rủi ro thấpMany small/low-risk tenants
Dữ liệu nhạy cảm, số lượng vừaSensitive data, moderate count
Ít khách hàng lớn/khắt kheFew large/strict customers
Case study
Slack
Mô hình 01 · DB dùng chungModel 01 · shared DB
Hàng triệu workspace — database-per-tenant không quản lý nổi. Dựa vào hạ tầng chung với tenant ID và nhiều lớp bảo vệ nội bộ; mức cô lập phụ thuộc vào code và kiểm thử cực kỳ chắc chắn.Millions of workspaces — database-per-tenant would be unmanageable. It leans on shared infrastructure with tenant IDs and heavy internal safeguards; isolation depends on rock-solid code and testing.
App trường họcThe school app
Mô hình 02 · DB riêngModel 02 · DB per tenant
Dữ liệu học sinh (điểm số, chuyên cần, liên lạc phụ huynh) rất nhạy cảm, số trường vừa phải. Mỗi tenant một database là điểm cân bằng: cô lập chặt mà không tốn tiền của các bản triển khai riêng.Student data (grades, attendance, parent contacts) is highly sensitive, and the school count is moderate. A database per tenant is the sweet spot: strong isolation without paying for separate deployments.
Sở giáo dục nhà nướcState education department
Mô hình 03 · instance riêngModel 03 · own instance
Luật có thể bắt buộc dữ liệu nằm trên hạ tầng tách biệt vật lý. Chi phí thêm chấp nhận được vì yêu cầu tuân thủ không thể thương lượng.The law may require data on physically separate infrastructure. The extra cost is acceptable because compliance is non-negotiable.
Mô hình laiHybrid
Trộn nhiều mô hìnhMixing models
Phần lớn khách ở mô hình rẻ, gói cao cấp có DB hoặc instance riêng. App trường học thực chất là vậy: mặc định 02, 03 khi khách yêu cầu.Most customers on the cheap model, a premium tier with a dedicated DB or instance. The school app is effectively this: 02 by default, 03 on request.
Lưới an toàn chồng lớpLayered safety nets
Ngay trong mô hình 02, app trường học không dựa vào một lớp duy nhất. Đây cũng là thứ chặn bug "control plane trả nhầm database" trong phần thử ở trên.Even within model 02, the school app doesn't rely on a single layer. These are also what stop the "control plane returns the wrong database" bug in the simulator above.
1Mỗi tenant một databaseA database per tenantKhông có bảng dùng chung để rò rỉ.No shared tables to leak from.
2Tra tenant trước tiênTenant lookup firstApp tra địa chỉ web → tenant trước mọi thứ khác trong request.Web address → tenant is resolved before anything else in the request.
3Khoá đăng nhập riêngPer-tenant login keysToken của Acme không dùng được cho Beta.An Acme token won't work for Beta.
4Quyền databaseDatabase permissionsUser DB của mỗi tenant chỉ chạm được database của chính nó, kể cả khi app bị chiếm quyền.Each tenant's DB user can only touch its own database, even if the app is compromised.
Sai lầm hay gặpCommon mistakes
Sai lầmMistake
Vì sao saiWhy it's wrong
Sửa thế nàoFix
Nghĩ multi-tenancy = chỉ tenant_idThinking multi-tenancy = only tenant_id
Cả ba mô hình đều là multi-tenancy; chúng chỉ đặt tường ở chỗ khác nhauAll three models are multi-tenancy; they put the wall in different places
Chọn chỗ đặt tường theo độ nhạy cảm dữ liệu và số tenantPlace the wall by data sensitivity and tenant count
Mô hình 01 chỉ dựa vào code nhớ lọcModel 01 relying on code to remember the filter
Quên một lần là lộ dữ liệu tenant khácForget once and another tenant's data leaks
Bật Postgres RLS để database tự áp điều kiện lọcEnable Postgres RLS so the database enforces the filter
Không index tenant_idNot indexing tenant_id
Mọi truy vấn đều lọc theo cột nàyEvery query filters on it
Index tenant_id trên mọi bảng nghiệp vụIndex tenant_id on every business table
Chạy migration tay cho từng DBRunning migrations by hand per DB
Mô hình 02 có N database cùng cấu trúc; sót một cái là lệch schemaModel 02 has N identically-shaped databases; miss one and schemas drift
Một lệnh CLI chạy migration cho mọi tenantOne CLI command migrates every tenant
Thêm tenant mà không tính dung lượng (sizing)Adding tenants without sizing
Mỗi DB tốn kết nối và bộ nhớ; server có trầnEach DB costs connections and memory; servers have a ceiling
Tính trước mỗi server chứa được bao nhiêu tenantWork out how many tenants fit per server up front
Ba câu cần nhớThree things to remember
Multi-tenancy luôn là nhiều công ty dùng chung một app; các mô hình chỉ khác nhau ở cách tách dữ liệu.Multi-tenancy is always many companies sharing one app; the models differ only in how the data is separated.
DB dùng chung + tenant_id là mô hình 01 — hợp lệ, nhưng không phải dạng duy nhất, và cô lập chỉ chắc bằng code của bạn.Shared DB + tenant_id is model 01 — valid, but not the only form, and its isolation is only as solid as your code.
Đánh đổi luôn là chi phí đổi lấy mức cô lập: dùng chung thì rẻ hơn, tách riêng thì an toàn hơn.The trade-off is always cost versus isolation: shared is cheaper, separated is safer.