Building Retrieval-Augmented Generation (RAG) systems for enterprise SaaS platforms presents a unique challenge: data isolation. Unlike single-tenant applications, multi-tenant environments require strict boundaries between customers. While Weaviate offers native multi-tenancy, simply separating chunks by tenant is often insufficient for enterprise-grade security. This post explores how to combine Weaviate's isolated indexes with application-level Row-Level Security (RLS) to create a secure, scalable RAG architecture.
Understanding Weaviate Multi-Tenancy
Weaviate’s multi-tenancy feature allows you to store data for different tenants in a single collection while maintaining physical or logical isolation. Each tenant has its own set of vectors, effectively creating sub-collections. This reduces resource overhead compared to creating a separate collection per customer but requires careful management of access control.
The key advantage is performance. Queries are scoped to specific tenants, ensuring that search operations only traverse the relevant data shard. However, this does not automatically handle user-level permissions within a tenant. That is where Row-Level Security comes in.
Implementing Row-Level Security with Metadata Filtering
RLS in Weaviate is typically enforced through metadata filtering. By tagging each vector with additional attributes such as department, role, or project_id, you can restrict which documents a user can retrieve. For example, a "Finance Manager" might only see documents tagged with department: finance, while an "Engineer" sees department: engineering.
Here is how you can structure your data to support RLS:
import weaviate
import json
client = weaviate.Client("http://localhost:8080")
# Define a document with RLS-relevant metadata
document = {
"title": "Q3 Financial Report",
"content": "Detailed analysis of Q3 earnings...",
"tenant": "acme-corp",
"department": "finance",
"clearance_level": "senior"
}
# Add to Weaviate with the appropriate tenant context
client.data_object.create(
data=doc,
collection="RAG_Documents",
tenant="acme-corp"
)
Building the Secure Query Pipeline
When performing a RAG query, you must apply two layers of filtering: tenant isolation and row-level security. The application layer should intercept the user’s context and dynamically construct the filter query.
def secure_rag_query(client, user_context, query_text):
tenant = user_context["tenant"]
department = user_context["department"]
# Construct the filter for RLS
rls_filter = {
"where": {
"path": ["department"],
"operator": "Equal",
"valueText": department
}
}
# Perform the hybrid search with tenant and filter
results = client.query.get(
collection="RAG_Documents",
properties=["title", "content"],
limit=5
).with_tenant(tenant).with_where_filter(rls_filter).with_nearest_neighbor(
query_text
).do()
return results
Best Practices for Enterprise Deployment
First, never trust client-side filters. Always enforce RLS on the server side using the Weaviate Python or Go client. Second, use consistent naming conventions for tenant IDs to avoid conflicts. Third, monitor query latency, as complex filters can impact performance; consider pre-computing embeddings for filtered subsets if necessary. Finally, implement logging to track which documents are accessed by which users for audit compliance.
Conclusion
Combining Weaviate’s multi-tenancy with robust Row-Level Security provides a powerful foundation for enterprise RAG systems. By isolating indexes per tenant and enforcing fine-grained access control via metadata, you can deliver personalized, secure knowledge retrieval that scales with your business. As AI applications become more central to enterprise operations, mastering these security patterns is no longer optional—it’s essential.