Multi-Tenancy in Vector Databases: Isolating Data for Different Users and Organizations
Learn three strategies for isolating tenant data in vector databases — namespaces, metadata filtering, and separate indexes — with tradeoffs for security, performance, and cost at scale.
Why Multi-Tenancy Matters for AI Applications
Any production AI application serving multiple customers needs data isolation. A customer support bot must not surface Company A's internal documents when Company B asks a question. A RAG-powered SaaS must ensure that each tenant's proprietary knowledge stays private. Getting multi-tenancy wrong is not just a performance issue — it is a data breach.
Vector databases add complexity to multi-tenancy because the ANN search algorithm operates on the entire index. Unlike relational databases where a WHERE clause neatly scopes a query, vector search must be architecturally designed to respect tenant boundaries.
Strategy 1: Namespace-Based Isolation
Most managed vector databases support namespaces — logical partitions within a single index. Each namespace has its own set of vectors and is searched independently.
from pinecone import Pinecone
pc = Pinecone(api_key="your-key")
index = pc.Index("multi-tenant-app")
def ingest_for_tenant(tenant_id: str, documents: list[dict]):
vectors = []
for doc in documents:
vectors.append({
"id": f"{tenant_id}_{doc['id']}",
"values": embed(doc["content"]),
"metadata": {"title": doc["title"], "source": doc["source"]}
})
index.upsert(vectors=vectors, namespace=tenant_id)
def search_for_tenant(tenant_id: str, query: str, top_k: int = 10):
query_vec = embed(query)
return index.query(
vector=query_vec,
top_k=top_k,
namespace=tenant_id,
include_metadata=True
)
Pros:
- Strong isolation — queries cannot cross namespace boundaries
- No metadata filter overhead — the database only searches the tenant's vectors
- Simple to implement and reason about
Cons:
- Some databases limit the number of namespaces per index
- Cannot search across tenants (if needed for admin or analytics features)
- Index-level settings (dimension, metric) apply to all namespaces
Best for: SaaS applications with moderate tenant counts (hundreds to low thousands) where cross-tenant search is never needed.
Strategy 2: Metadata Filtering
Store all tenants' vectors in a single namespace and filter by a tenant_id metadata field at query time.
def ingest_shared(tenant_id: str, documents: list[dict]):
vectors = []
for doc in documents:
vectors.append({
"id": f"{tenant_id}_{doc['id']}",
"values": embed(doc["content"]),
"metadata": {
"tenant_id": tenant_id,
"title": doc["title"],
"source": doc["source"]
}
})
index.upsert(vectors=vectors)
def search_shared(tenant_id: str, query: str, top_k: int = 10):
query_vec = embed(query)
return index.query(
vector=query_vec,
top_k=top_k,
filter={"tenant_id": {"$eq": tenant_id}},
include_metadata=True
)
Pros:
- No limit on number of tenants
- Can search across tenants for admin features by removing the filter
- Single index to manage
Cons:
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
- Weaker isolation — a bug that omits the filter leaks data across tenants
- Performance degrades if the filter is not selective (one tenant with 90% of the data)
- Every query pays the metadata filtering cost
Best for: Applications with many tenants (thousands+) where data volumes per tenant are relatively even and cross-tenant search is occasionally needed.
Strategy 3: Separate Indexes per Tenant
Create a dedicated index for each tenant. This provides the strongest isolation but the highest operational overhead.
def create_tenant_index(tenant_id: str):
pc.create_index(
name=f"tenant-{tenant_id}",
dimension=1536,
metric="cosine",
spec=ServerlessSpec(cloud="aws", region="us-east-1")
)
def search_tenant_index(tenant_id: str, query: str, top_k: int = 10):
tenant_index = pc.Index(f"tenant-{tenant_id}")
query_vec = embed(query)
return tenant_index.query(
vector=query_vec,
top_k=top_k,
include_metadata=True
)
Pros:
- Strongest possible isolation — no shared infrastructure between tenants
- Per-tenant performance tuning (different index sizes, configurations)
- Simplest compliance story for regulated industries
Cons:
- Operational complexity scales linearly with tenant count
- Higher cost — each index has base infrastructure costs
- Index management (creation, deletion, scaling) becomes a service in itself
Best for: Enterprise applications with few large tenants, strict compliance requirements (HIPAA, SOC 2), or tenants with vastly different data volumes.
Multi-Tenancy in pgvector
PostgreSQL's native features make multi-tenancy straightforward with pgvector:
-- Row-level security for automatic tenant filtering
CREATE POLICY tenant_isolation ON documents
USING (tenant_id = current_setting('app.current_tenant'));
-- Set tenant context before queries
SET app.current_tenant = 'acme-corp';
SELECT id, title, embedding <=> query_vec AS distance
FROM documents
ORDER BY distance
LIMIT 10;
-- RLS automatically filters to acme-corp's documents
def search_with_rls(tenant_id: str, query_vec: list[float], limit: int = 10):
conn.execute("SET app.current_tenant = %s", (tenant_id,))
return conn.execute("""
SELECT id, title, embedding <=> %s::vector AS distance
FROM documents
ORDER BY distance
LIMIT %s
""", (query_vec, limit)).fetchall()
Row-level security (RLS) is powerful because it works at the database engine level. Even if your application code has a bug and forgets to filter by tenant, RLS prevents data leakage.
Choosing a Strategy
| Factor | Namespaces | Metadata Filter | Separate Indexes |
|---|---|---|---|
| Isolation strength | Strong | Moderate | Strongest |
| Max tenant count | Hundreds | Unlimited | Tens |
| Operational cost | Low | Lowest | High |
| Cross-tenant search | No | Yes | Requires aggregation |
| Compliance | Good | Requires care | Best |
| Performance consistency | Good | Varies with data distribution | Best |
For most SaaS applications, start with namespaces. Move to separate indexes only if regulatory requirements demand it. Use metadata filtering when you need unlimited tenants or cross-tenant capabilities.
FAQ
Can a bug in my application code expose one tenant's data to another with the namespace approach?
Namespace isolation is enforced at the database level — a query against namespace "tenant-a" cannot return vectors from namespace "tenant-b" regardless of application code bugs. The risk is in your application routing logic: if a bug sends a user's query to the wrong namespace, they see another tenant's results. Validate tenant context early in your request pipeline, before the database call.
How do I handle shared knowledge that all tenants should access?
Create a shared namespace or a "global" tenant. At query time, search both the tenant's namespace and the shared namespace, then merge and re-rank results. In pgvector, use a UNION query across the tenant-specific rows and the shared rows, ordered by distance.
What is the performance impact of metadata filtering at scale?
With pre-filtering databases (Pinecone, Weaviate), metadata filtering adds 10-30% latency compared to unfiltered search for selective filters. The impact grows if the filter matches a very small fraction of vectors because the ANN index may need to explore more candidates to find enough matches. Namespaces avoid this overhead entirely because the ANN index only contains the tenant's vectors.
#MultiTenancy #VectorDatabase #DataIsolation #Security #Architecture #AgenticAI #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.