We use cookies to enhance your experience on the site
CodeWorlds
Back to collections
Guide12 min read

Weaviate, meaning and keyword search in one query

Weaviate combines vector and keyword search with multi tenancy and built in vectorisers. Schema, queries, Flex pricing, and a Qdrant comparison.

Weaviate, meaning and keyword search in one query

Most vector databases do one thing: find fragments of similar meaning. The problem is that users also search for exact strings, and a vector captures neither an order number nor an error code. Weaviate has both mechanisms built in and combines them in a single query.

The second thing setting this database apart is multi tenancy built into the data model. An application serving hundreds of customers needs neither a separate instance per customer nor manual filtering by identifier, since isolation is part of the schema.

Collections and schema

Data lives in collections with declared properties. A schema is not mandatory in the strict sense, but going without removes filtering and indexing, so in practice you always define one.

Code
Python
import weaviate
from weaviate.classes.config import Property, DataType, Configure

client = weaviate.connect_to_local()

client.collections.create(
    "Document",
    properties=[
        Property(name="content", data_type=DataType.TEXT),
        Property(name="title", data_type=DataType.TEXT),
        Property(name="language", data_type=DataType.TEXT),
        Property(name="year", data_type=DataType.INT),
    ],
    vector_config=Configure.Vectors.text2vec_openai(),
)

The last line is what separates this database from options where you compute embeddings yourself. The database can call a vectoriser model on your behalf, so you insert plain text and never handle the transformation.

That is convenient and carries one consequence to think through. The database needs access to a model vendor and a key, and with a cloud vectoriser document content leaves your premises. For sensitive data you reach for a locally run vectoriser or compute embeddings in your own code and insert ready vectors.

Hybrid search

This is the main reason people choose this database, so it is worth understanding exactly what it does.

Code
Python
collection = client.collections.get("Document")

results = collection.query.hybrid(
    query="login error ERR_AUTH_401",
    alpha=0.5,
    limit=5,
    filters=Filter.by_property("language").equal("en"),
)

The database runs two searches in parallel, vector and keyword, then merges results by awarding points according to rank on each list. A fragment present in both ranks above one present in only one.

The parameter governing the balance matters most here. A value of zero is pure keyword search, one is pure meaning search, and values between mix the two. A starting point around the middle works for most cases, though technical documentation full of proper names deserves a shift towards keyword search.

The biggest benefit shows in mixed queries, where a user supplies a problem description together with a specific code. Meaning search alone loses the code, keyword search alone loses the description, and the combination finds the right fragment.

Multi tenancy

An application serving many customers faces the question of how to separate their data. A filter by identifier works until somebody forgets to add it, and then it is a leak.

Code
Python
client.collections.create(
    "CustomerDocument",
    multi_tenancy_config=Configure.multi_tenancy(enabled=True),
    properties=[Property(name="content", data_type=DataType.TEXT)],
)

collection = client.collections.get("CustomerDocument")
collection.tenants.create(["customer-118", "customer-119"])

customer_data = collection.with_tenant("customer-118")

Each tenant gets its own index shard, so a query physically cannot return another's data. That is a qualitative difference from filtering, since safety follows from structure rather than from discipline when writing queries.

The second advantage is cost management. Inactive tenants can be moved to a dormant state where their data drops to cheaper storage and occupies no memory. In a service with a thousand customers of whom a hundred are active, the saving is considerable.

The third is deletion. A customer leaves, you delete their tenant, and all their data vanishes in one call, with no hunting for entries by identifier. Under deletion on request requirements that property turns a procedure into a single call, instead of a script you then have to document.

Filtering and moving data in

Filters run alongside search rather than after it, so a condition narrows the candidate set instead of sieving finished results. That difference matters for correctness: with post filtering a narrow condition can leave an empty list despite matching entries in the database.

Code
Python
from weaviate.classes.query import Filter

