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).

๐Ÿค” Sound familiar?
  • 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, or none (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 โ€” alpha parameter (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

  1. 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.
  2. 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.
  3. REST instead of gRPC for batch. v4 clients default to gRPC. Sticking with REST batches costs you 3-5x throughput.
  4. Ignoring autoTenantCreation. Without it, every new tenant requires an explicitAddTenantsAsync call before the first write โ€” easy to forget in app onboarding code.
  5. 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.
  6. Hybrid alpha set 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 alpha per 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.