Skip to content
Whyire
← Back to Engineering Journal
8 MIN / TECHNICAL / DECEMBER 9, 2025 — BY TADIWA GWENA

Building Scalable Multi-Tenant Applications

Building Scalable Multi-Tenant Applications

Harare, Zimbabwe

Building Scalable Multi-Tenant Architecture in the Agentic Era

Let’s talk about multi-tenancy: serving multiple customer organizations from one application while keeping their data and operations appropriately separated. Scale, configuration needs, and performance targets vary by product; no architecture can promise absolute privacy or zero latency.

A shared-table design commonly stores a tenant identifier on each tenant-owned row. Application queries must then scope reads and writes correctly. This can be a reasonable design, but relying only on every application query to remember the filter leaves room for mistakes.

That design is still multi-tenancy; the important question is where tenant boundaries are enforced and how they are tested. A missed authorization check can expose data, so application-level filtering may warrant additional safeguards.

Isolation Is a Design Requirement

Multi-tenancy is a way of serving multiple tenants from an application or service. Isolation can be implemented at different layers, and the right combination depends on the product's security, operational, and scaling requirements. No single pattern makes isolation absolute.

AI features add another place to consider tenant boundaries: retrieval, prompt construction, model calls, and response caching must not mix one tenant's data into another tenant's result. This is an engineering risk to test for, not a guarantee that a particular isolation pattern will prevent it.

Apply authorization checks at the boundaries that handle tenant data, and test them end to end. More layers can provide defense in depth, but adding layers without clear ownership can also create mistakes.

Three Common Data Isolation Choices

Database-per-tenant, schema-per-tenant, and shared tables with row-level policies are common options. Each has operational and security trade-offs; the choice affects deployment, cost, migration, and recovery work and should be evaluated for the workload:

1. The Silo: Database-per-Tenant

  • The Pitch: Maximum isolation. Every tenant gets their own physical or logical database.
  • The Trade-off: Separate databases can provide stronger administrative separation, but operating and migrating many databases adds coordination work. It does not automatically satisfy a compliance requirement; that depends on the system and the applicable rules.

2. The Bridge: Schema-per-Tenant

  • The Pitch: One database, but a separate schema (namespace) for every tenant.
  • The Trade-off: Schemas organize objects within a database, but do not by themselves create a privilege boundary; PostgreSQL notes that users with the necessary privileges can access objects across schemas. If a connection's schema or session state varies by tenant, pooled connections must be managed carefully; this is an operational concern, not an inherent PostgreSQL latency penalty. See the PostgreSQL schema documentation.

3. The Pool: Shared Schema with Row-Level Security (RLS)

  • The Pitch: All tenants share the same tables. Isolation is enforced by the database engine itself, not by the application code.
  • The Trade-off: Shared tables can use database row-security policies as an additional enforcement layer. They are not a universal winner and still require careful policy, role, and connection-context design.

Applying the Options to Akademeek

Whyire's product materials describe Akademeek as a multi-tenant education product, but do not specify its deployed database-isolation pattern. The following PostgreSQL example is a design option, not a claim about Akademeek's implementation. Row-Level Security (RLS) can add a database-enforced policy to a shared-table design.

Here is why this architecture dominates the modern stack:

The Database is the Final Authority

When RLS is enabled and correctly configured, PostgreSQL applies the table's policies to ordinary queries. However, table owners normally bypass those policies, as do superusers and roles with BYPASSRLS; a table owner can opt into enforcement with FORCE ROW LEVEL SECURITY. RLS reduces the risk of a missed application filter, but it does not eliminate configuration errors or replace authentication and authorization. See the PostgreSQL Row Security Policies documentation.

Establishing Tenant Context on the Server

A server-first architecture can keep database access on the server, but it does not by itself establish a trustworthy tenant identity. Resolve the tenant from authenticated membership and validated request context; do not treat a client-supplied tenant ID as authorization.

  1. A request hits the server.
  2. The server verifies the user and resolves the tenant from authorized membership and any validated domain mapping.
  3. The server validates input at each relevant boundary and authorizes the requested operation.
  4. The server passes the validated tenant context only to trusted server-side code that needs it.

Within a transaction, server-side code can set a transaction-local PostgreSQL custom setting (for example, SET LOCAL app.tenant_id = '…') and have an RLS policy read it. Use the database driver's safe parameter-binding mechanism, and ensure the tenant value comes from verified authorization context. PostgreSQL documents SET LOCAL and its custom configuration options; these settings are a way to pass context, not a cryptographic identity proof.

State Management is Scoped by Default

Server-side data access does not eliminate browser, client-router, or application-cache risks. Scope cache keys and invalidation to the tenant and user where appropriate, avoid sharing private responses, and re-check authorization on each protected operation. Next.js also warns that Server Functions are directly reachable and authentication and authorization must be verified inside each one; see its data-security guidance.

Documenting and Testing the Choice

Whyire's engineering standards call for Architecture Decision Records for system-level changes before review. A useful record should state the chosen isolation pattern, alternatives, role and policy assumptions, migration and recovery approach, and how cross-tenant access is tested. For Akademeek, do not attribute a specific database pattern unless its implementation is documented and verified.

The practical goal is to make tenant boundaries explicit, enforce them consistently, and verify that the system fails safely when those boundaries are challenged.


Continue Exploring the Journal

Return to the index of published engineering essays and research archives.

View Journal Index →