results = collection.query.near_text(
    query="returns policy",
    limit=5,
    filters=(
        Filter.by_property("language").equal("en")
        & Filter.by_property("year").greater_or_equal(2025)
    ),
)

Conditions combine with logical operators, so a typical query with three restrictions fits in one expression. Mark properties used in filters as indexed, since without that the database scans them linearly.

Bulk loading is a separate matter. Inserting one object at a time across a hundred thousand fragments means a hundred thousand round trips, so batch insertion with error checking is the way.

Code
Python
with collection.batch.dynamic() as batch:
    for fragment in fragments:
        batch.add_object(properties=fragment)

if collection.batch.failed_objects:
    print(f"Not stored: {len(collection.batch.failed_objects)}")

Checking the failed object list is mandatory here. Batch insertion does not stop on a single error, so without that check some fragments quietly miss the index, and you notice only when a question goes unanswered.

The query agent

The database also exposes a layer that plans searching itself: it turns a natural language question into a sequence of queries, runs them, and assembles an answer.

That is convenient in a prototype, since it lifts search logic off you. It carries two consequences, though. The first is cost, since the agent is billed separately: the Free plan allows a thousand requests a month, while a separate subscription at 30 USD per month per organisation covers four thousand requests and lets you go past that on a usage basis. The second is predictability, since diagnosing a bad answer is harder when you do not know which queries ran.

The practical approach uses the agent to discover which queries work and then records that logic in your own code. You gain control and predictable cost.

Pricing

VariantCostWho it suits
Open source0 USDSelf hosted, full control
Free0 USD, no expiryOne cluster per account, learning and small projects
Flexfrom 45 USD monthlyPay as you go, no commitment
Premiumfrom 400 USD monthlyPrepaid contract, shared or dedicated deployment

Two things in that table changed against older write ups. The Sandbox that expired after two weeks gave way to a Free plan with no expiry date: one cluster per user, a hundred thousand objects, a gigabyte of memory and ten gigabytes of disk, one collection and at most three tenants, with an upgrade to a paid plan that keeps your data. The Plus plan that the price list used to carry between Flex and Premium is gone as well.

Billing in the managed version rests on three dimensions: vector dimensions (object count times index dimensionality times the replication factor), disk usage, and backup volume. Rates depend on index type, compression method, and on the cloud provider and region, so enabling quantisation lowers the invoice directly rather than only the memory footprint.

Embeddings are a separate item, and here everything depends on whose model you call. With a vectoriser pointing at an outside vendor, that vendor issues the bill rather than the database. With the Weaviate Embeddings service you pay the database operator per token, at the time of writing between 0.025 and 0.065 USD per million, and the Free plan carries an allowance of two thousand requests a day. When indexing a million fragments that item often exceeds the monthly cost of the database itself.

Maintenance and memory

A vector index stays fast while it fits in memory and slows sharply once it starts being read from disk. That is the most common cause of a database working well for six months and then suddenly not.

The starting point for an estimate is vector size times fragment count, plus index structure overhead. A million vectors of dimensionality one thousand five hundred thirty six is around six gigabytes of data alone, and the index adds its own.

The database offers several ways to reduce that demand. Vector compression, both by lowering precision and by quantisation, can cut occupancy severalfold at a small accuracy cost. Enable it when creating the collection, since changing it later requires rebuilding the index.

The second mechanism moves rarely used data to a cheaper tier. In a multi tenant arrangement that happens at tenant level, which is more convenient than deciding about individual objects.

The third is replication, needed less for performance than for availability. A single instance means a machine failure is a search outage, so for a production deployment price a second copy and compare it against the cost of downtime.

Weaviate against the alternatives

ToolStrengthWeaknessPick it when
WeaviateHybrid and multi tenancy included, vectorisersMore concepts to learnMulti customer service, mixed search
QdrantPerformance and filtering, simple modelMulti tenancy through filtersHigh volume, complex filters
ChromaSimplest startFewer production capabilitiesPrototype and early production
pgvectorVectors alongside relational dataTuning is yoursProject already on PostgreSQL

