Tổng quan về Multi-Tenant Database
Thư viện django-multitenant cung cấp giải pháp cho phép ứng dụng Python/Django hoạt động với cơ sở dữ liệu phân tán nhiều tenant như Postgres+Citus. Thư viện này tự động thêm ngữ cảnh tenant vào các truy vấn, giúp database có thể định tuyến hiệu quả đến đúng node xử lý.
Trong kiến trúc multi-tenant, có ba phương pháp chính:
- Tạo riêng database cho từng tenant
- Tạo riêng schema cho từng tenant
- Cho tất cả tenant chia sẻ chung một table
Thư viện django-multitenant áp dụng phương pháp thứ ba - tất cả tenant cùng sử dụng chung một bảng. Các model/table liên quan đến tenant cần có một cột tenant_id để xác định tenant sở hữu dữ liệu.
Tài liệu tham khảo
- https://www.citusdata.com/blog/2016/10/03/designing-your-saas-database-for-high-scalability/
- https://www.citusdata.com/blog/2017/03/09/multi-tenant-sharding-tutorial/
- https://www.citusdata.com/blog/2017/06/02/scaling-complex-sql-transactions/
Mã nguồn dự án
https://github.com/citusdata/django-multitenant
Cài đặt
pip install --no-cache-dir django_multitenant
Yêu cầu hệ thống
| Python | Django |
|---|---|
| 3.X | 2.2 |
| 3.X | 3.2 |
| 3.X | 4.0 |
Hướng dẫn sử dụng
Để tích hợp thư viện vào dự án, bạn có thể sử dụng Mixins hoặc kế thừa từ custom model class.
Phương pháp 1: Kế thừa TenantModel
Bước 1: Import thư viện trong file chứa models:
from django_multitenant.fields import *
from django_multitenant.models import *
Bước 2: Tất cả models cần kế thừa từ TenantModel. Định nghĩa biến tĩnh tenant_id để chỉ định cột tenant. Các foreign key trong model cần sử dụng TenantForeignKey thay vì models.ForeignKey.
Ví dụ:
class Organization(TenantModel):
tenant_id = 'id'
name = models.CharField(max_length=100)
domain = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
class Employee(TenantModel):
organization = models.ForeignKey(Organization)
tenant_id = 'organization_id'
full_name = models.CharField(max_length=200)
email = models.EmailField()
position = models.CharField(max_length=100)
class Meta(object):
unique_together = ["id", "organization"]
class Project(TenantModel):
organization = models.ForeignKey(Organization)
tenant_id = 'organization_id'
owner = TenantForeignKey(Employee)
title = models.CharField(max_length=255)
description = models.TextField()
status = models.CharField(max_length=50, default='active')
Phương pháp 2: Sử dụng Mixins
Bước 1: Import mixin trong file models:
from django_multitenant.mixins import *
Bước 2: Models cần kết hợp TenantModelMixin với models.Model. Định nghĩa biến tenant_id và sử dụng TenantForeignKey.
Ví dụ:
class EmployeeManager(TenantManagerMixin, models.Manager):
pass
class Employee(TenantModelMixin, models.Model):
organization = models.ForeignKey(Organization)
tenant_id = 'organization_id'
full_name = models.CharField(max_length=200)
email = models.EmailField()
objects = EmployeeManager()
class Meta(object):
unique_together = ["id", "organization"]
class ProjectManager(TenantManagerMixin, models.Manager):
pass
class Project(TenantModelMixin, models.Model):
organization = models.ForeignKey(Organization)
tenant_id = 'organization_id'
owner = TenantForeignKey(Employee)
title = models.CharField(max_length=255)
status = models.CharField(max_length=50)
objects = ProjectManager()
Cấu hình Database Layer
Để đảm bảo composite foreign key với tenant_id được tạo ở mức database, cần cập nhật settings.py:
'default': {
'ENGINE': 'django_multitenant.backends.postgresql',
'NAME': 'your_database_name',
'USER': 'your_user',
'PASSWORD': 'your_password',
'HOST': 'localhost',
'PORT': '5432',
}
Cấu hình Tenant Context
Phương pháp 1: Sử dụng Middleware
Tạo middleware để tự động set/unset tenant cho mỗi request:
from django_multitenant.utils import set_current_tenant
class TenantContextMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.user and not request.user.is_anonymous:
# Lấy organization từ user đang đăng nhập
set_current_tenant(request.user.profile.organization)
return self.get_response(request)
Cập nhật settings.py để thêm middleware:
MIDDLEWARE = [
# ...
# các middleware hiện có
# ...
'yourapp.middleware.TenantContextMiddleware'
]
Phương pháp 2: Sử dụng API trực tiếp
Trong các view cụ thể, có thể set tenant thủ công bằng set_current_tenant(t):
from django_multitenant.utils import set_current_tenant
def my_view(request, org_id):
org = Organization.objects.get(id=org_id)
set_current_tenant(org)
# Các truy vấn sau sẽ tự động được lọc theo organization
Các API được hỗ trợ
Thư viện hỗ trợ hầu hết các API của Model.objects.* và tự động thêm tenant_id vào các truy vấn:
# Thiết lập tenant context
org = Organization.objects.first()
set_current_tenant(org)
# Các truy vấn sau sẽ tự động có tenant filter
# Lấy queryset
Employee.objects.get_queryset()
# Join các bảng
Project.objects.filter(id=1).filter(organization__name='Công ty ABC').filter(owner__full_name='Nguyễn Văn A')
# Cập nhật dữ liệu
Project.objects.filter(id=1).update(status='completed')
# Lưu dữ liệu mới
emp = Employee(5, 1, 'Trần Thị B', 'tranthib@example.com', 'Developer')
emp.save()
# Aggregate
Employee.objects.count()
Employee.objects.filter(organization__name='Công ty ABC').count()
# Subquery
Project.objects.filter(title='Dự án X')
Employee.objects.filter(full_name='Trần Thị B')
projects = Project.objects.filter(title='Dự án X')
Employee.objects.filter(project__in=projects)
Tài liệu bổ sung
- Hướng dẫn triển khai Django-Multitenant với PostgreSQL+Citus
- Citus官方示例 - Xử lý dữ liệu time-series với PostgreSQL phân tán