DEV Community

Cover image for PostgreSQL Multi-Tenant Design: RLS vs Schema SaaS
Bijak Technology
Bijak Technology

Posted on Originally published at bijaktechnology.com

PostgreSQL Multi-Tenant Design: RLS vs Schema SaaS

πŸ’‘ Originally published on Bijak Technology Enterprise Journal with full interactive parameter benchmarks.

At exactly 08:00 AM during nationwide online examinations, thousands of schools access an EduTech Software-as-a-Service (SaaS) platform simultaneously. Within seconds, a spike in SQL queries hits the primary database, pushing CPU usage to 100% and inflating latency from 15 milliseconds to a 2,400 ms timeout. Worse, cross-tenant data leaks lurk if application-level tenant isolation logic contains bugs. For engineering leads, this crisis exposes a fundamental dilemma: how to design an EduTech Multi-Tenant Architecture that guarantees strict data isolation without causing uncontrollable infrastructure costs?

When building mid-to-large scale EduTech systems, evaluating patterns such as Modular Monolith architecture is often the first step before tackling isolation boundaries at the storage layer. Within the PostgreSQL ecosystem, the core debate centers on two dominant strategies: Row-Level Security (RLS) and Schema-per-Tenant. Each option presents concrete trade-offs between resource efficiency, operational complexity, and data security guarantees.

Dissecting Data Isolation Models: RLS vs Schema-per-Tenant

In the Row-Level Security (RLS) model, all tenants share the exact same database tables and schema (public). Data segregation is enforced directly by the PostgreSQL engine using security policies that evaluate session variables such as app.current_tenant_id. These policies automatically append filters to every SELECT, UPDATE, and DELETE query without relying on manual WHERE clauses in application code.

Conversely, Schema-per-Tenant isolates each educational institution into a distinct PostgreSQL schema within the same database instance. Data routing is controlled via search_path. This model eliminates data leak risks stemming from developer query oversights, but introduces substantial metadata overhead when the tenant count scales into thousands.

Resource Overhead and Connection Pooling Mechanics

Deploying Schema-per-Tenant at scale suffers from catalog bloat. When schema counts reach 2,000 with 40 tables per schema, PostgreSQL must manage 80,000 relations inside the pg_class system catalog. This exhausts shared buffers and raises shared catalog cache RAM consumption up to 2.4 GB just to hold relation metadata. Furthermore, running DDL migrations (ALTER TABLE) across 2,000 schemas requires up to 45 minutes of sequential execution time while holding relation locks.

Under RLS, DDL migrations execute once on a single unified table. However, RLS introduces complexity in connection pooling. When utilizing PgBouncer in transaction pooling mode, SET LOCAL app.current_tenant_id = 'uuid' must be strictly executed within every transaction block to prevent session variable leakage across queries handling different tenants.

Technical Evaluation Metric Row-Level Security (RLS) Shared Schema Schema-per-Tenant (Isolated Schemas)
Catalog Memory Overhead (pg_class) Very Low (< 50 MB for 10,000 tenants) Very High (> 2 GB for 2,000 tenants)
Schema Migration Time (1000 Tenants) Fast (< 3 Seconds, Single Migration) Slow (15–45 Minutes, Sequential Loop)
Data Leakage Risk (Human Error) Moderate (Depends on RLS Policy & Session) Extremely Low (Namespace isolation via search_path)
PgBouncer Transaction Mode Compatibility Requires strict SET LOCAL transaction scope Requires explicit search_path reset per transaction
Scalability Limit (Tenant Capacity) > 100,000 Tenants per Cluster ~2,500 Tenants per Cluster (Catalog Bottleneck)

Hybrid Strategies and Engine Infrastructure Tuning

To bridge this gap, enterprise engineering teams frequently adopt a hybrid model: standard tier accounts are hosted within a high-density shared schema guarded by RLS, while high-compliance enterprise institutions or large universities are assigned dedicated PostgreSQL schemas.

Managing high-concurrency database workloads demands consistent compute IOPS and predictable disk latency. Running PostgreSQL clusters alongside PgBouncer and monitoring execution plans via pg_stat_statements requires an unthrottled infrastructure foundation. Deploying on Hostinger KVM VPS infrastructure backed by NVMe storage provides full root access to fine-tune engine parameters like shared_buffers = 4GB, work_mem = 64MB, and max_worker_processes to effectively absorb sudden exam traffic spikes.

Infrastruktur di atas mendukung skalabilitas proses operasional jangka panjang.

Operational Multi-Tenant Implementation Checklist

Before deploying a multi-tenant database design to production, complete the following technical validation steps:

  1. Verify RLS via Automated Integration Tests: Execute concurrent test suites across different session contexts to verify that tenant A queries return 0 rows when attempting to access tenant B records.
  2. Enforce SET LOCAL via Middleware: Ensure application ORM layers execute SET LOCAL app.current_tenant_id inside the active SQL transaction block prior to running payload queries.
  3. Restrict Application Database Roles: Confirm that application connection roles lack SUPERUSER or BYPASSRLS privileges, as these roles implicitly bypass all defined RLS policies.
  4. Monitor PostgreSQL Catalog Bloat: Set alert triggers if pg_class tuple counts or shared catalog cache size exceed 15% of allocated system RAM.
  5. Apply Composite Tenant Indexing: Create composite indexes (tenant_id, primary_key) on shared tables to ensure index scans execute without scanning foreign tenant data.

Transparency: Some links in this article are affiliate links that may earn us a commission at no additional cost to you.


Explore more in-depth system architecture blueprints, FinOps strategies, and enterprise implementations at Bijak Technology.

Top comments (0)