Marcio Cunha

Domain Isolation in Shared Databases Using Dynamic Schemas and Access Policies

Learn how to isolate data for multiple tenants in a single relational database using dynamic schemas and row-level access policies.

Marcio Cunha•5 min
Also available in:EspañolPortuguês
Summary
  • Dynamic schemas in PostgreSQL separate logical data without the operational cost of dedicated physical instances.
  • Row-level security policies ensure SQL queries automatically block records belonging to other tenants.
  • The key to multi-tenant scalability lies in balancing strict isolation with infrastructure cost efficiency.
  • Errors in connection pooling can leak data between schemas if the session context is not properly cleared.
  • Adopting automated migrations prevents structural drift among logical data compartments.

The Challenge of Secure Infrastructure Sharing

When building software that serves multiple distinct companies or clients from a single codebase, a critical engineering challenge arises known as multi-tenancy. In practice, this means hundreds of different organizations share the same servers and the same database without knowing of each other's existence. The core dilemma of this approach is balancing reduced infrastructure costs with the absolute guarantee that one client's data is never visible to another. In enterprise systems, a single failure in this barrier can result in hefty regulatory fines for data leaks and irreparable loss of trust.

Historically, the simplest solution was to create a fully separate database for each client. While secure, this strategy becomes financially unviable and operationally chaotic when the user base grows to tens of thousands. Maintaining thousands of active instances consumes unnecessary RAM, complicates backup processes, and turns applying updates into a logistical nightmare. This is precisely where intelligent logical isolation strategies come in, allowing data to be divided within the same physical infrastructure strictly, rapidly, and inexpensively.

Understanding Dynamic Schemas in PostgreSQL

To solve this dilemma without sacrificing security, we can use the concept of schemas, which essentially function as organizing folders inside a single database. In practice, imagine a filing cabinet where each drawer has folders with the exact same form structure, but completely different contents belonging to different people. A client who opens their drawer can see only their own documents, with no visual access to neighboring drawers, even though the physical cabinet belongs to the same company.

In PostgreSQL, the command SET search_path TO allows changing the focus of the current session to a specific schema dynamically. When the application receives an HTTP request from a client, it identifies who is calling the system through the authentication token and immediately alters the database search path to that user's corresponding schema. This means any simple search command, such as SELECT * FROM clients, will automatically search only in that specific client's exclusive folder, eliminating the need to add manual identification filter clauses to every query in the code.

Implementing this dynamic context switching requires careful attention to connection management. Because web servers reuse active connections through a mechanism called a connection pool to gain speed, forgetting to reset the search path to default can cause the next client to accidentally access the previous client's data. To prevent this type of disaster, the application's data access layer must always wrap operations in secure transactions and ensure context cleanup occurs immediately after each processed request finishes.

Row-Level Access Policies for Advanced Protection

Although dynamic schemas offer great structural separation, we often need to go further and control data access row by row within a shared table. This is where row-level security policies, known in the technical community as RLS, come into play. In practice, this tool acts like a bouncer at a party door who checks each guest's ID before letting them into a specific room, preventing the visualization of any unrelated items.

RLS policies work by silently intercepting any command executed in the database and applying mathematical rules based on active session variables. For example, we can configure a rule stating that a record can only be read or modified if the tenant identification column matches the identifier stored in the current session's memory. The code below demonstrates creating a table with this protection enabled:

CREATE TABLE reports (    id SERIAL PRIMARY KEY,    tenant_id UUID NOT NULL,    content TEXT NOT NULL);ALTER TABLE reports ENABLE ROW LEVEL SECURITY;CREATE POLICY tenant_isolation_policy ON reports    USING (tenant_id = current_setting('app.current_tenant')::uuid);

With this policy configured, even if a developer makes a logical error and forgets to include the filter clause in the application query, the database itself intercepts the instruction and hides rows from other tenants. This adds a robust defensive layer, protecting the system against human errors in code writing and ensuring that isolation is maintained natively and inviolably.

Mitigating Operational Risks and Schema Maintenance

Adopting an architecture based on dynamic schemas and access policies brings major advantages, but it also introduces new operational challenges that must be managed carefully. The primary one is executing structural migrations. When we decide to add a new column to a table, that change must be replicated to dozens or hundreds of individual schemas simultaneously, otherwise future queries will fail due to structural incompatibility.

To overcome this problem, we use automated migration scripts that iterate through all active schemas registered in the system whenever a new update package is released. Modern database versioning tools can be configured to run these routines in a controlled manner, ensuring no compartment is left behind or exhibits layout divergences. Additionally, performance monitoring routines should be implemented to track the individual growth of each schema, preventing an overly active tenant from monopolizing disk resources and hurting the overall performance of the shared server.

Final Considerations on Scalability and Security

Data isolation in multi-tenant environments using dynamic schemas and access policies represents an elegant balance between cost efficiency and strict security. By delegating part of the access control directly to the database engine, we reduce exclusive reliance on the application layer and build defense-in-depth against accidental leaks. Although it requires discipline in connection management and structural update automation, this approach empowers software companies to scale operations to thousands of clients without fragmenting physical infrastructure.

In short, choosing this architecture should be guided by client data volume and industry regulatory rigor. When designed with attention to operational details, dynamic schemas offer the necessary flexibility to grow sustainably while keeping data integrity and confidentiality non-negotiable priorities in any modern software engineering system.