Djangocon EU: scaling the database, using multiple databases with Django - Jake Howard

Tags: django, djangocon

(One of my summaries of the 2026 Djangocon EU in Athens).

(Jake is the author of Django tasks, mentioned before at the conference. He’s also in this week’s Django podcast.)

Scaling: horizontal and vertical. Horizontal: more machines. Vertical: more resources per machine. Horizontal scaling is often easier. Just start more django processes on more servers. But… often there’s still only one database server.

Databases aren’t typical applications. They bring enormous speed and features, but have a big requirement: there’s only one database server that’s allowed to write to its data.

One thing you can do is sharding: splitting up your data over multiple database servers. You need to think hard about this, as moving data afterwards is hard. And there are foreign key problems.

You can improve scaling in a simpler way, with syncing: a primary with multiple replicas. Writing only happens to one databases, this syncs to the replicas (often in milliseconds). When you want Django to talk to them, you need a load balancer (pb_bouncer and so) in front of the replicas for the read traffic. Note: you don’t want to use the primary for reading for performance reasons.

Django allows multiple databases:

DATABASES = {
    "default": {.....},
    "replica": {.....},
}

You can switch betwee databases by doing MyModel.objects.using("replica").all(). You can also use database routers. It is just a class with at most 4 methods (if a method is missing, the default is used):

class MyRouter:

    def db_for_read(...):
        return "replica"

    def db_for_write(...):
        return "default"

The other methods are allow_relation() and allow_migrate(). You don’t want to migrate on replica databases. And relations, that’s for when you’re doing sharding. When sharding, the read/write methods can tell which database hosts which of your models.

There are some corner cases, for instance with transactions. Inside a transaction, writes go to the primary, reads go to the replicas. But the primaries are only updated after the transaction is finished.

Another corner case: read and write, now what about User.objects.get_or_create(username="jake")? get=read, create=write. Django does the safe thing and uses the primary.

There are more corner cases: he showed us two Django bug reports he reported.

Update: someone mentioned https://pypi.org/project/django-multidb-router/ as possible database router.

https://reinout.vanrees.org/images/2026/ottbergen7.jpeg

Unrelated photo explanation: a recent trip to the “Modellbundesbahn” in Germany. Part of the locomotive shed in Ottbergen in model, with lots of goods traffic and series 44 locos.