Multitenancy is an architecture where a single instance of software serves multiple customers (tenants). It is the backbone of almost every modern SaaS application, balancing resource efficiency with data isolation.
To understand multitenancy, compare buying a house vs. renting an apartment.
Like owning a detached house. You have your own walls, plumbing, and security system.
Like an apartment complex. Everyone shares the foundation, plumbing, and security guard, but has their own private key.
How do we actually separate the data? There are three main patterns.
The "Premium" option. Each tenant gets their completely own database instance. The application connects to a different DB based on who is logging in.
const tenant = getTenant(request);
const connection = createConnection({
host: 'db-cluster-1',
database: `db_${tenant.id}` // distinct DB
});
The "Balanced" option. Everyone shares one database server, but each tenant gets their own "Schema" (namespace) with their own tables.
-- Switch search path context
SET search_path TO tenant_a;
SELECT * FROM users;
// Automatically queries tenant_a.users
The "Efficient" option. Everyone lives in the exact same tables. Every table has a `tenant_id` column (Discriminator). This is how huge SaaS apps (Salesforce, Slack) scale.
SELECT * FROM users
WHERE tenant_id = 'tenant_123';
See how the backend handles a request for "All Orders" depending on the strategy and the logged-in user.
The biggest fear with the "Shared Database" model is developer error—forgetting the `WHERE` clause. PostgreSQL RLS solves this by enforcing the rule at the database engine level.
Even if your backend code tries to `SELECT * FROM users`, the database itself intercepts the query and says, "Wait, you are Tenant A, so you only see Tenant A's rows."
Read Postgres Docs open_in_new-- 1. Enable Security on Table ALTER TABLE orders ENABLE ROW LEVEL SECURITY; -- 2. Create Policy CREATE POLICY tenant_isolation_policy ON orders USING (tenant_id = current_setting('app.current_tenant')); -- 3. App code just sets the variable SET app.current_tenant = 'tenant_a'; SELECT * FROM orders; -- ^ Safe! Returns only tenant_a orders automatically.
| Feature | Database per Tenant | Schema per Tenant | Shared DB (Pool) |
|---|---|---|---|
| Isolation Level | Highest (Physical) | Medium (Logical) | Lowest (Logical) |
| Cost efficiency | Low ($$$) | Medium ($$) | High ($) |
| Schema Migrations | Hard (Run 1000x) | Hard (Run 1000x) | Easy (Run 1x) |
| Best Use Case | Banking, Healthcare, Enterprise | Mid-market B2B | High-volume SaaS (Slack, Notion) |
| Complexity | Infrastructure Heavy | Scripting Heavy | App Logic / Security Heavy |