Viktar Patotski Viktar Patotski · · Architecture  · 6 min read

Tenant Data Isolation: It Is a Stack, Not a Switch

Row-Level Security is one layer, not the whole answer. Real tenant isolation is defense in depth across the data, the API, and the keys, so that no single mistake exposes another customer. Here are the layers and where each one earns its place.

Row-Level Security is one layer, not the whole answer. Real tenant isolation is defense in depth across the data, the API, and the keys, so that no single mistake exposes another customer. Here are the layers and where each one earns its place.

TL;DR - “Are tenants isolated?” is the wrong question, because isolation is not one switch. It is a stack of layers, and you want enough of them that no single mistake leaks one customer’s data to another:

  • Data layer: a tenant_id on every row, in every cache key, in every storage path, with Row-Level Security as the database-enforced backstop.
  • Compute and API layer: per-tenant rate limits and quotas, so one tenant cannot starve the shared pool. This is your noisy-neighbor control.
  • Encryption layer: per-tenant encryption keys for your most sensitive tenants, so even a full database compromise does not expose everyone.
  • The nuclear option: silo the tenants whose blast-radius or compliance profile a shared environment cannot satisfy.

No single layer is enough. Defense in depth means a forgotten filter, a misconfigured cache, or a leaked key each gets caught by another layer.

Isolation is a spectrum, not a binary

Teams ask “is our SaaS isolated?” and want a yes. But a shared multi-tenant system is isolated to a degree, across several dimensions, and the honest answer is “to this level, enforced by these mechanisms.” Treating it as a single boolean is how gaps hide: you enable Row-Level Security, check the isolation box, and never notice that your cache keys, your file storage, or your API rate limits have no tenant awareness at all.

The mature way to think about it is defense in depth: multiple independent layers, so that when one fails (and one eventually will), another still contains the breach.

The layers

Diagram of tenant isolation as concentric defense-in-depth layers. The outermost layer is the API and compute layer with per-tenant rate limits and quotas. Inside it is the data layer with tenant_id in every query, cache key, and storage path, backed by Row-Level Security. Inside that is the encryption layer with per-tenant keys for sensitive tenants. At the center, for the highest-risk tenants, is the silo: a dedicated database. A caption notes that a failure in any one layer is caught by the next.

Data layer

The foundation. Every tenant-scoped row carries a tenant_id, and that id has to follow the data everywhere it goes, not just in the primary table:

  • Queries: filter by tenant_id, and enforce it with Row-Level Security so the database guarantees it rather than trusting every query. (The full RLS pattern is its own post: Postgres Row-Level Security for multi-tenancy.)
  • Cache keys: a Redis or in-memory cache key without a tenant prefix is a cross-tenant leak waiting to happen. One tenant’s cached result served to another is just as bad as a bad SQL query. Prefix every key with the tenant.
  • Storage paths: files in S3 or on disk go under a tenant-scoped prefix, and access is checked against the requesting tenant. A predictable object URL without an authorization check is a classic leak.

The rule of thumb: anywhere data is stored or keyed, the tenant_id is part of the key, and every index leads with it.

Compute and API layer

Isolation is not only about who can see what. It is also about one tenant not being able to degrade the others. In a pooled system, a single tenant running a punishing workload can consume a disproportionate share of CPU, IO, memory, or database connections, starving everyone else. That is the noisy-neighbor problem, and the data layer does nothing to stop it.

The control is per-tenant limits enforced above the database: rate limiting and quotas at the API gateway, and caps so one tenant cannot exhaust the shared connection pool. Inside the database you can add query limits and resource groups. The goal is that a tenant’s bad day stays the tenant’s bad day.

Encryption layer

For your most sensitive tenants, encrypt their data with per-tenant keys managed separately (for example, distinct KMS keys). The payoff is concrete: even if an attacker gets a full dump of the shared database, data encrypted under separately managed per-tenant keys does not all fall at once. It is extra key management, so it is worth it for high-security tenants rather than as a blanket default.

The silo, for when shared is not enough

Some tenants should not share at all. If a customer’s compliance profile demands physical separation, or the blast radius of any cross-tenant exposure would be catastrophic, the right isolation layer is a dedicated database or stack for that tenant. This is the silo from the tenancy models, used surgically: pool the many, silo the few who need it. That mix is the bridge model.

Blast radius is the framing that decides how far to go

The question behind every isolation layer is: when something goes wrong, how many customers does it touch? In a fully pooled system, one bad query or one leaked key potentially touches everyone. Each layer you add shrinks that radius. Per-tenant cache keys contain a caching bug to one tenant. Per-tenant encryption contains a database dump. A silo contains everything to a single customer.

You do not need every layer for every tenant. You need enough layers that the blast radius of a realistic failure is acceptable for the data you hold. That is a judgment call, and it is driven by what your customers and their regulators require.

The traps

Stopping at RLS. Row-Level Security is excellent and it is one layer. If your cache and your object storage have no tenant awareness, you have a locked front door and open windows.

No noisy-neighbor control. Teams isolate data perfectly and forget that one tenant can still take the whole system down by hogging connections. Per-tenant limits are isolation too.

Blanket maximum isolation. Per-tenant keys and silos for every tenant, including the free ones, burns money and ops time on isolation most customers never needed. Match the layers to the tenant’s risk profile.

Predictable identifiers without authorization. Sequential ids and guessable object URLs invite enumeration. Tenant scoping has to be enforced on access, not just present in the path.

Summary

Tenant isolation is a stack: tenant_id everywhere with Row-Level Security at the data layer, per-tenant rate limits and quotas at the API layer, per-tenant encryption for sensitive tenants, and a silo for the few who cannot share at all. The point of the stack is that no single failure exposes another customer, because another layer catches it. Decide how many layers each tenant needs by asking how large a blast radius you can tolerate for the data you hold, and let your customers’ compliance requirements set the floor.

This is the security view of the tenancy decision. For the models themselves, see multi-tenant architecture for vertical SaaS and single-tenant vs multi-tenant. For the database mechanism that anchors the data layer, see Postgres Row-Level Security for multi-tenancy.


Want your tenant isolation pressure-tested before a customer or an auditor does it for you? I do this as part of Scale Readiness. Book a free 30-minute call and we will find the layer you are missing.

Back to Blog

Related Posts

View All Posts »