Django указывает, какую базу данных использовать для модуля

В моем проекте Django у меня есть несколько приложений, одно из них email_lists, и это приложение выполняет большое количество операций чтения данных из модели Customers. В моей производственной среде у меня есть две базы данных: default и read-replica. Я хотел бы, чтобы все запросы в конкретном модуле выполнялись к базе данных replica-set.

Я могу сделать это, если я явно укажу запросу сделать это:

def get_customers(self):
    if settings.ENV == 'production':
        customers = Customer.objects.using('read-replica').filter()
    else:
        customers = Customer.objects.filter()

но этот модуль имеет более 100 запросов к Customer и другим моделям. У меня также есть запросы к таким отношениям, как:

def get_value(self, customer):
    target_sessions = customer.sessions.filter(status='open')
    carts = Cart.objects.filter(session__in=target_sessions)

идея в том, что я хочу избежать написания:

if settings.ENV == 'production':
    instance = Model.objects.using('read-replica').filter()
else:
    instance = Model.objects.filter()

для каждого запроса. В моем проекте есть и другие места, которые нужно читать из базы данных default, поэтому это не может быть глобальная настройка. Мне просто нужен этот модуль или файл для чтения с помощью реплики.

Возможно ли это в Django, есть ли ярлыки?

Спасибо


person PepperoniPizza    schedule 31.03.2020    source источник


Ответы (2)


Вы можете прочитать на django маршрутизаторы базы данных для этого в Интернете также можно найти несколько хороших примеров, и они должны быть простыми.

--

Другим решением было бы изменить диспетчер моделей.

from django.db import models


class ReplicaRoutingManager(models.Manager):
    def get_queryset(self):
        queryset = super().get_queryset(self)

        if settings.ENV == 'production':
            return queryset.using('read-replica')

        return queryset


class Customer(models.Model):

    ...
    objects = models.Manager()
    replica_objects = ReplicaRoutingManager()

при этом вы можете просто использовать обычный Customer.objects.filter, а менеджер должен выполнить маршрутизацию.

Я по-прежнему предлагаю использовать решение для маршрутизатора базы данных и создать собственную логику в классе. Но если менеджер работает на вас, это нормально.

person ibaguio    schedule 31.03.2020
comment
Маршрутизатор базы данных получает модель, поэтому я могу направлять запросы модели к реплике, а атрибут hints содержит только экземпляр. Я не могу понять, как сделать маршрут модуля к реплике или все запросы, поступающие от приложения, для маршрутизации к реплике. - person PepperoniPizza; 31.03.2020
comment
Итак, вы говорите, что у вас есть модель, и когда запрос выполняется модулем A, вам нужно, чтобы он запрашивался по умолчанию, а когда запрос выполняется модулем B, вам нужно, чтобы он был перенаправлен на реплику? - person ibaguio; 31.03.2020
comment
У меня есть еще одна идея с использованием диспетчера моделей/набора запросов. дайте мне несколько минут, чтобы состряпать псевдокод - person ibaguio; 31.03.2020
comment
Да вроде все запросы из email_lists.views.py файла надо делать к реплике, все остальные запросы идут по дефолту. - person PepperoniPizza; 31.03.2020

Если вы хотите, чтобы все запросы в приложении email_lists запрашивали реплику чтения, то маршрутизатор — это то, что вам нужно. Если вам нужно запрашивать разные базы данных в одном приложении, то решение @ibaguio — это то, что вам нужно. Вот базовый пример маршрутизатора, аналогичный тому, что я использую:

проект/database_routers.py

MAP = {'some_app': 'default',
       'some_other_app': 'default',
       'email_lists': 'read-replica',}

class DatabaseRouter:

    def db_for_read(self, model, **hints):
        return MAP.get(model._meta.app_label, None)

    def db_for_write(self, model, **hints):
        return MAP.get(model._meta.app_label, None)

    def allow_relation(self, object_1, object_2, **hints):
        database_object_1 = MAP.get(object_1._meta.app_label)
        database_object_2 = MAP.get(object_2._meta.app_label)
        return database_object_1 == database_object_2

    def allow_migrate(self, db, app_label, model=None, **hints):
        return MAP.get(app_label, None)

В настройках.py:

DATABASE_ROUTERS = ['project.database_router.DatabaseRouter',]

Похоже, вам это нужно только в производстве, поэтому я думаю, вы могли бы добавить его условно:

if ENV == 'production':
    DATABASE_ROUTERS = ['project.database_router.DatabaseRouter',]
person Rob Vezina    schedule 31.03.2020