Search code examples
pythondjangodatabasemultiple-databases

Using Two Databases in Django (one read-only)


I am using two databases, one to read data from and then write data to another. Below is my Router class and my changes to settings but am very new to this concept, can someone with more experience in this area confirm that it should work or help make edits?

The application is named "myapp", the read-only database I have named "readonly_db" and the database I want to write to is the "default" database.

    class MyappRouter:
        def db_for_read(self, model, **hints):
            if model._meta.app_label == 'myapp':
                return 'readonly_db'
            return None
        def db_for_write(self, model, **hints):
            if model._meta.app_label == 'myapp'
                return 'default'
            return None

        def allow_relation(self, obj1, obj2, **hints):
            if obj1._meta.app_label == obj2._meta.app_label:
                return True
            else:
                return False
            return None

        def allow_migrate(self, db, **hints):
            if db == 'default':
                return True
            elif db == 'readonly_db':
                return False
            return None
    DATABASE_ROUTERS = ['<path to>.myapprouter.py']
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': 'myapp' ,
            'USER': 'USER',
            'PASSWORD': 'PASSWORD',
            'HOST': 'LOCALHOST',
            'PORT': '5432'
        }
        'readonly_db': {
            'ENGINE': 'django_postgres_readonly'
            'NAME': 'READONLY'
            'USER': 'USER'
            'PASSWORD': 'PASSWORD'
            'HOST': 'LOCALHOST'
            'PORT': '5432'
    }

Solution

  • You're going the right way. I'll share the solution I have currently running.

    settings.py

    USE_REPLICA = eval_env_as_boolean("USE_REPLICA", False)
    
    DATABASES = {
        "default": {
            "ENGINE": os.getenv("DB_ENGINE", "django.db.backends.sqlite3"),
            "NAME": os.getenv("DB_DATABASE", os.path.join(BASE_DIR, "db.sqlite3")),
            "USER": os.getenv("DB_USER"),
            "HOST": os.getenv("DB_HOST"),
            "PORT": os.getenv("DB_PORT"),
            "PASSWORD": os.getenv("DB_PASSWORD"),
        }
    }
    
    DATABASES["default"]["CONN_MAX_AGE"] = int(os.getenv("DB_CONN_MAX_AGE", 0))  # type: ignore
    
    if USE_REPLICA:
        DATABASES["replica"] = {
            "ENGINE": os.getenv("DB_ENGINE_REPLICA"),
            "NAME": os.getenv("DB_DATABASE_REPLICA"),
            "USER": os.getenv("DB_USER_REPLICA"),
            "HOST": os.getenv("DB_HOST_REPLICA"),
            "PORT": os.getenv("DB_PORT_REPLICA"),
            "PASSWORD": os.getenv("DB_PASSWORD_REPLICA"),
        }
    
        DATABASES["replica"]["CONN_MAX_AGE"] = int(os.getenv("DEFAULT_DB_CONN_MAX_AGE_REPLICA", 0))  # type: ignore
        DATABASE_ROUTERS = ["my_app.setup.db_routing.DatabaseRouter"]
    
    

    db_routing.py

    class DatabaseRouter:
        """
        A router to control all database operations on models in the
        auth application.
        """
    
        def db_for_read(self, model, **hints):
            """
            Always read from REPLICA database
            """
            return "replica"
    
        def db_for_write(self, model, **hints):
            """
            Always write to DEFAULT database
            """
            return "default"
    
        def allow_relation(self, obj1, obj2, **hints):
            """
            Objects from REPLICA and DEFAULT are de same, then True always
            """
            return True
    
        def allow_migrate(self, db, app_label, model_name=None, **hints):
            """
            Only DEFAULT database
            """
            return db == "default"
    
    

    Basically you can simplify your router a bit

    UPDATE:

    USE_REPLICA exists only as an option so I can quickly disable the read database. Settings.py runs only at system startup, so the replica configuration is added when the system starts, but only if that variable exists.

    About the CONN_MAX_AGE you can see more here. And here too on checklist for production.