pedrotorres
pedrotorres

Reputation: 1232

Django Db routing

I am trying to run my Django application with two db's (1 master, 1 read replica). My problem is if I try to read right after a write the code explodes. For example:

OR

The code runs way faster than the read replica. And if the read operation uses the replica the code crashes, because it didn't update in time.

Is there any way to avoid this? For example, the db to read being chosen by request instead of by operation?

My Router is identical to Django's documentation:

import random

class PrimaryReplicaRouter(object):
    def db_for_read(self, model, **hints):
        """
        Reads go to a randomly-chosen replica.
        """
        return random.choice(['replica1', 'replica2'])

    def db_for_write(self, model, **hints):
        """
        Writes always go to primary.
        """
        return 'primary'

    def allow_relation(self, obj1, obj2, **hints):
        """
        Relations between objects are allowed if both objects are
        in the primary/replica pool.
        """
        db_list = ('primary', 'replica1', 'replica2')
        if obj1._state.db in db_list and obj2._state.db in db_list:
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        All non-auth models end up in this pool.
        """
        return True

Upvotes: 9

Views: 2111

Answers (3)

Hevlastka
Hevlastka

Reputation: 1948

Depending on the size of the data and the application I'd tackle this with either of the following methods:

  1. Database pinning:

Extend your database router to allow pinning functions to specific databases. For example:

from customrouter.pinning import use_master

@use_master
def save_and_fetch_foo():
    ...

A good example of that can be seen in django-multidb-router. Of course you could just use this package as well.

  1. Use a model manager to route queries to specific databases.

    class MyManager(models.Manager):
        def get_queryset(self):
            qs = CustomQuerySet(self.model)
            if self._db is not None:
                qs = qs.using(self._db)
            return qs
    
  2. Write a middleware that'd route your requests to master/slave automatically. Basically same as the pinning method but you wouldn't specify when to run GET requests against master.

Upvotes: 3

sachin
sachin

Reputation: 379

IN master replica conf the new data will take few millisecond to replicate the data on all other replica server/database.

so whenever u tried to read after write it wont gives you correct result.

Instead of reading from replica you can use master to read immediately after write by using using('primary') keyword with your get query.

Upvotes: 1

pedrotorres
pedrotorres

Reputation: 1232

Solved it with :

class Model(models.Model): 
    objects = models.Manager()  -> objects only access master
    sobjects = ReplicasManager() -> sobjects access either master and replicas

    class Meta: 
        abstract = True  -> so django doesn't create a table

make every model extend this one instead of models.Model, and then use objects or sobjects whether I want to access only master or if want to access either master or replicas

Upvotes: 3

Related Questions