A schema mismatch in Weaviate rarely surfaces as a clear exception at write time. More often, production queries return empty result sets or near-vector searches silently degrade, and Weaviate schema mismatch debugging turns into forensic reconstruction of class definitions that drifted from your code. The following ordered path is the one we use to isolate, reproduce, and fix these issues without extended downtime.
1. Confirm the Symptom Is a Schema Mismatch
Not every empty query is a schema problem. Before starting Weaviate schema mismatch debugging, rule out vectorizer outages, network partitions, and client-side filter bugs. Hit the schema endpoint directly:
curl -X GET "http://localhost:8080/v1/schema" \
-H "Authorization: Bearer $WEAVIATE_API_KEY" | jq '.classes[] | {class: .class, properties: [.properties[].name]}'
If a class you expect is missing, or properties have unexpected dataType, you have a mismatch. A common red herring is a vectorizer config pointing to a deleted module; that fails at import, not at query. Check object counts and whether vectors exist:
curl "http://localhost:8080/v1/objects/Article?limit=1" \
-H "Authorization: Bearer $WEAVIATE_API_KEY" | jq '._additional'
If _additional lacks vector, the class imported without embeddings.
2. Capture the Live Schema and the Expected Schema
Pull the live schema with the Python client and compare to your version-controlled schema definition. Store expected schema as JSON in Git so every environment has a source of truth.
import weaviate, json
client = weaviate.Client("http://localhost:8080", auth_client_secret="TOKEN")
live = client.schema.get()
with open("live_schema.json", "w") as f:
json.dump(live, f, indent=2)
A minimal diff script:
import json
live = json.load(open("live_schema.json"))
expected = json.load(open("expected_schema.json"))
live_classes = {c["class"]: c for c in live["classes"]}
for c in expected["classes"]:
name = c["class"]
if name not in live_classes:
print(f"MISSING CLASS: {name}")
continue
live_props = {p["name"]: p for p in live_classes[name].get("properties", [])}
for p in c.get("properties", []):
if p["name"] not in live_props:
print(f"MISSING PROP {name}.{p['name']}")
elif live_props[p["name"]]["dataType"] != p["dataType"]:
print(f"TYPE MISMATCH {name}.{p['name']}: live {live_props[p['name']]['dataType']} expected {p['dataType']}")
Weaviate schema mismatch debugging hinges on this diff. Typical divergences: dataType of a property changed from ["text"] to ["string"]; vectorIndexType changed from hnsw to flat; moduleConfig lost text2vec-openai causing objects to have no vectors.
3. Identify the Specific Divergence
Weaviate does not allow altering property types in place. Knowing exactly what changed dictates the migration path.
Property Type and Tokenization
If you defined title as text but live is string, queries using tokenization will break. Example live property:
{
"name": "title",
"dataType": ["string"],
"tokenization": "word"
}
Expected:
{
"name": "title",
"dataType": ["text"],
"tokenization": "whitespace"
}
The string type is deprecated; text is required for full-text search.
Vectorizer and Module Config
A class might have been created with none vectorizer, then code assumes text2vec-cohere. The schema won’t show vectors unless you inspect an object. This is a frequent trigger for Weaviate schema mismatch debugging because the failure appears only when you call with_near_text.
Vector Dimension Drift
If you swapped embedding models (e.g., 1536-dim to 768-dim), existing objects keep old vectors. New imports use new dims, causing vector dimension mismatch errors. The class schema doesn’t expose dimensions; only the module config implies it. Record model versions in moduleConfig.
Index Parameters
hnsw efConstruction or maxConnections changes don’t break reads but alter recall. Not a true mismatch but worth noting during review.
4. Reproduce in an Isolated Environment
Never experiment on production. Launch a local Weaviate with Docker, mirroring production modules:
docker run -d -p 8080:8080 \
-e AUTHENTICATION_APIKEY_ENABLED=true \
-e AUTHENTICATION_APIKEY_ALLOWED_KEYS=localkey \
-e ENABLE_MODULES=text2vec-openai,text2vec-cohere \
semitechnologies/weaviate:1.23.0
Apply the exported live schema to this instance, then run the failing query from your app. If it fails identically, you’ve confirmed the mismatch. Pitfall: local Weaviate without the same vectorizer module will not reproduce vector generation issues. Use the same ENABLE_MODULES env.
Tradeoff: reproducing with full production data may be impossible due to PII. Sample 1k objects using cursor:
cursor = None
for _ in range(10):
res = client.query.get("Article", ["title"]).with_limit(100)
if cursor: res = res.with_after(cursor)
batch = res.do()
# store batch
cursor = batch["data"]["Get"]["Article"][-1]["_additional"]["id"]
That’s enough to trigger the error.
5. Remediate Without Losing Data
Weaviate’s schema API rejects PATCH on property types. You must create a new class with the correct schema and migrate objects. This phase of Weaviate schema mismatch debugging requires careful coordination.
Step A: Create Corrected Class
new_class = {
"class": "Article_v2",
"vectorizer": "text2vec-openai",
"properties": [
{"name": "title", "dataType": ["text"], "tokenization": "whitespace"},
{"name": "body", "dataType": ["text"]}
]
}
client.schema.create_class(new_class)
Step B: Dual-Write or Backfill
For zero downtime, change app to write to both Article and Article_v2 (dual-write). Then backfill historical objects:
def migrate(obj):
client.data_object.create(obj["properties"], "Article_v2",
vector=obj.get("vector"))
# iterate with cursor as above
If vector dimensions changed, you cannot copy vectors; you must re-embed. That means calling your embedding service per object.
Step C: Swap Reads
After backfill, point reads to Article_v2. Monitor error rates. Then drop old class:
client.schema.delete_class("Article")
Tradeoff: dual-write complicates rollback; if new class has bugs, you’ve polluted it. Alternative is maintenance window with full migrate—simpler but downtime.
6. Validate Queries and Vector Consistency
Run near-vector queries against both old and new classes with same vector:
near_vector = {"vector": [0.1, -0.2, 0.05]}
old_res = client.query.get("Article", ["title"]).with_near_vector(near_vector).do()
new_res = client.query.get("Article_v2", ["title"]).with_near_vector(near_vector).do()
If result counts diverge >1%, inspect missing objects’ vectors. A frequent cause is that the old class used a different vectorizer; you cannot copy vectors if dimensions differ—you must re-embed. If you’re generating embeddings through a gateway, note that n4n.ai honors client routing directives and forwards provider cache-control hints, so you can pin a specific embedding model version to guarantee dimension stability during re-embedding.
Also validate hybrid queries and bm25 filters on text fields. A tokenization mismatch will return zero hits on where filters even when data exists.
7. Prevent Recurrence
Weaviate schema mismatch debugging is reactive; prevention is cheaper.
- Store schema as code (JSON/YAML) in repo.
- Add CI step that spins a scratch Weaviate container and applies expected schema, failing on diff.
- Alert on schema drift: a cron job that pulls live schema and compares to Git, paging on diff.
- Document embedding model versions in the class
moduleConfig. If you change models, bump class name suffix (_v3). - Never use
client.schema.updateexpecting type changes; it only updates metadata.
Common Pitfalls
- Assuming
client.schema.updatemodifies types—it doesn’t. - Ignoring
tenantschemas in multi-tenant classes; each tenant inherits but can override. - Forgetting that
textvsstringchanges tokenization, breakingbm25filters. - Copying objects without vectors after a vectorizer change, then wondering why
with_near_vectorfails.
Treat schema as immutable versioned artifacts. The moment you allow ad-hoc changes in production, you sign up for 3am incidents.
8. Operational Checklist
- Confirm symptom via
/v1/schemaand object inspection. - Diff live vs expected schema in code.
- Identify exact divergence (type, vectorizer, dims).
- Reproduce in Docker with same modules.
- Create new versioned class, dual-write, backfill.
- Validate query parity and vector dims.
- Swap reads, delete old class, close the loop in CI.
Following this ordered path makes Weaviate schema mismatch debugging a routine, low-risk operation rather than a fire drill.