21 February 20266 min read

Multi-tenant SaaS architecture for Indian SMBs, explained

A practical breakdown of multi-tenant SaaS architecture (row-level, schema-per-tenant, and database-per-tenant models) and why RLS-based isolation matters most for Indian franchise and multi-branch businesses.

Multi-tenant SaaS architecture is a design where one running application and one (or a small number of) database serves many separate customers, called tenants, while keeping each tenant's data logically isolated. For Indian SMBs, such as a franchise with 40 outlets, a coaching chain across three cities, or a distributor with regional branches, the right tenancy model decides how cheaply you can scale, how hard a data leak is to cause, and how fast you can onboard the next branch. There are three common models: row-level (shared database, a tenant ID column on every table), schema-per-tenant (one database, separate schemas), and database-per-tenant (a fully separate database per customer). Most Indian SMB products should start with row-level isolation enforced by Postgres Row-Level Security.

What multi-tenant SaaS architecture actually means

The word "tenant" trips people up, so make it concrete. If you build billing software and sell it to a gym chain, the gym chain is a tenant. If that chain has ten branches, those branches might be sub-tenants, or just locations inside the one tenant. That is a modelling choice you make early. The opposite of multi-tenant is single-tenant, where every customer gets their own copy of the app and database. Single-tenant is simpler to reason about and painful to operate at scale: 200 customers means 200 deployments to patch every time you fix a bug.

Multi-tenancy flips that. One codebase, one deploy, one place to ship a feature. The cost you pay is isolation discipline. Every query, every cache key, every file upload path has to be tenant-aware, because a single forgotten WHERE clause can show one gym's member list to another gym. That single risk is the whole reason the three models exist.

The three tenancy models

Row-level (shared database, shared schema)

Every table carries a tenant_id (or org_id) column. All tenants live in the same tables, and you filter by tenant on every read and write. This is the cheapest model: one database, one schema, one connection pool. Adding a new tenant is an INSERT, not an infrastructure event, so onboarding a new franchise branch takes a second instead of a provisioning script.

The downside is that isolation is entirely your responsibility unless you enforce it at the database. Application code is fallible. A junior dev writes a report query, forgets the tenant filter, and now you have an incident. This is exactly the gap Row-Level Security closes, which is why row-level plus RLS is the default we reach for.

Schema-per-tenant

One database, but each tenant gets its own Postgres schema (a namespace of tables). Tenant A's members table is physically separate from tenant B's. Isolation is stronger and a bit more intuitive: you set the search path to the tenant's schema and queries cannot accidentally cross over. It also lets you do per-tenant schema tweaks, though you should resist that temptation.

The cost shows up in operations. Migrations now have to run across every schema. With 500 tenants that is 500 schema migrations per release, and a single failure mid-run leaves you in a mixed state. Connection pooling and the Postgres catalog also strain as schema count climbs into the thousands. It is a reasonable middle ground for a few hundred tenants, less so for tens of thousands of tiny ones.

Database-per-tenant

Each tenant gets a fully separate database, sometimes on a separate instance. This is the strongest isolation: a noisy tenant cannot starve another, and you can host a specific client's data in a specific region, which matters when an enterprise customer or a regulated sector demands it. Backups, restores, and even deletion are clean. Drop the database, the tenant is gone.

It is also the most expensive and operationally heavy. Every tenant is a provisioning step, a connection string to manage, a migration target, a backup schedule. For a B2B product selling to large organisations at a high price, the economics work. For an SMB tool charging a few thousand rupees a month per customer, database-per-tenant will quietly eat your margins.

Why RLS-based isolation is the practical default

Postgres Row-Level Security lets the database itself enforce tenant boundaries. You write a policy once, roughly "a user may only see rows where tenant_id equals the tenant ID on their session", and Postgres applies it to every query automatically. If a developer forgets the WHERE clause, RLS still filters the result. The safety net moves from human discipline to the engine.

With row-level security in the database this is the native pattern: the tenant identifier travels inside the user's JWT, a policy reads it via the auth context, and tables enforce it without the application doing anything extra. You get the cost profile of row-level tenancy with isolation that does not depend on every query being perfect. The two rules that make it hold up: enable RLS on every tenant-scoped table (an un-policied table is wide open), and never run normal traffic through a role that bypasses RLS, like the service role or the table owner. Audit periodically by querying which tables have no policies. That list should be empty.

The cheapest model to run is row-level. The safest version of the cheapest model is row-level with RLS. That combination is where most Indian SMB SaaS should live until a specific customer forces you off it.

Why this matters for franchise and multi-branch businesses

Indian SMBs are unusually multi-branch. A sweet shop becomes three outlets, a tuition centre franchises across Pune and Nagpur, a logistics SMB runs depots in four states. The tenancy model has to answer questions that show up constantly in these businesses.

  • Who sees what. The Andheri branch manager should see only Andheri's sales; the regional head should see the whole western zone; the owner sees everything. This is a hierarchy of access inside a single tenant, and RLS policies can encode "branch", "region", and "org" roles directly so the data layer enforces them.
  • Onboarding a new branch. With row-level tenancy, opening branch number 41 is a record creation, not a deployment. The franchise can be live the same afternoon.
  • Cross-branch reporting. Because all branches share the same tables, the owner's "all outlets" dashboard is one query with the tenant scope widened. That is trivial in row-level and awkward when each branch sits in its own database.
  • Leaving the franchise. When a franchisee exits, you need their data carved out cleanly. This is the one argument that pushes some businesses toward schema- or database-per-tenant, where export and deletion are surgical. With row-level you handle it with scoped exports and soft-delete, which works but takes more care.
  • Data residency. A large enterprise client may insist their data sits in a specific region or even a dedicated database. That is a real reason to run a hybrid: most tenants on shared row-level, a handful of big accounts on dedicated databases.

How to choose, in plain terms

Default to row-level with RLS. It gives you the lowest operating cost, instant tenant onboarding, and easy cross-tenant reporting, and RLS removes the main downside. Move a tenant to schema-per-tenant or database-per-tenant only when a concrete requirement forces it: a contractual data-residency clause, a customer big enough to need isolated performance, or a compliance demand for physically separate storage. Designing for a hybrid from day one, shared by default and dedicated by exception, keeps the cheap path cheap without painting you into a corner.

The mistake we see most often is over-engineering at the start: a two-month-old product building database-per-tenant for customers it does not have yet, then drowning in migration scripts. Start shared, enforce hard at the database, and let real customer requirements pull you toward stronger isolation. The second most common mistake is the opposite: row-level tenancy with no RLS, isolation living only in application code, one missing filter away from a breach.

Getting the boring parts right

Tenancy is not only a database decision. The tenant scope has to be present everywhere data lives. Cache keys must include the tenant ID, or one tenant gets served another's cached response. Uploaded files need tenant-prefixed paths in your object storage. Background jobs must carry the tenant context, because a queued report generator that loses its scope is as dangerous as a missing WHERE. Search indexes, exports, and webhooks all inherit the same rule: tenant-aware by default, no exceptions.

Getting these calls right early is the difference between a product that scales calmly to a few hundred Indian SMB tenants and one that needs a painful re-architecture at customer fifty. This is the kind of foundational decision BotBrained engineering sets up at the start of a build, so the cheap, safe path stays open as the customer base grows. If you are planning a multi-branch product and want a second opinion on the tenancy model before you write the first migration, that is a good conversation to have early rather than late.