In this article, we explore how to design and implement multitenant database architectures tailored for B2B SaaS applications. You’ll learn how to securely isolate data, scale your backend efficiently, and choose the right multitenancy strategy.
Imagine you're building a SaaS CRM tool for businesses. Every customer expects their data to be private and isolated, even though you're serving them all from a single backend system. This need for isolation, without deploying a separate application for each customer, is what makes multitenancy essential.
Multitenancy allows your application to support multiple independent tenants, usually companies or organizations, using a shared set of infrastructure like application code, servers, databases, while keeping their data isolated. It's not just about saving money on infrastructure; it's about scalability, maintainability, and security. Choosing the right multitenancy strategy early can dramatically reduce complexity as your application grows.
As a backend developer, your job isn't just to implement endpoints. It's to architect systems that are safe, scalable, and tenant-aware.
In the context of multitenancy, a tenant is a single customer or organization that uses your application. Each tenant expects:
Your backend system should ensure that tenant data is always isolated, even though the codebase is shared.
There are three primary strategies to implement multitenancy at the database level. Each comes with trade-offs.
All tenants use the same database and the same tables. You distinguish tenant data by adding a tenant_id
column to each table.
SELECT * FROM invoices WHERE tenant_id = 'acme_corp';
Pros:
Cons:
tenant_id
, or you'll risk data leaksThis model is good for early-stage products or internal tools where complexity needs to be kept low.
Here, all tenants share the same database, but each tenant has their own schema (a namespace of tables).
-- Example: schema tenant_acme
SELECT * FROM tenant_acme.invoices;
Pros:
Cons:
This is a good compromise when you need more isolation but don't want the operational cost of multiple databases.
Each tenant gets their own dedicated database. You provision a new database when a customer signs up.
def get_engine(tenant_id):
db_url = f"postgresql://user:pass@host/tenant_{tenant_id}"
return create_engine(db_url)
Pros:
Cons:
This model is ideal for enterprise-grade SaaS platforms, especially when clients require guaranteed isolation.
Let’s say you’re building a CRM app for B2B clients. Each tenant needs to:
We'll walk through how each multitenancy model shapes your architecture using this scenario.
All tenant data lives in shared tables, distinguished by tenant_id
:
class BaseModel(Base):
__abstract__ = True
tenant_id = Column(String, nullable=False)
class Lead(BaseModel):
__tablename__ = 'leads'
id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String)
Every query must filter on tenant_id
:
def get_leads_for_tenant(session, tenant_id):
return session.query(Lead).filter_by(tenant_id=tenant_id).all()
Mistakes are easy here: forget to filter by tenant_id
, and you may leak data between tenants.
Each tenant gets a unique schema: tenant_acme
, tenant_foobar
, etc.
You dynamically switch the schema in SQLAlchemy by setting search_path
on connection:
@event.listens_for(engine, "connect")
def set_schema(dbapi_connection, connection_record):
schema = get_schema_for_request()
cursor = dbapi_connection.cursor()
cursor.execute(f"SET search_path TO {schema}")
cursor.close()
This way, you use the same models, but they resolve to tenant-specific schemas. There's no need for a tenant_id
field anymore.
Each signup triggers a new database creation:
subprocess.run(["createdb", f"tenant_acme"])
When handling a request:
def get_session_for_tenant(tenant_id):
db_url = f"postgresql://.../tenant_{tenant_id}"
engine = create_engine(db_url)
return sessionmaker(bind=engine)()
Every tenant has a fully isolated environment, but now you're managing hundreds or thousands of databases.
Let’s look at the big picture.
Model | Pros | Cons |
---|---|---|
Shared Schema | Easy to start, efficient | Risk of cross-tenant leaks, harder compliance |
Schema-per-Tenant | Better isolation, easier RLS | Migration + tooling complexity |
DB-per-Tenant | Strongest isolation | Highest ops + infra cost |
There is no “best” model, only trade-offs based on your product, team size, and customer expectations.
So there are some common pitfalls to watch out for.
tenant_id
into every query."leads_count"
; use tenant-specific keys like "acme:leads_count"
.alembic
+ scripts to iterate per schema.pgbouncer
or pool sharing strategies.Multitenancy is not just about data architecture, it's about trust. Your tenants trust that their data is isolated, secure, and performant, regardless of how many other customers you're hosting. Start simple if you're early, but design for the long term. Build abstractions that allow you to evolve from shared schema to schema-per-tenant or DB-per-tenant as needed. Most importantly, treat multitenancy as a first-class concern in your architecture not just a detail in your models.