Django multidb: запись в несколько баз данных

С Django multidb довольно легко написать маршрутизатор, который запускает инфраструктуру master/slave. Но можно ли написать маршрутизатор, который записывает в несколько баз данных? Мой вариант использования — это набор проектов, работающих в одном домене. Чтобы избавить пользователей от регистрации/входа на каждый сайт, я хотел бы синхронизировать таблицы contrib.auth и contrib.sessions. Возможно ли это с Django multidb или мне следует изучить функции репликации системы баз данных (в моем случае MySQL)?


person Benjamin Wohlwend    schedule 28.10.2010    source источник


Ответы (4)


Сейчас я работаю над схемой шардинга Django.

Я посмотрел на роутер Django, но решил накатить свой.

некоторые мысли по вашей проблеме:

  1. Одна из идей состоит в том, чтобы использовать одну базу данных, а затем копировать соответствующие данные с помощью сигналов Django при пост-сохранении.

что-то типа--

import settings.databases as dbs_list

def post_save_function(UserModel):
     for db in dbs_list:
          UserModel.save(using=db,force_insert=True)

сохранение пользовательских объектов (по крайней мере, в модели с одной БД), по-видимому, сохраняет данные сеанса, аутентификации и т. д. под капотом с помощью различных магических действий, происходящих внутри django.contrib, поэтому есть шанс, что вам не придется заходить и выяснять имена и типы всех этих таблиц базы данных.

в поддержку возможности этой работы, клянусь, я недавно где-то читал (вероятно, в одном из сообщений в блоге Алекса Гейнора), что если объект имеет внешний ключ, Django попытается использовать ту же БД, в которой живет объект (по очевидным причинам в в свете того, как обычно работает Django).

  1. другая идея:

из примера на странице Django multiDB, на которую вы ссылались, мне интересно, будет ли работать что-то вроде следующего:

их пример кода:

def allow_syncdb(self, db, model):
        "Explicitly put all models on all databases."
        return True

возможная модификация:

def allow_syncdb (я, БД, модель):

    if isinstance(model,User):
         return True

    elif isinstance(model,Session):
         return True

    else:
         ''' something appropriate --
             whatever your sharding schema is for other objects '''

взглянув на него еще раз, этот код, вероятно, был бы более полезен в качестве функции "db_for_write". Но ты получил идею.

нет никаких сомнений в том, что вам придется добавить другие типы моделей, чтобы сделать эту работу (все материалы аутентификации, которые обширны).

удачи! надеюсь, что это полезно в некотором роде.

мне интересны ваши выводы и комментарии!

jb

person jsh    schedule 29.03.2011
comment
Привет, спасибо за ваш комментарий. Я больше не занимался этим конкретным вопросом, поскольку мы остановились на системе LDAP/Kerberos для обеспечения централизованной аутентификации и единого входа. - person Benjamin Wohlwend; 30.03.2011

я думаю, вам будет лучше внедрить службу SSO или OAuth

но если вы хотите синхронизировать пользователей вашей таблицы между двумя базами данных, и если вы используете свою собственную UserModel, вы можете сделать что-то вроде этого

class MyUser(models.Model):
    name = models.CharField(max_length=100)
    user = models.ForeignKey(User, unique=True)


    def save(self, ...): # ALL the signature
        super(MyUser, self).save(using='database_1')
        super(MyUser, self).save(using='database_2')

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

def save_multi_db(model_class):

    def save_wrapper(save_func): 
        def new_save(self, *args, **kws):
            super(model_class, self).save(using='database_1')
            super(model_class, self).save(using='database_1')
        return new_save

    func = getattr(model_class, 'save')
    setattr(model_class, 'save', save_wrapper(func)) 

    return save_wrapper

# and use it like this:

@save_multi_db
class MyUser(models.Model):
      ....

Надеюсь, это поможет :)

person mouad    schedule 28.10.2010
comment
Чтобы это работало, мне пришлось бы изменить исходный код Django (как я сказал в вопросе, таблицы, которые я хочу реплицировать, взяты из contrib.auth и contrib.sessions). Если это вообще возможно, я бы хотел не связываться с самим Django. - person Benjamin Wohlwend; 28.10.2010

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

Я попробовал ответ mouad, но не смог заставить работать декоратор классов... И мне кажется, что это решение не позволяет иметь пользовательские save() в моделях.

Более подходящий для моих нужд, я определил собственный универсальный класс и просто переопределил функцию save().

class MultiDbModel(models.Model):
    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        for dbname in settings.DATABASES:
            super(MultiDbModel, self).save(using=dbname)

А потом:

class MyObject(MultiDbModel):
    [...]
    def save(self, *args, **kwargs):
        [custom save]
        super(MyObject, self).save(args, kwargs)
person Stéphane    schedule 17.06.2011
comment
очень плохо, db_for_write в маршрутизаторе db не может возвращать несколько dbs для записи .. было бы круто. Другая проблема заключается в том, что filter().update() не будет запускать save(), поэтому вы можете использовать только save. - person radtek; 11.01.2021
comment
Это также не учитывает сохранение связанных объектов (FK) для объекта, который вы сохраняете, если он не существует во 2-й БД. - person radtek; 12.01.2021

Вот мой multidb_model.py, который, кажется, также обрабатывает внешние ключи. Обновит его, если есть ошибки, но как только вы наследуете этот класс в соответствии с ответом Stepahne, это будет обрабатывать сохранение внешнего ключа.

import logging

from django.db import models

DEFAULT_DB = 'default'  # main postgres host
MIRROR_COPY_DB = 'pg_mirror'  # a copy original db, i.e. if you want to move your data and keep it in sync


class MultiPgDbModel(models.Model):
    class Meta:
        abstract = True

    def save(self, *args, **kwarg):
        super(MultiPgDbModel, self).save(using=DEFAULT_DB)
        try:
            fields = [field for field in self._meta.fields if field.get_internal_type() == 'ForeignKey']

            for field in fields:
                getattr(self, field.name).save(using=MIRROR_COPY_DB)
            super(MultiPgDbModel, self).save(using=MIRROR_COPY_DB)
        except Exception as e:
            logging.exception(f"MultiPgDbModel.save unexpected error when saving to pg_mirror, object id "
                              f"{self.pk} of model {self._meta.model}. {e}")
person radtek    schedule 13.01.2021