Multi-tenant RAG: lessons from 17 phases of EinCoreRAG
How PostgreSQL RLS and per-tenant Qdrant collections isolate data in enterprise AI environments.
- architecture
- security
Multi-tenant RAG: lessons from 17 phases of EinCoreRAG
When building enterprise AI systems, the first question from any CISO is: “How do you guarantee my data won’t leak to another tenant?”
In EinCoreRAG, we tackled this by enforcing isolation at two critical layers: the relational database and the vector database.
1. Relational Layer: PostgreSQL Row-Level Security (RLS)
Application-layer authorization is brittle. A single missing WHERE tenant_id = ? clause can expose sensitive data.
Instead, we pushed isolation down to the database engine using PostgreSQL RLS:
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON documents
USING (tenant_id = current_setting('app.current_tenant_id'));
Our API layer sets the app.current_tenant_id variable at the start of every request transaction. This guarantees that all queries naturally filter out data belonging to other tenants.
2. Vector Layer: Qdrant Collections
Vector databases pose a different challenge. Initially, we considered using a single shared collection with a tenant_id payload filter.
However, we moved to per-tenant collections in Qdrant for three reasons:
- True Isolation: A compromised or misconfigured query cannot cross collection boundaries.
- Performance: HNSW indices perform better when they don't have to navigate massive, diverse graphs.
- Data Residency: Specific tenants can have their collections routed to specific geographic regions or dedicated clusters.
The Result
The combination of RLS and separated vector collections creates a "fail-closed" architecture. Even if the application logic contains a flaw, the data engines refuse to serve cross-tenant records.
This post has been redacted by our automated security pipeline to remove internal topology details.