Problem Context
Weaviate is the open-source vector database that pioneered "bring your own model" via vectorizer modules and now (versions 1.27 and 1.28 in late 2025) ships first-class multi-tenancy, dynamic indexes that flip from flat to HNSW automatically as a tenant grows, and async replication for cross-region reads. By 2026 it's the most popular self-hostable vector DB for teams that want Pinecone's shape without a SaaS bill or vendor lock-in.
Weaviate earns its place when you need on-prem / sovereign deployment, hybrid search out of the box, GraphQL or gRPC clients, and thousands-to-millions of tenants each with their own index. It loses when you want zero ops (use Pinecone) or already have Postgres and small data (use pgvector).
- You need a vector DB you can run inside your own VPC
- You have 10k tenants and Pinecone's pricing makes you wince
- You want hybrid (BM25 + vector) in one query, not glued together in app code
- You don't know the difference between a "collection" and a "tenant" in Weaviate
Multi-tenancy + dynamic indexes change the calculus for SaaS RAG. Here's the playbook.
Concept Explanation
Weaviate organizes data into collections(formerly "classes") โ each collection has a schema, a vectorizer config, and an index config. Within a multi-tenant collection, each tenant gets its own physical shard with isolated storage and indexes.
- Vectorizer modules โ
text2vec-openai,text2vec-cohere,text2vec-transformers, ornone(BYO vectors). Configure once on the collection. - Dynamic index โ starts as a flat (brute-force) index, auto-converts to HNSW once a tenant exceeds a threshold. Saves memory for the long tail of small tenants.
- Hybrid search โ
alphaparameter (0 = pure BM25, 1 = pure vector) blends in one call. - gRPC API โ default in v4 clients, ~3-5x faster than REST for batch operations.
flowchart LR
APP["App"] -->|gRPC| WV["Weaviate"]
WV --> COLL["Collection: Article<br/>(multi-tenant: true)"]
COLL --> T1["Tenant: acme<br/>(HNSW, hot)"]
COLL --> T2["Tenant: globex<br/>(flat, small)"]
COLL --> TN["Tenant: ...<br/>(dynamic)"]
WV --> MOD["Vectorizer module<br/>text2vec-openai"]
style WV fill:#0078D4,color:#fff,stroke:#005a9e
style MOD fill:#16a34a,color:#fff,stroke:#15803d
Implementation
Step 1: Define a multi-tenant collection
// Weaviate.Client (community .NET SDK) wraps the REST + gRPC API
var client = new WeaviateClient(new Config("https", "weaviate.example.com:443"));
await client.Schema.CreateClassAsync(new WeaviateClass
{
Class = "Article",
Description = "Customer KB articles, multi-tenant",
Vectorizer = "text2vec-openai",
ModuleConfig = new {
text2vec_openai = new { model = "text-embedding-3-small", type = "text" }
},
MultiTenancyConfig = new { enabled = true, autoTenantCreation = true },
VectorIndexType = "dynamic", // flat โ HNSW automatically
VectorIndexConfig = new { threshold = 10000 },
Properties = new[] {
new Property { Name = "title", DataType = new[] { "text" } },
new Property { Name = "content", DataType = new[] { "text" } },
new Property { Name = "category", DataType = new[] { "text" },
Tokenization = "field" }
}
});Step 2: Add tenants and ingest in batch (gRPC)
await client.Schema.AddTenantsAsync("Article",
new[] { new Tenant { Name = "acme" }, new Tenant { Name = "globex" } });
// Batch insert is gRPC under the hood โ keep batches at 100-200 objects
var batch = client.Batch.ObjectsBatcher();
foreach (var doc in docs)
{
batch.WithObject(new WeaviateObject
{
Class = "Article",
Tenant = "acme",
Id = Guid.NewGuid().ToString(),
Properties = new {
title = doc.Title,
content = doc.Body,
category = doc.Category
}
// No 'vector' field โ server vectorizes via text2vec-openai
});
}
await batch.RunAsync();Step 3: Hybrid search with the alpha knob
var hits = await client.GraphQL.GetAsync(new GetQuery
{
Class = "Article",
Tenant = "acme",
Hybrid = new HybridArgument
{
Query = userQuery,
Alpha = 0.7f // 0.7 weight to vector, 0.3 to BM25
},
Limit = 10,
Fields = new[] { "title", "category", "_additional { score id }" }
});Step 4: Filtered semantic search
var results = await client.GraphQL.GetAsync(new GetQuery
{
Class = "Article",
Tenant = "acme",
NearText = new NearTextArgument {
Concepts = new[] { "kubernetes upgrade procedure" }
},
Where = Filter.And(
Filter.Equal("category", "ops"),
Filter.GreaterThan("publishedAt", "2026-01-01T00:00:00Z")),
Limit = 5,
Fields = new[] { "title", "content" }
});Step 5: Generative search (RAG in one call)
// generative-openai module turns the top-k into an LLM answer server-side
var answer = await client.GraphQL.GetAsync(new GetQuery
{
Class = "Article",
Tenant = "acme",
NearText = new NearTextArgument { Concepts = new[] { userQuery } },
Limit = 5,
Generate = new GenerateArgument {
SingleResultPrompt = "Summarize this article: {content}",
GroupedTask = $"Answer the question using these sources: {userQuery}"
},
Fields = new[] { "title", "_additional { generate { groupedResult } }" }
});Step 6: Offboard a tenant
// One call drops the tenant's shard and all its vectors
await client.Schema.DeleteTenantsAsync("Article", new[] { "acme" });Common Pitfalls
- Single-tenant collection for SaaS. Putting all customers in one shard means index rebuilds touch everyone. Enable multi-tenancy from day one โ converting later requires a re-import.
- Static HNSW for thousands of small tenants. Each HNSW index has a fixed memory cost. Use the dynamic index so cold tenants stay flat (cheap) until they grow.
- REST instead of gRPC for batch. v4 clients default to gRPC. Sticking with REST batches costs you 3-5x throughput.
- Ignoring
autoTenantCreation. Without it, every new tenant requires an explicitAddTenantsAsynccall before the first write โ easy to forget in app onboarding code. - Trusting the wrong vectorizer at scale. Server-side vectorization simplifies code but couples you to one model and makes re-embedding painful. For large or volatile workloads, vectorize client-side and use
vectorizer: none. - Hybrid
alphaset and forgotten. The right blend is dataset-dependent. Eval with a labelled set; don't ship 0.5 because it sounds neutral.
Practical Takeaways
- Multi-tenancy + dynamic index is the right default for SaaS RAG on Weaviate.
- Use gRPC clients (v4+) โ REST is for one-off scripts.
- Hybrid search is one parameter, not two queries โ tune
alphaper dataset. - Generative modules let Weaviate produce the LLM answer in the same round trip when latency matters.
- Tenant offboarding is one call:
DeleteTenants. - Self-host when sovereignty / cost matters; use Weaviate Cloud when it doesn't.
- For < 1M vectors and one tenant, pgvector is simpler. Weaviate shines past that.

