Storing in a Vector DB

Intermediate4 min

A namespace is a hard partition, not a tag. Changing the embedding model invalidates every vector, so plan the rebuild before you need it.

#rag
#vector-search

What a record in a vector store holds

Three parts: an identifier, the vector, and metadata.

The metadata is where most of the design work goes, because it is what you filter and cite by. Pinecone describes these as "extra key-value fields you filter on at query time," with two constraints worth knowing before you load a corpus: the object must be flat, with no nesting, and no null values.

Store the source text alongside the vector. An answer that cites nothing is an answer nobody can check, and going back to your primary database for every retrieved chunk turns one search into a search plus a fan-out of lookups. The duplication costs storage. It buys you a result you can render.

A workable record looks like this:

{
  "id": "handbook-2026#chunk-14",
  "values": [0.021, -0.118, ...],
  "metadata": {
    "text": "Refunds are issued to the original payment method...",
    "document_id": "handbook-2026",
    "section": "Billing",
    "updated_at": "2026-08-01",
    "tenant_id": "acme"
  }
}

The identifier is doing real work there. handbook-2026#chunk-14 is stable across re-runs, so ingesting the same document twice overwrites rather than duplicates, and a corrected document replaces its own chunks.

Partitioning with namespaces

Namespaces divide an index. Pinecone documents two uses: multitenancy, one namespace per customer, and faster queries, since a search scans only the relevant records.

The constraint matters more than the feature:

All upserts, queries, and other data read and write operations always target > one namespace.

A namespace is a hard partition, not a tag. Choosing one namespace per tenant is also choosing that you can never search across two tenants in one query. For per-customer isolation that is the point, and it gives you a deletion story that is one call rather than a filtered sweep.

It is the wrong tool when you might want to search across the boundary. Splitting by document type into policies and handbooks looks tidy until a question needs both, and then you are issuing two queries and merging ranked lists from different searches by hand. Use metadata for anything you might want to combine, and namespaces for boundaries you want enforced.

Why changing the embedding model invalidates everything

A query and a corpus must be embedded by the same model, because vectors from different models sit in different spaces. Comparing across them produces numbers that look like distances and mean nothing.

So there is no incremental migration. You cannot re-embed 10% of your corpus with a new model and serve mixed results, because the new vectors and the old ones are not comparable. The moment you change models, every stored vector is stale.

This catches teams who treat the embedding model as a tuning knob. It is closer to a schema decision.

Re-indexing without downtime

Build beside, then swap.

  1. Create a second index or namespace, with its own dimension count if the new model differs.
  2. Re-embed the corpus into it, while the old one keeps serving.
  3. Compare the two on a fixed set of queries. This is the step people skip, and it is the only evidence that the new model is an improvement.
  4. Point your application at the new index.
  5. Keep the old one long enough to roll back, then delete it.

Have your application read the index name from configuration rather than a constant. Step 4 is then a config change, and so is the rollback.

Keep your chunk identifiers stable across the rebuild. When they match, you can diff the two indexes chunk by chunk and see what moved.

Handling updates and deletes

Documents change, and a store you cannot correct becomes a store that confidently serves last quarter's policy.

Derive chunk identifiers from the document and position, as in document_id#n, so re-ingesting a document overwrites its chunks. Handle shrinkage: if version two produces 9 chunks where version one produced 12, chunks 10 through 12 survive as orphans unless you delete by document_id first and then write.

For deletions, filter by document_id and remove the matching records. This is the point of putting document_id in metadata even though no search needs it.

The same applies when a customer leaves. A namespace makes that one call; a metadata filter makes it a query and a delete. Decide which you need while you are designing the record, not when the request arrives.

Further reading

Knowledge check

Question 1 of 3

You split an index into namespaces by document type, `policies` and `handbooks`. A question arrives that needs both. What now?

Sign in to save your progress and pick up where you left off.