Hướng Dẫn Triển Khai Django-Multitenant Với PostgreSQL+Citus

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

PythonDjango
3.X2.2
3.X3.2
3.X4.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

Thẻ: Django python PostgreSQL Citus multi-tenant

Đăng vào ngày 8 tháng 6 lúc 16:04