Handling Multiple Databases in Django

In the world of web development, managing data efficiently is paramount. Django, a high-level Python web framework, offers robust support for handling databases. However, as applications grow in complexity, the need to work with multiple databases may arise. Whether it's for sharding data, separating read and write operations, or integrating with legacy systems, Django provides powerful tools to tackle this challenge.

Head to the official documentation for multiple databases support here .

Understanding Multiple Databases in Django

Django's default behavior is to work with a single database. However, it also provides built-in support for managing multiple databases seamlessly. Each database connection is represented by a Django DATABASES setting in the project's settings file. These settings define various parameters such as the database engine, name, user, password, host, and port.

Configuring Multiple Databases

To configure multiple databases in Django, start by defining each database in the DATABASES setting. For example:

 DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'my_database',
        'USER': 'my_user',
        'PASSWORD': 'my_password',
        'HOST': 'localhost',
        'PORT': '3306',
    },
    'other_db': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'other_db.sqlite3',
    },
}

In this example, there are two database configurations: 'default' and 'other_db'. The 'default' database is MySQL, while 'other_db' uses SQLite. You can add as many database configurations as needed.

Migrating in individual databases

The migrate management command operates on one database at a time. By default, it operates on the default database, but by providing the --database option, you can tell it to synchronize a different database. So, to synchronize all models onto all databases in the first example above, you would need to call:

py manage.py migrate
py manage.py migrate --database=default

If you don’t want every application to be synchronized onto a particular database, you can define a database router that implements a policy constraining the availability of particular models.

If, as in the second example above, you’ve left the default database empty, you must provide a database name each time you run migrate. Omitting the database name would raise an error. For the second example:

py manage.py migrate --database=defaults
py manage.py migrate --database=other_db

Using other management commands

Most other django-admin commands that interact with the database operate in the same way as migrate – they only ever operate on one database at a time, using
--database to control the database used.

An exception to this rule is the makemigrations command. It validates the migration history in the databases to catch problems with the existing migration files (which could be caused by editing them) before creating new migrations. By default, it checks only the default database, but it consults the allow_migrate() method of routers if any are installed.

Database Routing

Once the databases are configured, Django needs to know which database to use for each query. This is where database routing comes into play. Django allows you to define custom database routers to control which database to use for specific models or operations.

class MyAppRouter:
    def db_for_read(self, model, **hints):
        if model._meta.app_label == 'my_app':
            return 'other_db'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == 'my_app':
            return 'other_db'
        return None

In this example, MyAppRouter routes read and write operations for models in the 'my_app' app to the 'other_db' database.

Database Routers

A database Router is a class that provides up to four methods:

db_for_read(model, **hints)

Suggest the database that should be used for read operations for objects of type model. If a database operation is able to provide any additional information that might assist in selecting a database, it will be provided in the hints dictionary. Details on valid hints are provided below.
Returns None if there is no suggestion.

db_for_write(model, **hints)

Suggest the database that should be used for writes of objects of type Model. If a database operation is able to provide any additional information that might assist in selecting a database, it will be provided in the hints dictionary. Details on valid hints are provided below.
Returns None if there is no suggestion.

allow_relation(obj1, obj2, **hints)

Return True if a relation between obj1 and obj2 should be allowed, False if the relation should be prevented, or None if the router has no opinion. This is purely a validation operation, used by foreign key and many to many operations to determine if a relation should be allowed between two objects.
If no router has an opinion (i.e. all routers return None), only relations within the same database are allowed.

allow_migrate(db, app_label, model_name=None, **hints)

Determine if the migration operation is allowed to run on the database with alias db. Return True if the operation should run, False if it shouldn’t run, or None if the router has no opinion. The app_label positional argument is the label of the application being migrated.

A router doesn’t have to provide all these methods – it may omit one or more of them. If one of the methods is omitted, Django will skip that router when performing the relevant check.

Hints

The hints received by the database router can be used to decide which database should receive a given request.

At present, the only hint that will be provided is instance, an object instance that is related to the read or write operation that is underway. This might be the instance that is being saved, or it might be an instance that is being added in a many-to-many relation. In some cases, no instance hint will be provided at all. The router checks for the existence of an instance hint, and determine if that hint should be used to alter routing behavior.

Using Multiple Databases in Views and Models

Once routing is configured, you can use multiple databases in your views and models. For example, to read from or write to a specific database, you can use Django's using method:

from my_app.models import MyModel

# Read from 'other_db'
objs = MyModel.objects.using('other_db').all()

# Write to 'other_db'
obj = MyModel(field1=value1, field2=value2)
obj.save(using='other_db')

Considerations and Best Practices

When working with multiple databases in Django, there are several considerations and best practices to keep in mind:

  1. Consistency: Ensure data consistency across databases by carefully designing your database schema and transaction management.

  2. Performance: Monitor and optimize database performance, especially when dealing with multiple databases.

  3. Security: Secure database connections and credentials to prevent unauthorized access.

  4. Testing: Test your application thoroughly, especially when using multiple databases, to ensure correct behavior in various scenarios.

  5. Documentation: Document your database configurations, routing logic, and any customizations for future reference.

Conclusion

Handling multiple databases in Django opens up a world of possibilities for building scalable and efficient web applications. By understanding how to configure multiple databases, implement database routing, and use them in views and models, you can take full advantage of Django's powerful features while effectively managing your data.

10
Subscribe to my newsletter

Read articles from Nischal lamichhane directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nischal lamichhane
Nischal lamichhane

There are always 2 ways to do something in Django. They are Django Master's WAY WRONG WAY