5 Stimmen

Django Daten Sharding

Ich habe meine Anwendung erfolgreich über mehrere Datenbanken laufen lassen, indem ich das auf Modellen basierende Routing-Schema verwendet habe. D.h. Modell A läuft auf DB A und Modell B läuft auf DB B. Jetzt muss ich meine Daten splitten. Ich schaue mir die Dokumentation an und habe Schwierigkeiten, herauszufinden, wie das geht, da dasselbe Modell auf mehreren Datenbankservern vorhanden sein muss. Ich möchte eine Markierung haben, die besagt, dass die DB für NEUE Mitglieder jetzt die Datenbank X ist und dass die Mitglieder X-Y auf der Datenbank N leben usw.

Wie kann ich das tun? Ist es mit **Hinweise, scheint dies unzureichend dokumentiert zu mir.

6voto

MiniQuark Punkte 43252

En hints soll Ihrem Datenbank-Router helfen zu entscheiden, wo er seine Daten lesen oder schreiben soll. Er kann sich mit zukünftigen Versionen von Python weiterentwickeln, aber im Moment gibt es nur eine Art von Hinweisen, die vom Django-Framework gegeben werden können, und das ist der instance an dem es arbeitet.

Ich habe diesen sehr einfachen Datenbank-Router geschrieben, um zu sehen, was Django kann:

# routers.py
import logging
logger = logging.getLogger("my_project")

class DebugRouter(object):
    """A debugging router"""

    def db_for_read(self, model, **hints):
        logger.debug("db_for_read %s" % repr((model, hints)))
        return None

    def db_for_write(self, model, **hints):
        logger.debug("db_for_write %s" % repr((model, hints)))
        return None

    def allow_relation(self, obj1, obj2, **hints):
        logger.debug("allow_relation %s" % repr((obj1, obj2, hints)))
        return None

    def allow_syncdb(self, db, model):
        logger.debug("allow_syncdb %s" % repr((db, model)))
        return None

Sie deklarieren dies in settings.py :

DATABASE_ROUTERS = ["my_project.routers.DebugRouter"]

Vergewissern Sie sich, dass die Protokollierung richtig konfiguriert ist, um Debug-Ausgaben auszugeben (z. B. auf stderr):

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        [...some other handlers...] 
        'stderr': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler'
        }
    },
    'loggers': {
        [...some other loggers...]
        'my_project': {
            'handlers': ['stderr'],
            'level': 'DEBUG',
            'propagate': True,
        },
    }
}

Dann können Sie eine Django-Shell öffnen und ein paar Anfragen testen, um zu sehen, welche Daten Ihr Router erhält:

$ ./manage.py shell
[...]
>>> from my_project.my_app.models import User
>>> User.objects.get(pk = 1234)
db_for_read (<class 'my_project.my_app.models.User'>, {})
<User: User object>
>>> user = User.objects.create(name = "Arthur", title = "King")
db_for_write (<class 'my_project.my_app.models.User'>, {})
>>> user.name = "Kong"
>>> user.save()
db_for_write (<class 'my_project.my_app.models.User'>, {'instance':
              <User: User object>})
>>>

Wie Sie sehen können, ist die hints ist immer leer, wenn noch keine Instanz (im Speicher) vorhanden ist. Sie können also keine Router verwenden, wenn Sie Abfrageparameter (z. B. die ID des Objekts) benötigen, um zu bestimmen, welche Datenbank abgefragt werden soll. Es könnte in Zukunft möglich sein, wenn Django die Query- oder Queryset-Objekte in der hints dict.

Um also Ihre Frage zu beantworten, würde ich sagen, dass Sie zunächst einen benutzerdefinierten Manager erstellen müssen, wie von Aaron Merriam vorgeschlagen. Aber das Überschreiben nur des create Methode reicht nicht aus, denn Sie müssen auch in der Lage sein, ein Objekt aus der entsprechenden Datenbank zu holen. Etwas wie dies könnte funktionieren (noch nicht getestet):

class CustomManager(models.Manager)
    def self.find_database_alias(self, pk):
        return #... implement the logic to determine the shard from the pk

    def self.new_object_database_alias(self):
        return #... database alias for a new object

    def get(self, *args, **kargs):
        pk = kargs.get("pk")
        if pk is None:
            raise Exception("Sharded table: you must provide the primary key")
        db_alias = self.find_database_alias(pk)
        qs = self.get_query_set().using(db_alias)
        return qs.get(*args, **kargs)

    def create(self, *args, **kwargs):
        db_alias = self.new_object_database_alias()
        qs = super(CustomManager, self).using(db_alias)
        return qs.create(*args, **kwargs)

class ModelA(models.Model):
    objects = CustomManager()

Prost

3voto

Piper Merriam Punkte 2414

mit sollten Sie angeben können, welche Datenbank Sie verwenden möchten.

Die Unterklassifizierung der create-Methode könnte das bewirken, was Sie erreichen wollen.

class CustomManager(models.Manager)
    def get_query_set(self):
        return super(CustomManager, self).get_query_set()

    def create(self, *args, **kwargs):
        return super(CustomManager, self).using('OTHER_DB').create(*args, **kwargs)

class ModelA(models.Model):
    objects = CustomManager()

Ich habe dies nicht getestet und weiß daher nicht, ob man ein "create" an das Ende eines "using" anhängen kann.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X