programing

멀티 테넌트 장고 애플리케이션: 요청당 데이터베이스 연결 변경?

newstyles 2023. 10. 31. 20:35

멀티 테넌트 장고 애플리케이션: 요청당 데이터베이스 연결 변경?

데이터베이스 수준의 격리를 사용하여 멀티 테넌트 장고 애플리케이션을 구축하려고 시도한 다른 사람들의 작업 코드와 아이디어를 찾고 있습니다.

업데이트/해결 방법:새로운 오픈소스 프로젝트인 sedjango-db-multitenant에서 이 문제를 해결했습니다.

제 목표는 요청 호스트명이나 요청 경로(예를 들어, gunicorn과 같은 WSGI 프론트엔드)를 기반으로 요청이 하나의 앱 서버(WSGI 프론트엔드)로 들어오면서 다중화하는 것입니다.foo.example.com/데이터베이스를 사용하도록 Django 연결을 설정합니다.foo,그리고.bar.example.com/데이터베이스 사용bar).

선례

장고의 멀티 테넌시(Multi-tenancy)를 위한 몇 가지 기존 솔루션에 대해 알고 있습니다.

  1. django-tenant-schemas:이것은 제가 원하는 것에 매우 가깝습니다. 당신이 미들웨어를 가장 높은 우선순위로 설치하면, 그것은 다음을 전송합니다.SET search_pathDB에 명령합니다.유감스럽게도 그것은 Postgres 특정적이고 나는 MySQL에 갇혀있습니다.
  2. 장고- simple-다중화제:여기서의 전략은 모든 모델에 "임차인" 외부 키를 추가하고 모든 애플리케이션 비즈니스 로직을 조정하여 키를 끄도록 하는 것입니다.기본적으로 각 행은 다음과 같이 색인화됩니다.(id, tenant_id)보다는(id)이 에 들지 않았습니다.더 만들고 어려울 수 하지 않기 때문입니다 이 접근 방식은 여러 가지 이유로 시도해 보았지만 마음에 들지 않았습니다. 애플리케이션을 더 복잡하게 만들고 버그를 찾기 어려울 수 있으며 데이터베이스 수준의 격리를 제공하지 않기 때문입니다.
  3. 테넌트당 적절한 db}이(가) 포함된 {app 서버, django 설정 파일 하나입니다.일명 가난한 남자의 다중 거주지(실제로는 부자의 것, 관련된 자원을 고려할 때).테넌트마다 새로운 앱 서버를 설치하고 싶지 않으며 확장성을 위해 모든 앱 서버가 클라이언트에 대한 요청을 발송할 수 있기를 바랍니다.

아이디어들

지금까지 나의 가장 좋은 생각은 다음과 같은 것을 하는 것입니다.django-tenant-schemas: 첫번째 미들웨어에서, 잡습니다.django.db.connection스키마가 아닌 데이터베이스 선택사항을 처리합니다.저는 이것이 풀링/지속적인 연결의 관점에서 무엇을 의미하는지 생각해보지 못했습니다.

제가 추구했던 또 다른 막다른 길은 테넌트별 테이블 접두사였습니다.동적이어야 한다는 것은 차치하고, 글로벌 테이블 접두사조차 장고에서는 쉽게 얻을 수 없습니다(거절된 티켓 5000 등 참조).

마지막으로, Django 다중 데이터베이스 지원을 통해 인스턴스 유형 및 읽기/쓰기 모드를 기반으로 여러 개의 명명된 데이터베이스와 그 중 mux를 정의할 수 있습니다.요청 단위로 db를 선택할 수 있는 설비가 없기 때문에 도움이 되지 않습니다.

질문.

비슷한 일을 해본 사람이 있습니까?그렇다면 어떻게 구현했습니까?

포인트 1에 가장 가까운 유사한 작업을 수행했지만 미들웨어를 사용하여 기본 연결을 설정하는 대신 장고 데이터베이스 라우터를 사용합니다.이렇게 하면 각 요청에 필요한 경우 응용프로그램 로직에서 여러 데이터베이스를 사용할 수 있습니다.모든 쿼리에 적합한 데이터베이스를 선택하는 것은 애플리케이션 로직에 달려 있으며, 이는 이 접근 방식의 큰 단점입니다.

이 설정을 사용하면 모든 데이터베이스가settings.DATABASES, 고객 간에 공유될 수 있는 데이터베이스를 포함합니다.고객별 모델은 특정 앱 레이블이 있는 장고 앱에 각각 배치됩니다.

다음 클래스는 모든 고객 데이터베이스에 존재하는 모델을 정의합니다.

class MyModel(Model):
    ....
    class Meta:
        app_label = 'customer_records'
        managed = False

데이터베이스 라우터는settings.DATABASE_ROUTERS체인을 통해 데이터베이스 요청 라우팅app_label, 이와 같은 것(완전한 예는 아님):

class AppLabelRouter(object):
    def get_customer_db(self, model):
        # Route models belonging to 'myapp' to the 'shared_db' database, irrespective
        # of customer.
        if model._meta.app_label == 'myapp':
            return 'shared_db'
        if model._meta.app_label == 'customer_records':
            customer_db = thread_local_data.current_customer_db()
            if customer_db is not None:
                return customer_db

            raise Exception("No customer database selected")
        return None

    def db_for_read(self, model, **hints):
        return self.get_customer_db(model, **hints)

    def db_for_write(self, model, **hints):
        return self.get_customer_db(model, **hints)

이 라우터의 특별한 부분은thread_local_data.current_customer_db()가 현재 call을 라우터가 실행되기 전에, 호출자/응용 프로그램이 현재 고객 db를 설정해야 합니다.thread_local_data 관리자를 하여 현재 고객 수 Python context manager를 사용하여 현재 고객 데이터베이스를 푸시/팝핑할 수 있습니다.

이 모든 것이 구성된 상태에서 애플리케이션 코드는 다음과 같이 보입니다.UseCustomerDatabase현재 고객 데이터베이스 이름을 푸시/팝핑하는 컨텍스트 관리자입니다.thread_local_data하도록thread_local_data.current_customer_db()라우터가 최종적으로 적중되면 올바른 데이터베이스 이름을 반환합니다.

class MyView(DetailView):
    def get_object(self):
        db_name = determine_customer_db_to_use(self.request) 
        with UseCustomerDatabase(db_name):
            return MyModel.object.get(pk=1)

이것은 이미 상당히 복잡한 설정입니다.효과는 있지만 장점과 단점을 요약해 보겠습니다.

이점

  • 데이터베이스 선택이 유동적입니다.이를 통해 고객별 데이터베이스와 공유 데이터베이스를 모두 요청에 사용할 수 있으며, 하나의 쿼리에서 여러 데이터베이스를 사용할 수 있습니다.
  • 데이터베이스 선택이 명확합니다(장점인지 단점인지 확실하지 않음).고객 데이터베이스에 도달하는 쿼리를 실행하려고 하지만 응용프로그램이 쿼리를 선택하지 않은 경우 프로그래밍 오류를 나타내는 예외가 발생합니다.
  • 데이터베이스 라우터를 사용하면 다른 데이터베이스가 다른 호스트에 존재할 수 있습니다.USE db;단일 연결을 통해 모든 데이터베이스에 액세스할 수 있음을 가정하는 문장입니다.

단점들

  • 설정이 복잡하고, 작동하는 데는 꽤 많은 계층이 포함되어 있습니다.
  • 스레드 로컬 데이터의 필요성과 사용법은 불분명합니다.
  • 보기에는 데이터베이스 선택 코드가 흩어져 있습니다.미들웨어가 기본 데이터베이스를 선택하는 것과 같은 방식으로 요청 매개변수를 기반으로 데이터베이스를 자동으로 선택하기 위해 클래스 기반 보기를 사용하여 이를 추상화할 수 있습니다.
  • 데이터베이스를 선택하기 위한 컨텍스트 관리자는 쿼리를 평가할 때 컨텍스트 관리자가 계속 활성화되도록 쿼리 세트를 둘러싸야 합니다.

제안들

유연한 데이터베이스 접근을 원한다면 장고의 데이터베이스 라우터를 사용하는 것이 좋습니다.요청 매개변수에 따라 연결에 사용할 기본 데이터베이스를 자동으로 설정하는 미들웨어 또는 뷰 믹스를 사용합니다.라우터가 타격을 받았을 때 라우팅할 데이터베이스를 알 수 있도록 기본 데이터베이스를 저장하기 위해 스레드 로컬 데이터에 의존해야 할 수도 있습니다.이를 통해 Django는 데이터베이스에 대한 기존의 지속적인 연결(필요한 경우 다른 호스트에 상주할 수 있음)을 사용할 수 있으며 요청에 설정된 라우팅을 기반으로 사용할 데이터베이스를 선택합니다.

이 접근 방식은 기본값이 아닌 다른 데이터베이스를 선택하는 함수를 사용하여 필요한 경우 쿼리에 대한 데이터베이스를 재정의할 수 있다는 장점도 있습니다.

참고로, 저는 제 첫 번째 아이디어의 변형을 실행하기로 결정했습니다: 이슈 a.USE <dbname>조기 요청 미들웨어로CASH 접두사도 동일하게 설정합니다.

소규모 프로덕션 사이트에서 요청 호스트를 기반으로 Redis 데이터베이스에서 테넌트 이름을 검색하고 있습니다.지금까지는 결과에 상당히 만족합니다.

저는 이 프로젝트를 (hopefully에 귀중한) github 프로젝트로 전환했습니다. https://github.com/mik3y/django-db-multitenant

서브 도메인 등에서 데이터베이스 이름을 결정한 다음 각 요청에 대해 데이터베이스 커서에 USE 문을 실행하는 간단한 미들웨어를 만들 수 있습니다.장고-테넌트-스킴 코드를 보면, 그것이 본질적으로 하고 있는 것입니다.psycopg2를 서브클래싱하고 USE와 동등한 포스트그레스인 "set search_path XXX"를 발행하고 있습니다.테넌트를 관리하고 생성할 수 있는 모델도 만들 수 있지만 장고 테넌트 스키마의 많은 부분을 다시 작성하게 됩니다.

MySQL에서 스키마(db 이름)를 전환하는 데 성능이나 리소스 패널티가 없어야 합니다.연결에 대한 세션 파라미터를 설정하는 것일 뿐입니다.

언급URL : https://stackoverflow.com/questions/16721051/multi-tenant-django-applications-altering-database-connection-per-request