Choosing between the first two rows comes down to whether you need multi tenancy and hybrid search as ready mechanisms. If so, the first row saves weeks of work. If you build one search for one application, the second's simpler model is often better.

The last row deserves consideration whenever the application uses PostgreSQL anyway and fragments number in the tens of thousands. An extension to the existing database saves a whole separate service to maintain.

One thing escapes notice most often in such comparisons: the choice of database rarely decides search quality. How documents are split, which embedding model is used, and whether filters exist affect relevance more than the differences between these tools. You change database when a specific capability is missing, multi tenancy or hybrid search for instance, not because the results are poor.

The second practical conclusion concerns the order of work. Start with the simple option and measure relevance on your own questions, recording how often the right fragment landed in the top three. Only once you know where the problem sits should you reach for a more elaborate database, since otherwise you add complexity without knowing whether it fixes anything. Migration between these databases is fairly simple anyway, because the data lives in source files and embeddings can be recomputed.

Reranking results

Hybrid search returns a candidate list ordered by similarity, which does not always match what is most helpful. A separate reranking model scores the question and fragment pair together, so it sees more than the distance between vectors.

The arrangement runs like this: the database returns twenty candidates, the reranker scores each one, and the best five reach the prompt. It costs one extra call per query and usually improves accuracy noticeably.

Check whether it actually improves things in your case, though, since it is not free. Across thirty real questions, compare accuracy without reranking and with it. If the difference is slight, you save a call and the latency.

A cheaper and often sufficient alternative raises the number of returned fragments while tightening filters. Often the problem lies not in the ordering but in the right fragment being absent from the returned set altogether.

Common mistakes

The first is relying on meaning search for identifier queries. An order number and an error code are keyword search work, so without hybrid or a filter the database returns something similar instead of the right thing.

The second is filtering by customer identifier rather than using multi tenancy. It works until somebody writes a query without the filter, and then it is a leak between customers.

The third is a cloud vectoriser on data that cannot leave the company. The database then sends document content to a model vendor, which gets overlooked because it happens outside your code.

The fourth is indexing whole documents rather than fragments. A vector computed over twenty pages describes everything and nothing in particular, and the model then receives a wall of text.

The fifth is leaving the balance parameter at its default without checking. The right value depends on content character, and the accuracy difference often exceeds that between two embedding models.

The sixth is having no test set. Thirty real questions with the expected fragment marked let you compare settings, and without them tuning comes down to impressions.

FAQ

How does Weaviate differ from Qdrant?

Weaviate has hybrid search, multi tenancy, and vectorisers calling a model on your behalf built in. Qdrant emphasises performance and advanced filtering on a simpler data model. If you build a service for many customers, the first saves work; if one high volume search, the second is often faster.

Is Weaviate free?

The open source version is, and you run it yourself with no licence fees. The managed version has a Free plan with no expiry date, capped at one cluster and a hundred thousand objects, and paid plans start at forty five dollars a month, billed by vector dimensions, disk, and backups.

What does hybrid search give me?

It merges meaning and keyword results, so it finds both fragments of similar sense and those containing an exact string. The difference shows most in technical documentation full of proper names, codes, and numbers, where vector search alone fails.

Must I compute embeddings myself?

No, the database can call a vectoriser model for you, so you insert plain text. For sensitive data, though, compute embeddings in your own code or use a locally run vectoriser, since the cloud variant sends document content to a model vendor.

How does it work across many customers?

Through multi tenancy built into the collection, where every tenant holds its own index shard. A query in one tenant's context physically cannot reach another's data, so isolation follows from structure rather than from remembering a filter. Inactive tenants can be made dormant so they occupy no memory.

Documentation sits on the project site, and billing details appear in the pricing update post.