On this page
The problem
As we expand from a single-tenant learning platform to supporting teams and organisations, we need to isolate content data between tenants in our RAG pipeline. A data leak where Tenant A's proprietary content appears in Tenant B's responses would be a critical trust and compliance failure.
What we weighed
Row-Level Security (RLS) with tenant_id filter
- +Single database, single schema — simpler operations
- +PostgreSQL RLS enforces isolation in the database, so an application query that forgets the tenant filter still can't read other tenants' rows (superusers, BYPASSRLS roles and table owners bypass it unless FORCE ROW LEVEL SECURITY is set)
- +Works with our existing pgvector setup
- +Scales to thousands of tenants without infrastructure changes
- −RLS policies add query planning complexity
- −Developer mistakes in disabling RLS can expose all data
- −Metadata filtering on vector indexes adds latency, and with approximate indexes a post-scan filter can return fewer results than requested
Collection-per-tenant (separate vector namespace)
- +Strong physical isolation — tenant data never shares an index
- +Easy to delete all tenant data (drop collection)
- +No risk of cross-tenant query leakage at the vector DB level
- −Gets costly to operate at high tenant counts; check your vector DB limits on collections or namespaces
- −Operational overhead: N tenants = N indexes to manage
- −Cannot do cross-tenant analytics
Schema-per-tenant (separate PostgreSQL schema)
- +Strong isolation
- +Each tenant schema is independently backupable
- −Operationally complex — schema management, migrations must run per-tenant
- −Only practical for a small number of large tenants
What we chose
Pending final evaluation. Lean toward RLS with tenant_id metadata filtering for vector queries, combined with application-level tenant context validation. This balances isolation quality with operational scalability. Will implement collection-per-tenant for any tenants with > 100K documents and compliance requirements.
What it committed us to
- •Add tenant_id column to all content tables with NOT NULL constraint
- •Enable RLS on all content tables with tenant_id-based policies, set FORCE ROW LEVEL SECURITY, and connect as an application role that does not own the tables or have BYPASSRLS
- •All vector search queries must include tenant_id metadata filter
- •Audit all LLM prompt assembly to verify cross-tenant context cannot be injected
- •Implement output validation step to detect and redact potential cross-tenant references

