🚐 Weaviate — The Crossover That Ships Fully Loaded
Every option already installed: vectorization, hybrid search, multi-tenancy. One question — who services all those options?
Under the hood: a Go core under BSD-3-Clause, one container to start. Three protocols: REST, GraphQL, and gRPC, with the v4 client using gRPC as its fast lane. The philosophy: "bring your data, the factory does the rest" — vectorizer modules, hybrid search, and generation are toggled by config, not external code.
Where it shines:
- Factory vectorization: attach a module (local transformers, OpenAI, Cohere, multimodal CLIP) and the database embeds on insert. Contrast with the scooter: there the embedder is hardwired; here it's a factory option you choose
- Hybrid, factory-grade: BM25F (the lexical index is already inside) + vectors in a single query, with the alpha knob balancing semantics vs keywords. No separate sparse embedder needed — unlike the hatchback
- Native multi-tenancy — the signature feature: tenant = its own shard, isolated search and performance, active/inactive tenants with cold storage. For a SaaS, this is what other databases make you assemble from payload filters
- Generative search: RAG inside the database — a generative module builds the answer right next to the retrieved objects
- Cross-references between objects: a mini-graph inside your vector DB (Neo4j ambitions excluded)
- Above-average operations: built-in backups to S3/GCS/Azure/disk, Prometheus metrics, OIDC auth. The hatchback with its manual snapshots is jealous
Where it stalls:
- Factory options need a service bay: self-hosted vectorization modules are separate containers — each eats RAM and needs babysitting. API modules put an external-provider dependency inside your database
- No transactions "like in Postgres": batches are not ACID operations
- Schema changes: some apply on the fly, but switching the vectorizer or a property type = re-import. Design the schema before loading
- Distributed mode exists, but the tuning is plentiful: ef, efConstruction, quantization (scalar/product/binary) trading recall
What breaks if you skip the manual:
- AutoSchema: by default the schema is inferred from the first objects — "the factory assembles the trim based on your first order." Wrong types in the first batch and you live with them or re-import. In production, define the schema explicitly
- Changing the vectorizer module — or a model deprecation — means a full re-embedding. That's an operation, not a flag
- Multi-tenancy is enabled at collection creation. Once on, queries must pass a tenant: an inactive one returns an activation error — not a search across everything. A feature, but a first-time surprise
- Python clients v3 and v4 are incompatible: half the tutorials online use v3 syntax that won't run on v4 (even the terms changed: classes → collections). Install v4 (
pip install -U weaviate-client) and keep the migration guide handy - Hybrid alpha: 0 = pure vector, 1 = pure BM25 (keyword). Don't copy the default from tutorials — run evals on your own queries
Cost of ownership: free under BSD-3-Clause. Weaviate Cloud (serverless/enterprise) — managed, if you'd rather outsource the service bay. Embedded mode for local tests and CI.
Mechanics & parts: Weaviate B.V. (Amsterdam) behind it; excellent docs, an active community. "Weaviate engineers" are as rare on the market as "Qdrant engineers" — but a backend developer can drive this one.
✅ Take it if: a SaaS with many tenants, you want a BM25+vector hybrid without rolling your own sparse embedder, or you want vectorization and generation bundled in one database
❌ Pass if: your logic lives in SQL (the station wagon), you're counting billions with GPUs (the train), or your ops budget is minimal — a fully-loaded crossover needs a service bay
Test drive:
# weaviate-client v4: pip install -U weaviate-client
# embedded mode downloads binaries once; in production — Docker or Weaviate Cloud
import weaviate
from weaviate.classes.config import Configure, Property, DataType
from weaviate.classes.query import Filter
client = weaviate.connect_to_embedded()
docs = client.collections.create(
"Document",
vectorizer_config=Configure.Vectorizer.none(), # we bring vectors ourselves (hatchback-style)
properties=[
Property(name="title", data_type=DataType.TEXT),
Property(name="year", data_type=DataType.INT),
],
)
docs.data.insert(
properties={"title": "Quarterly report 2024", "year": 2024},
vector=embed("Quarterly report 2024"), # embed() — your embedding function
)
# hybrid: BM25F + vectors in one query
# alpha = balance: 0 — vector, 1 — BM25; tune on evals
res = docs.query.hybrid(
query="annual financial summary",
vector=embed("annual financial summary"),
alpha=0.5,
filters=Filter.by_property("year").equal(2024),
limit=10,
)
for obj in res.objects:
print(obj.uuid, obj.metadata.score, obj.properties["title"])
A crossover wins neither with the hatchback's speed nor the scooter's simplicity — it wins on equipment: a whole RAG factory inside one database. You don't pay for the steering wheel; you pay for the service bay that keeps the options running.
Top comments (0)