Skip to content

Architecture · 4 min read

Django Multi-Tenancy: Shared vs Separate Database for SaaS

Shared schema or a database per tenant? How I choose a Django multi-tenant architecture for SaaS, with trade-offs, code and an isolation checklist.

By · · Updated

Glass database cylinders in a shared cluster and isolated groups, representing multi-tenant SaaS architecture

Most SaaS products start with a single customer. The interesting question shows up when you have hundreds, or thousands: do all of them share one database, or does each get their own?

I have spent the last year building multi-tenant systems in Django, including Savoir AI, a platform for school districts, schools, teachers and students. This is what I have learned about the two main approaches, and how I decide between them.

What multi-tenancy actually means

In a multi-tenant application, many customers (tenants) share the same running application and infrastructure, while their data stays isolated from each other. Instead of deploying a separate copy of your product for every customer, you deploy once and serve everyone.

The benefits are real:

  • Lower infrastructure cost. One deployment, one set of servers, one pipeline.
  • Easier deployments. You ship a fix once, not once per customer.
  • Faster onboarding. A new tenant is a row of data, not a new environment.
  • Better scalability. You scale the platform, not hundreds of tiny installs.

The cost is that isolation becomes your responsibility. That is where the architecture decision starts.

Option 1: Shared database, shared schema

Every tenant's rows live in the same tables, and each row carries a tenant identifier.

class TenantOwnedModel(models.Model):
    tenant = models.ForeignKey("tenants.Tenant", on_delete=models.CASCADE)

    class Meta:
        abstract = True


class Course(TenantOwnedModel):
    title = models.CharField(max_length=200)

Strengths

  • Lowest infrastructure cost and the simplest deployments.
  • One migration run updates every tenant.
  • Easy cross-tenant reporting for your own team.

The trade-off: every single query must respect the tenant boundary. One missing filter can expose data that should never be visible.

Option 2: Separate database (or schema) per tenant

Each tenant gets its own database, or its own PostgreSQL schema, and requests are routed to the right one.

Strengths

  • Much stronger isolation, which helps with strict compliance requirements.
  • Tenant-specific problems (a huge import, a runaway query) stay contained.
  • Backups and restores can be done per tenant.

The trade-off: higher operational complexity and cost. Migrations must run across many databases, connection management gets harder, and monitoring multiplies.

How I make the shared approach safe

If you go with a shared schema, do not rely on every developer remembering to write .filter(tenant=...). Make the safe path the default.

A tenant-aware manager is a good start:

class TenantManager(models.Manager):
    def get_queryset(self):
        tenant = get_current_tenant()  # set by middleware per request
        qs = super().get_queryset()
        return qs.filter(tenant=tenant) if tenant else qs.none()

Then:

  1. Resolve the tenant once, in middleware, from the subdomain, the authenticated user, or a header, and store it for the request.
  2. Fail closed. If there is no tenant in context, return nothing instead of everything.
  3. Test isolation directly. Write tests that create two tenants and assert that tenant A can never read tenant B's data through every endpoint.
  4. Review raw SQL and bulk operations, because they bypass managers.
  5. Scope background jobs the same way. Celery tasks and scheduled work do not have a request, so pass the tenant explicitly.

How I decide

I ask four questions:

  • What are the compliance requirements? Strict isolation or data residency pushes me toward separate databases.
  • How many tenants, and how different are they? Hundreds of similar tenants favour shared schema. A few very large, very different tenants may justify separation.
  • How much operational capacity do we have? Per-tenant databases are only cheap if you have automation for them.
  • What does a mistake cost? If a leak would be catastrophic, pay for stronger isolation.

So, is Django multi-tenancy worth it?

For most growing SaaS products, I would say yes. But only if you are prepared to treat tenant isolation as a first-class concern from day one, not something you add after the first customer complains.

Multi-tenancy is not just a scalability feature. It is a responsibility. Done well, it makes a platform far more efficient. Done poorly, it becomes one of the biggest risks in your system.

There is no universally correct choice. The mistake is not choosing shared or separate. The mistake is choosing without understanding the trade-offs.

If you are planning a multi-tenant product and want a second opinion on the architecture, get in touch.