VISEN GROUP
Technology & Engineering

Kiến trúc Multi-Tenant cho B2B App: Phân tách dữ liệu an toàn mà không đội chi phí

October 5, 20264 min readUncategorized

Khi quy mô ứng dụng B2B tăng từ vài khách lên hàng trăm công ty, kiến trúc cơ sở dữ liệu sẽ đứng trước thế tiến thoái lưỡng nan: chọn an toàn tuyệt đối bằng cách cấp riêng database cho từng khách thì ví tiền cạn kiệt, còn nhét chung vào một bảng thì nguy cơ rò rỉ thông tin luôn rình rập.

Lối thoát chuẩn mực nhất nằm ở việc chuyển trách nhiệm cô lập dữ liệu từ code logic sang chính bộ máy database. Cùng điểm qua 3 mô hình Multi-Tenancy phổ biến và cách áp dụng Row-Level Security (RLS) để bảo vệ dữ liệu khách hàng tuyệt đối mà không làm đội chi phí hạ tầng.

1. Ba cấp độ phân tách dữ liệu trong ứng dụng B2B

Tùy vào quy mô và mức độ nhạy cảm của khách hàng, có 3 cách tiếp cận chính:

  • Database-per-Tenant (Cách ly vật lý): Mỗi khách hàng sở hữu một database riêng biệt. An toàn tuyệt đối, dễ sao lưu/khôi phục nhưng bảo trì di chuyển schema cực kỳ phức tạp và chi phí vận hành cao nhất.
  • Schema-per-Tenant (Cách ly logic theo Schema): Dùng chung một cụm database nhưng mỗi khách sở hữu một schema riêng (ví dụ trên PostgreSQL). Cân bằng tốt giữa chi phí và phân quyền, nhưng nếu vượt quá hàng ngàn schema thì hiệu năng hệ thống quản trị sẽ suy giảm.
  • Shared Database, Shared Schema + RLS (Cách ly theo hàng): Tất cả khách hàng lưu chung bảng dữ liệu, nhưng hệ quản trị tự động chặn quyền truy cập ở mức nhân (kernel) của database bằng cơ chế Row-Level Security. Đây là kiến trúc tối ưu nhất cho 95% ứng dụng SaaS hiện nay.

2. Thiết lập Row-Level Security (RLS) trên PostgreSQL / Supabase

Thay vì tin tưởng vào việc lập trình viên phải luôn nhớ thêm .where("tenant_id", currentTenant) trong từng hàm truy vấn backend, hãy để chính database từ chối trả về dữ liệu của khách khác.

Bước 1: Kích hoạt RLS cho bảng dữ liệu

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

Bước 2: Thiết lập Tenant Context trên mỗi kết nối

Khi backend nhận request từ người dùng, đọc JWT để lấy tenant_id và gán vào session của database:

SET LOCAL app.current_tenant_id = 'tenant_abc_123';

Bước 3: Tạo Policy cách ly tự động

CREATE POLICY tenant_isolation_policy ON orders
FOR ALL
USING (tenant_id = current_setting('app.current_tenant_id', true));

Kể từ lúc này, dù bạn viết lệnh SELECT * FROM orders mà không hề có mệnh đề WHERE, database cũng chỉ trả về đúng các dòng thuộc về tenant hiện tại. Bất kỳ truy vấn nào cố tình can thiệp sang tenant khác đều trả về danh sách rỗng.

3. Ba cạm bẫy sống còn khi vận hành hệ thống Multi-Tenant

  1. Rò rỉ qua Background Workers / Cron Jobs: Các tác vụ chạy ngầm định kỳ thường chạy bằng tài khoản Admin hệ thống (Service Role), bỏ qua RLS. Luôn phải chủ động inject tường minh tenant_id khi dispatch queue job.
  2. “Hàng xóm ồn ào” (Noisy Neighbor Problem): Một khách hàng lớn chạy báo cáo nặng khiến CPU database tăng vọt, làm app của các khách hàng khác bị treo. Giải pháp là thiết lập Connection Pool riêng biệt hoặc đặt giới hạn Rate Limit ở tầng API Gateway.
  3. Chiến lược di chuyển schema (Zero-Downtime Migration): Khi cập nhật thêm cột hoặc đổi kiểu dữ liệu, các bảng dùng chung phải luôn được chạy migration không khóa bảng (non-blocking) để tránh làm gián đoạn giao dịch của toàn bộ người dùng cùng lúc.

Lời kết

Xây dựng hệ thống Multi-Tenant bền vững không đòi hỏi hạ tầng đắt đỏ, mà nằm ở việc chọn đúng ranh giới cô lập dữ liệu ngay từ ngày đầu tiên. Cơ chế RLS kết hợp kiến trúc phân quyền chuẩn chỉ chính là nền móng để ứng dụng mở rộng từ 10 lên hàng ngàn doanh nghiệp mà không lo rò rỉ thông tin.

Leave a Reply

Your email address will not be published. Required fields are marked *