UgrĂĄs a tartalomhoz
← Back to the journal

Building a multi-tenant SaaS from MVP — 6 mistakes you must not make

Tenant_id everywhere, RLS day one, defer sharding until 200 tenants, per-tenant sequences, Host-header resolution, tenant-per-index. Six pitfalls — month-18 fixes are not an option.

Building a multi-tenant SaaS from MVP — 6 mistakes you must not make

Every SaaS you launch turns multi-tenant sooner or later. The only question is whether you plan for it, or your 7th customer forces it on you in a late-night incident. Six mistakes we or our clients have made — and want to spare you from.

1. Forgetting tenant_id on a table

The ground rule: every business table's first column is tenant_id. Even tags. Even user_preferences. The smallest exception comes back to bite you — one client of ours discovered 14 months in that email_templates had no tenant_id, and a template saved by tenant B showed up in tenant A's account.

Enforce it at migration level: no table without tenant_id unless explicitly shared (e.g. tenants, tenant_billing_plans).

2. Launching without row-level security (RLS)

“The service layer will check.” It won't. One forgotten WHERE in an admin tool, one badly scoped JOIN, and data leaks sideways. Turning on Postgres RLS isn't optional — it goes in during the MVP's first week.

A measured datapoint from a 2026 client audit: 3 out of 17 service-layer queries didn't filter on tenant_id. With RLS those errored out; without it, they leaked silently.

3. Sharding too early

Despite the “we're going to scale” narrative: one Postgres is enough for the first 200 tenants. The sharding tax (cross-shard JOINs, eventual consistency, devops complexity) is more cost than benefit right now. Add the shard-key column (shard_key), add the tenant→shard mapping table — but stay on one shard until p99 > 500ms or DB-CPU > 70% becomes sustained.

4. Shared sequence across tenants

quote_number and friends: if you use SERIAL with one global sequence, tenant B's quote numbers jump whenever tenant A inserts. That's not just ugly — it breeds accounting issues.

Pattern: composite (tenant_id, sequence_id), INSERT ... RETURNING from a per-tenant nextval('tenant_X_quotes_seq'), or an explicit tenant_counters table with FOR UPDATE. Simple, and both parties stay sane.

5. Hard-coded subdomain

“We'll put Acme under acme.app.example.com for now.” Six months in: 47 subdomains, an unmaintainable Cloudflare DNS ruleset, SSL cert rotation is a nightmare. And the new customer already wants the SaaS on their own domain (portal.acme.com).

From day one: Host-header based tenant resolution. The subdomain is just a convenience alias. In the DB, the tenant's host_aliases field holds a list (acme.com, portal.acme.com, acme.app.example.com). One ingress, one wildcard cert, many hosts.

6. Shared search index

Elasticsearch / Meilisearch / OpenSearch: if the whole SaaS searches a single index, the tenant_id filter is only at query time. The query planner gets expensive, re-indexing one tenant touches the whole index, and one config error leads to data leakage again. Tenant-per-index (or at least per-index-alias) — slightly pricier storage, vastly simpler security.

Takeaway

Multi-tenancy isn't a switch you flip later. It's an architectural layer you have to design in on day one of the MVP. Every one of the six mistakes above demands a rewrite at month 18 — unless you got it right at month one.

Let's talk about your project

Tell us what you are building — we will figure out how to help.

Building a multi-tenant SaaS from MVP — 6 mistakes you must not make — Nortinia Journal | Nortinia