Building a SaaS product means making architectural choices that will haunt you for years. Most founders don’t realise this until they’re trying to migrate 500 paying customers off a flawed database schema at 2am on a Sunday.
The multi-tenant decision is the biggest one. Get it wrong, and you’re not just slower-you’re fundamentally limited in what you can sell, who you can sell to, and how much you can charge. This isn’t theoretical. A fintech startup we worked with made the wrong call at day one and had to rebuild their entire data layer 18 months later, costing them roughly AUD 180k in engineering time and pushing their roadmap back by a quarter.
Let’s talk about what actually matters.
Single-Tenant vs Multi-Tenant: The Real Trade-Off
Single-tenant means each customer gets their own database, their own servers, sometimes their own entire deployment. Multi-tenant means all customers share infrastructure-same database, different data partitions.
Single-tenant sounds “safer” to non-technical founders. It feels simpler: no complex permission layers, no risk of data leakage between customers, no architectural gymnastics. And honestly, it’s the right choice for some businesses.
But here’s the cost: you’re running 50 databases instead of one. Your DevOps and infrastructure burden scales linearly with customers. A customer asking for custom data retention? That’s a custom deployment. A customer wanting on-premise installation? Single-tenant makes that feasible. A customer wanting HIPAA compliance? You’re already halfway there-just add auditing.
Multi-tenant is the opposite lever. You run one (or a few) databases for all customers. You scale horizontally by adding read replicas and caching, not by provisioning new servers. Your infrastructure costs grow logarithmically, not linearly. But you need to think harder about data isolation, permission models, and whether your schema actually supports per-customer configuration.
For most B2B SaaS in Australia, multi-tenant makes sense once you’re past the MVP. It’s cheaper to operate at scale. But you need to build it right from the start, because retrofitting multi-tenancy into a single-tenant codebase is genuinely painful.
The Three Isolation Models: Pick One and Commit
Within multi-tenant, there are three main patterns. Each has different trade-offs.
- Row-Level Isolation (RLS). One database, one schema. You add a
tenant_idcolumn to every table and use database-level row security policies to enforce access. A query for Customer A never returns Customer B’s rows, because the database won’t allow it. Pros: simplest code, lowest infrastructure cost, easy to add new features (they’re just new columns). Cons: if you misconfigure the policy, data leaks are possible; you can’t easily give one customer a custom schema (say, different field names or data types); performance at very high scale requires careful indexing. - Schema-Level Isolation. One database, different schemas per customer. Customer A gets
schema_a. Customer B getsschema_b. Pros: you can customise the schema per customer without affecting others; permission isolation is stronger; migrations are per-customer (you can roll out features gradually). Cons: your application needs to dynamically switch schemas (more complex routing logic); deployment and testing become harder (do you test against 50 schemas?); you can’t easily query across customers for analytics. - Database-Level Isolation. Each customer gets their own database. This is single-tenant dressed up. Pros: complete isolation, highest security, customers often feel good about it. Cons: you’re back to the DevOps burden-provisioning, monitoring, backing up 50+ databases; your deployment pipeline needs to handle this at scale; analytics and reporting become a nightmare because data is scattered.
Most Australian SaaS companies we work with start with row-level isolation. It’s the right balance: cheap, scalable, and simple enough that you won’t spend three months building plumbing. The database policies do the heavy lifting for you.
The Data Model Question: Where Custom Fields Live
Every SaaS founder eventually hears: “Can we add a custom field for our company?”
In a traditional fixed schema, the answer is either “no” or “ask our engineering team,” which means a 6-week roadmap slot and a new deployment. Not competitive.
The three options:
- Fixed schema. You define all fields upfront. Simple to code, simple to query, simple to index. But inflexible. Your customers will hate it. And you’ll add fields reactively anyway, fragmenting your codebase.
- JSON/JSONB columns. PostgreSQL has JSONB, MySQL has JSON. You store custom fields as unstructured data within a column. Pros: flexible, customers can add their own fields instantly. Cons: you can’t easily index into those fields (queries get slow); you can’t validate the schema (garbage in, garbage out); reporting against custom fields is painful.
- EAV (Entity-Attribute-Value) tables. A separate table for custom attributes. Customer’s object has ID 1, and there’s a row for each custom field. Pros: fully flexible, you can index it. Cons: your queries become ugly (lots of joins); performance degrades with many custom fields; it’s genuinely hard to query.
The sweet spot for most companies is JSONB with constraints. Store custom fields in a JSONB column, but document what’s allowed (via application logic or database constraints), and index the commonly-queried paths. You get flexibility without complete chaos.
Tenant Context Injection: A Security Bet You Get Wrong at Your Peril
Once you’ve decided on multi-tenant, you need a way to tell your application “this request is from Customer A.” If you get this wrong, you leak data between customers.
The pattern is straightforward: extract the tenant ID from the request (usually from a JWT token or API key), set it in the request context, and then every database query automatically filters by that tenant ID.
In practice, you need:
- A middleware that runs on every request and validates the tenant context
- A way to propagate that context through your application (thread-local storage, context objects, dependency injection-whatever your language supports)
- Explicit tests that verify a user from Customer A literally cannot read Customer B’s data, even if they try to bypass the UI
- Logging that tracks which tenant made which query, so if there’s a breach, you can audit it
The mistake we see most often is developers “forgetting” to filter by tenant in one query. Maybe it’s a reporting script. Maybe it’s an admin panel. Maybe it’s a background job. And suddenly, Customer B can see Customer A’s financial data. This is not a performance problem-it’s a liability problem.
When to Scale: Know Your Breaking Point
If you’ve chosen row-level isolation with a shared database, you’ll eventually hit a point where one database isn’t enough. Rough ballpark: if you’re handling 10,000+ concurrent users on a single PostgreSQL instance, or if your database is 2TB+ and growing, you’re approaching the hard ceiling.
At that point, you have options: vertical scaling (bigger hardware, costs roughly AUD 5k-15k per month for enterprise-grade databases), horizontal scaling (sharding the data, where Tenant IDs 1-100 live on Shard A, 101-200 on Shard B, etc.), or moving to a distributed database like CockroachDB.
Sharding is not a decision you make lightly. Once you’ve sharded, un-sharding is nearly impossible. And you need to shard early enough that you don’t have to rewrite your whole application logic to account for distributed data.
Most Australian companies don’t hit this problem until they’re doing AUD 2m+ ARR. But it’s worth thinking about now. If your product could plausibly get there, design for it upfront (even if you don’t implement it yet). Make sure every query has a tenant ID filter-that’s step one of sharding.
Get the Architecture Right, Then Move Fast
The reason we’re writing this is simple: architecture decisions make or break your velocity later. Spend two weeks now thinking clearly about multi-tenancy, and you’ll save months later.
If you’re building a SaaS product or AI agent and you’re not sure about the right model for your situation, talk to Amora about your build. We ship MVPs in 28 days, but the architecture underneath needs to support growth beyond that.
Get the tenant isolation right. Choose a data model that lets customers extend the product without you rewriting code every fortnight. Build the context injection pattern correctly. And document it so your future team doesn’t accidentally leak data.
Everything else is optimisation.
Got something you want built?
Amora Digital is an Australian software and AI agency. We scope it, build it, and ship it – live in 28 days. No offshore teams. No surprises.