Home/ Blog/ Article

Single database or database-per-tenant: choosing multi-tenant architecture for a SaaS product

·

If you’re building a SaaS product, one decision made in the first few months of engineering will quietly shape every migration, every support ticket, and every enterprise sales conversation for years afterwards: how you separate one customer’s data from another’s. Get it right early and it barely gets mentioned again. Get it wrong and you end up rebuilding the data layer under a live product, which is one of the more expensive things a small engineering team can be asked to do.

This isn’t a question with a universally correct answer. It’s a trade-off between isolation, cost and operational complexity, and the right answer depends on who your customers are and how many of them you expect to have. Here’s how to think about it properly, rather than copying whatever a scaling blog post from a much larger company recommends.

The three common patterns

Most multi-tenant SaaS products land on one of three approaches, or a hybrid of them.

  • Shared database, shared schema. Every tenant’s rows live in the same tables, distinguished by a tenant_id column. This is the cheapest to run and the simplest to migrate, since a schema change happens once for everyone.
  • Shared database, schema-per-tenant. One database, but each tenant gets its own schema or namespace. Better logical separation than the shared-schema model, without the operational overhead of separate database instances.
  • Database-per-tenant. Each customer gets a fully separate database. Maximum isolation, at the cost of running and migrating what is effectively N copies of your schema.
  • None of these is “the modern one” or “the legacy one”. Large, well-run SaaS platforms exist on all three, and plenty run a hybrid: shared infrastructure for most customers, dedicated databases for the handful who pay for it or require it contractually.

    What actually drives the decision

    Ignore the architecture diagrams for a moment. These are the questions that should settle it.

    Who bears the blast radius of a bug?

    In a shared schema, a query with a missing tenant_id filter, a bad index, or a runaway job doesn’t just affect one customer. It affects all of them, at once, in production. That’s the real cost of the shared model, and it’s an engineering discipline cost as much as an infrastructure one: every query, every background job, every cache key has to be tenant-scoped correctly, every time, forever. Database-per-tenant makes that mistake much harder to make, because the blast radius of a bug is naturally capped at one customer.

    What does your compliance story need to say?

    If you’re selling into sectors where customers ask “is our data physically separated from other tenants” during procurement — healthcare, financial services, public sector, larger enterprise generally — a shared-schema answer can lose the deal regardless of how good your row-level security actually is. That’s a sales and legal reality, not just a technical one, and it’s worth finding out early whether your target market cares.

    How many tenants, and how uneven are they?

    Database-per-tenant works comfortably for hundreds of customers. It gets awkward at tens of thousands, because connection pooling, backup scheduling, monitoring and schema migrations all have to scale with tenant count, not just with data volume. Conversely, if you expect a small number of large, high-usage tenants alongside many small ones, a shared database can leave your biggest customers competing for the same resources as your smallest, which is exactly the “noisy neighbour” problem that tends to surface first as a support complaint, not an architecture review.

    What’s your actual migration cadence?

    A schema change against one shared database is one deployment. Against a thousand tenant databases, it’s a rollout — with retries, partial failures, and a need to know which tenants are on which schema version at any given moment. That operational machinery has to be built and maintained by someone, and for a small team it’s easy to underestimate how much of it there is.

    The trap: this decision is hard to reverse

    The reason this matters more than most early architecture choices is asymmetry. Moving from database-per-tenant to a shared model is inconvenient but mechanical: you consolidate. Moving from a shared schema to per-tenant isolation, once you have real customer data and real uptime expectations, is a live data migration with no good moment to do it — you’re extracting tenants one at a time out of tables that were never designed to be split, usually while the product keeps running.

    The practical answer for most early-stage SaaS products isn’t to pick database-per-tenant defensively “just in case”. It’s to build the shared-schema version properly — tenant_id on every table, enforced at the query layer rather than trusted to application code, indexed correctly, with row-level security if your database supports it — so that splitting out a tenant later is a bounded, well-understood piece of work rather than an open-ended one. That single discipline, tenant scoping enforced structurally rather than by convention, is worth more than picking the “safer-sounding” architecture up front.

    Where a hybrid earns its complexity

    The pattern that tends to age best for products with a mixed customer base — some small self-serve accounts, some larger contracted ones — is a shared database as the default with the ability to move a specific tenant onto dedicated infrastructure when there’s a concrete reason: a compliance requirement in the contract, or a usage profile that’s genuinely disruptive to others. That requires the application layer to be agnostic about where a tenant’s data actually lives, which is a design decision you have to make deliberately from the start. Retrofitting it is close to the same amount of work as the full migration you were trying to avoid.

    This is also where the decision connects to less glamorous operational questions that are easy to skip in the early design: how backups and restores work per tenant, how you’d delete a single customer’s data completely on request, and how you monitor per-tenant resource usage so a noisy neighbour shows up on a dashboard before it shows up as a complaint. A multi-tenancy model that only looks good in the schema diagram, and hasn’t been thought through for these operational cases, tends to cause the most pain roughly a year after launch, once there’s data and customers depending on it.

    A checklist before you commit

    • Do any target customers or sectors require physical data separation as a contractual or regulatory condition, not just a preference?
    • Is tenant_id enforced structurally (row-level security, query middleware) or only by developer discipline?
    • What happens to every other tenant if one tenant’s usage spikes tenfold overnight?
    • Can you run a schema migration across every tenant unattended, with partial-failure handling, or does someone have to babysit it?
    • If a single customer asked you to delete all their data today, how long would that take and how confident are you nothing else was affected?
    • Does the application layer know how to find a tenant’s data, or does it assume one database for everyone?
    • If you can’t answer most of these with confidence, that’s the actual gap — not the choice of pattern itself. The architecture diagram is the easy part; the operational discipline around it is what determines whether the decision still looks sound in two years.

Filed under: