Choosing vector stores — Pinecone, Weaviate, Chroma, pgvector
Decision framework across managed Pinecone, Weaviate, local Chroma, Postgres pgvector, and Qdrant — architecture, how each works, ops contracts, tenancy, cost, tools, and exit plans.
Same job, different contracts
All serious options do vector insert + ANN query + metadata. You choose on ops, hybrid search, cost, team skills, and exit plan — not marketing “AI native” labels.
Read the flagship Vector databases — what, why, how first so HNSW / IVF / filters are not mysterious vendor jargon. This page is the buying and architecture companion: which box do you run, and why?
flowchart TD
Need[Need semantic retrieval?] --> Scale{Scale / team / SoT}
Scale -->|Laptop / class lab| Chroma[Chroma / Qdrant local]
Scale -->|Already on Postgres| PG[pgvector]
Scale -->|Managed SaaS, minimal ops| Pine[Pinecone / hosted Weaviate / Qdrant Cloud]
Scale -->|Schema + hybrid first-class| Weav[Weaviate / Elasticsearch / OpenSearch]
Scale -->|Self-host filters + Docker| Qdr[Qdrant / Weaviate self-host]
One-sentence decision rule
Pick the store that minimizes ops risk + data-model friction for your team’s next 12 months, while keeping an export/rebuild path so the vendor is not your source of truth.
What “choosing” actually decides
| Layer | You are choosing… | You are not choosing… |
|---|---|---|
| Math | Same cosine/dot/L2 + ANN family | A different embedding science |
| Ops | Who runs HA, backups, upgrades | Whether RAG is a good idea |
| Data model | SQL joins vs payload JSON vs GraphQL schema | Whether you need chunk ids |
| Cost curve | Always-on QPS vs spiky demos | Whether evals matter |
| Exit | How painful a rebuild is | Whether lock-in exists (it always does a little) |
If two stores both clear your must-haves, pick the one your team can debug at 2am.
Architecture: what every store shares
Before brand comparison, fix the shared architecture. Every production RAG path looks like this — only the middle box changes.
flowchart LR
subgraph WritePath[Write path]
Docs[Docs / tickets / PDFs] --> Chunk[Chunker]
Chunk --> EmbW[Embed worker]
EmbW --> Upsert[Upsert vectors + payload]
end
subgraph Store[Vector store]
Idx[(ANN index)]
Meta[(Payload / metadata)]
Upsert --> Idx
Upsert --> Meta
end
subgraph ReadPath[Read path]
Q[User query] --> Auth[Auth + tenant bind]
Auth --> EmbQ[Embed query]
EmbQ --> ANN[Filtered ANN]
Idx --> ANN
Meta --> ANN
ANN --> Pack[Top-k + hydrate]
Pack --> LLM[LLM]
end
Shared contracts you must keep portable
- Stable ids —
chunk_id/point_idthat survive re-embeds - Payload schema —
tenant_id, ACL fields,source_uri,doc_id, timestamps - Model stamp —
embedding_model_id+ dimension (never mix silently) - Delete semantics — hard delete vs tombstone; GDPR “forget this user” runbook
- Eval set — frozen queries with expected doc ids, independent of vendor
flowchart TB
SoT[(System of record<br/>Postgres / object store)] -->|text + metadata| Rebuild[Rebuild job]
Rebuild -->|same ids + model| AnyStore[(Any vector store)]
App[App] -->|reads SoT for citations| SoT
App -->|ANN only| AnyStore
Ship rule: the vector store holds a derived index. Canonical chunk text lives in your SoT. If the vendor disappears Monday, you rebuild by Friday.
How it works: the decision loop (not the brand pitch)
Teams that regret their choice usually skipped this loop:
flowchart TD
A[Inventory SoT + skills] --> B[List must-haves]
B --> C[Shortlist 2 stores]
C --> D[Spike: ingest 10k + filtered query]
D --> E[Measure p95 + recall@10]
E --> F{Must-haves pass?}
F -->|No| C
F -->|Yes| G[Cost model 12 months]
G --> H[Write exit/rebuild script]
H --> I[Design review + ship]
Must-haves vs nice-to-haves (write these down)
| Must-have (examples) | Nice-to-have |
|---|---|
| Server-bound tenant filters | Fancy console UI |
| Delete-by-doc in < N minutes | Built-in vectorizer modules |
| Export all payloads | Proprietary sparse formats |
| p95 < 80ms at target QPS | “AI-native” marketing |
| Fits existing compliance region | GraphQL if team is REST-only |
A store that wins on nice-to-haves and fails a must-have is a no.
Comparison (engineering view)
| Store | Best when | Strengths | Watch-outs |
|---|---|---|---|
| Chroma | Prototypes, local DX, teaching | Fast to first query | Multi-tenant prod maturity, ops |
| pgvector | You already run Postgres; want one DB | SQL filters + joins + TXNs | ANN ops, vacuum, huge pure-vector scale |
| Pinecone | Managed scale, minimal infra | Ops off your plate | Cost; less SQL join flexibility |
| Weaviate | Schema, hybrid, modules | GraphQL/REST, hybrid modules | Complexity if self-hosted |
| Qdrant | Strong payload filters + Docker | Filterable HNSW, clear APIs | “Another system” to run/backup |
| Elastic / OpenSearch | Already have search cluster | BM25+vector in one engine | JVM/ops weight; tune carefully |
| FAISS / local libs | Offline batch, research | Speed, control | Not a product DB alone |
Decision checklist (use in design docs)
- Where does source-of-truth relational data live? If Postgres — try pgvector before a second datastore.
- Do you need hybrid BM25+vector in one engine? Prefer Weaviate / Elastic / OpenSearch; or fuse in app with RRF (hybrid article).
- SLA and staffing? Managed Pinecone / Weaviate Cloud / Qdrant Cloud vs self-host.
- Tenant isolation? Separate indexes/collections vs shared + mandatory metadata filters + tests.
- Filter selectivity? Sparse tenants + strict ACL → verify filter-aware ANN, not post-filter only.
- Write path? Burst re-embeds, CDC from docs DB, GDPR deletes — who owns the runbook?
- Exit plan? Store raw chunks +
embedding_model_id+ stable ids so you can rebuild elsewhere. - Budget shape? Always-on managed QPS vs spiky student/demo traffic.
- Latency geography? Multi-region users → where does ANN live relative to embed + LLM?
- Compliance? Data residency, encryption at rest, audit logs — vendor docs vs your Postgres already approved.
flowchart LR
subgraph App
Ingest[Ingest worker]
Query[RAG query path]
end
subgraph Choice
PG[(Postgres+pgvector)]
Dedicated[(Pinecone/Qdrant/Weaviate)]
end
Ingest --> PG
Ingest --> Dedicated
Query --> PG
Query --> Dedicated
PG -.->|joins ACLs docs| Rel[(users/docs)]
Architecture deep dive by store
Chroma — architecture
Chroma is a developer-first embedding database: collections of embeddings + documents + metadata, with a Python/JS client that feels like a notebook library more than a clustered database.
flowchart TB
Client[Chroma client / SDK] --> API[Local server or embedded]
API --> Coll[(Collections)]
Coll --> Emb[Embedding vectors]
Coll --> Doc[Document text]
Coll --> MD[Metadata dicts]
API --> ANN[ANN backend]
How it works in practice
- Create a collection (optionally with a default embedding function — prefer BYO embeddings in products).
upsertids, documents, metadatas, embeddings.querywith a query embedding +wheremetadata filters.- Get distances + documents back for packing into an LLM prompt.
Tradeoffs
| Wins | Loses |
|---|---|
| Minutes to first RAG demo | HA / multi-node maturity vs dedicated engines |
| Teaching mental model | Strict multi-tenant prod patterns |
| Embedded or local server | Long-term ops story often “migrate later” |
Tools
chromadbPython package, Docker image for local server- LangChain / LlamaIndex Chroma integrations (wrap behind your own protocol)
- Simple persistence directory for demos — know where files live before you treat it as durable
pgvector — architecture
pgvector is a Postgres extension: vector type + distance operators + IVFFlat / HNSW index access methods. The “vector store” is your OLTP database.
flowchart TB
App[App] --> PG[(Postgres)]
PG --> Tables[docs / chunks / acls]
PG --> Ext[pgvector extension]
Ext --> Col[embedding vector N]
Ext --> Idx[HNSW / IVFFlat]
Tables --> SQL[SQL WHERE + JOIN]
Idx --> SQL
SQL --> Hits[Ordered rows]
How it works in practice
CREATE EXTENSION vector;addembedding vector(dim)columns.- Build
USING hnsworivfflatindexes with the right ops class (vector_cosine_ops, etc.). - Query with
ORDER BY embedding <=> $1 LIMIT kplus normalWHERE tenant_id = …. - Joins hydrate titles/URLs/ACLs without a second network hop.
Tradeoffs
| Wins | Loses |
|---|---|
| One backup, one auth, SQL filters | ANN load shares CPU/IO with OLTP |
| Transactions with doc metadata | Multi-region ANN is awkward |
| Team already knows Postgres | Pure vector scale (tens of M+) gets expensive on primary |
Full page: Postgres and pgvector.
When pgvector wins interviews: “We already had Postgres, ACLs in SQL, and <5M chunks — adding a second ANN SaaS was premature.”
Tools
pgvectorextension; managed on RDS/Aurora/Cloud SQL where supportedpsql,EXPLAIN (ANALYZE, BUFFERS)for query plans- Migration tools (Alembic, Flyway) to version schema + index params
- Connection poolers (PgBouncer) — ANN queries need pool sizing thought
Pinecone — architecture
Pinecone is a managed vector database SaaS: you create indexes (and often namespaces), upsert vectors with metadata, and query over HTTPS. You buy ops time, not a Docker Compose file.
flowchart LR
App[Your app] -->|HTTPS API| Pine[Pinecone control + data plane]
Pine --> Idx[(Distributed ANN index)]
Pine --> Meta[Metadata store]
App -->|SoT still yours| SoT[(Postgres / S3)]
How it works in practice
- Create an index with dimension + metric matching your embedding model.
- Upsert
(id, values, metadata)in batches; use namespaces for tenancy/versioning if that fits. - Query with vector + metadata filter; receive ids, scores, metadata.
- Hydrate full text from your SoT if you did not store large blobs in metadata.
Tradeoffs
| Wins | Loses |
|---|---|
| Minimal infra staffing | Cost at high QPS + storage |
| Fast path to multi-replica ANN | SQL joins are app-side |
| Clear product API | Exit requires disciplined SoT + export |
Tools
- Official Python/Node SDKs; REST API
- Console for index health and usage
- Your own
export_payloads.py— do not rely on console clicks for disaster recovery - Terraform / IaC providers where available for index lifecycle
Weaviate — architecture
Weaviate is a vector-native search engine with a schema (classes/properties), optional modules (vectorizers, Q&A), and strong hybrid (BM25 + vector) story. Cloud or self-host.
flowchart TB
Client[GraphQL / REST client] --> WV[Weaviate node s]
WV --> Schema[Class schema]
WV --> Inv[Inverted index BM25]
WV --> Vec[(Vector index)]
Schema --> Objects[Objects + properties]
Objects --> Inv
Objects --> Vec
Client --> Hybrid[hybrid search]
Inv --> Hybrid
Vec --> Hybrid
How it works in practice
- Define classes and properties (types, tokenization, vectorization config).
- Prefer bring-your-own embeddings in production so model choice is yours.
- Query with nearVector / hybrid / filters; GraphQL is common, REST also exists.
- Modules are optional power — do not couple your product to a module you cannot replace.
Tradeoffs
| Wins | Loses |
|---|---|
| Schema + hybrid first-class | Self-host complexity |
| Modules ecosystem | Overkill if you only need dumb ANN |
| Cloud option | Team must learn Weaviate concepts, not just SQL |
Tools
- Weaviate Cloud / open-source Docker images
- GraphQL playground; client libraries (Python, JS, Go)
- Backup modules / snapshots for self-host
- Schema migration discipline (treat like DB migrations)
Qdrant — architecture
Qdrant is a vector similarity search engine optimized for payload-filtered HNSW, with REST and gRPC, excellent Docker DX, and a clear points/collections model.
flowchart TB
Client[REST / gRPC] --> QD[Qdrant]
QD --> Coll[(Collections)]
Coll --> Points[Points: id + vector + payload]
Coll --> HNSW[HNSW segments]
Coll --> PayIdx[Payload indexes]
Client --> Search[Search + filter]
HNSW --> Search
PayIdx --> Search
How it works in practice
- Create a collection with vector size + distance.
- Upsert points with JSON payloads; index fields you filter on.
- Search with vector + filter conditions; Qdrant aims for filter-aware ANN.
- Snapshots / backups for self-host; or use Qdrant Cloud.
Tradeoffs
| Wins | Loses |
|---|---|
| Filterable HNSW + clear APIs | Another system to operate if self-host |
| Great Compose path for portfolios | Not SQL — joins stay in app/Postgres SoT |
| Cloud + OSS | You still own capacity planning on OSS |
Tools
- Official Docker image; Qdrant Cloud
- Python
qdrant-client, gRPC for throughput - Web UI for collections in local/dev
- Snapshot APIs for backup drills
Elastic / OpenSearch — architecture
These are search platforms that grew dense-vector / kNN support. If the company already runs them for logs or product search, adding vectors can beat introducing Pinecone.
flowchart TB
App --> ES[(Elastic / OpenSearch)]
ES --> Inv[Inverted index BM25]
ES --> kNN[kNN / HNSW fields]
ES --> Analyzers[Analyzers / synonyms]
App --> Hybrid[BM25 + kNN fusion]
Inv --> Hybrid
kNN --> Hybrid
How it works in practice
- Map a
dense_vector(or equivalent) field with dims + similarity. - Index documents with both text fields and vectors.
- Query with knn + bool filters; fuse with BM25 when IDs/SKUs matter.
- Respect JVM heap, shard counts, and circuit breakers — this is still a search cluster.
Tradeoffs
| Wins | Loses |
|---|---|
| One engine for keyword + vector | Heavy ops / JVM tuning |
| Existing analyzers and SRE skills | Easy to “checkbox” kNN without evals |
| Elastic Cloud / managed OS options | Overkill for a solo RAG MVP |
Tools
- Elasticsearch / OpenSearch clusters; Kibana / Dashboards
- Language clients; Search templates
- Snapshot/restore to S3-compatible storage
- Ranking eval notebooks against your frozen set
FAISS / local libraries — architecture
FAISS (and similar) are libraries, not product databases. They shine for offline eval, batch nearest neighbor, and research.
flowchart LR
Vectors[Numpy / memmap vectors] --> FAISS[FAISS index]
FAISS --> IDs[Neighbor ids]
IDs --> Join[Join to metadata in your DB]
How it works: build an index in-process or on disk; search returns integer ids; you own persistence, filters, HA, and multi-tenant safety.
Tradeoffs: maximum control and speed for batch; almost never the online multi-tenant store alone.
Tools: faiss-cpu / faiss-gpu, Annoy, ScaNN — pair with Postgres for metadata.
Deep enough on each option (product lens)
Chroma
- Use: notebooks, RAG-style labs, internal prototypes, teaching the mental model.
- Avoid as sole prod store until you have a clear HA/backup story — or treat it as a client over a stronger backend if that is where the project is headed.
- Great for teaching collection / upsert / query quickly.
- Portfolio tip: fine for demos; write the abstract
VectorStoreprotocol so you can swap later without rewriting ingest.
pgvector
- Keep vectors next to
docs,acls,tenants. - One backup story, one auth story, SQL you already know.
- Graduate when ANN QPS, index RAM, or multi-region ANN dominates Postgres primary health.
- Full page: Postgres and pgvector.
Pinecone
- Buy ops time: indexes, replicas, basic scaling.
- Model your metadata filters and namespaces/tenancy early.
- Watch unit economics at high QPS + large metadata; export chunks regularly so you are not trapped.
- Good default when the team is AI-product-heavy and infra-light.
- Ask vendors (and yourself): filter-aware search behavior under sparse ACL selectivity.
Weaviate
- Schema-first classes/properties; strong hybrid story.
- Modules for vectorizers (or BYO embeddings — usually better to control the model yourself).
- Self-host only if you want the ops; otherwise cloud.
- Fits teams that think in “search schema” rather than “SQL tables.”
Qdrant
- Excellent payload filtering + HNSW; pleasant Docker/Compose path for portfolios.
- Clear REST/gRPC; easy to wrap behind a
VectorStoreprotocol in Python. - Still: backups, upgrades, and capacity planning are yours if self-hosted.
- Strong choice when filters are the product (multi-tenant SaaS RAG).
Elastic / OpenSearch
- If the company already runs them for logs/search, adding dense vectors can beat introducing Pinecone.
- BM25 expertise transfers; dense vector scoring and kNN settings still need their own evals.
- JVM heap, shard strategy, and cluster ops are real — do not treat kNN as a checkbox.
FAISS / local libraries
- Excellent for batch similarity, offline eval, and research.
- Pair with a real DB for online serving unless you invent sharding, filters, and HA yourself (usually a bad idea for a product team).
Tenancy patterns
| Pattern | Pros | Cons |
|---|---|---|
| Collection / index per tenant | Hard isolation, easy drop | Many small indexes; ops sprawl |
Shared index + tenant_id filter |
Simple ops | Bug = cross-tenant leak; test hard |
| Namespace / partition features | Vendor-assisted isolation | Portability ↓ |
| Hybrid (big tenants isolated) | Balance cost vs risk | More code paths |
Ship rule: isolation bugs are security bugs. Add an automated test that tenant A’s query never returns tenant B payloads.
flowchart TD
Q[Query + auth context] --> Bind[Bind tenant_id / roles in server]
Bind --> F[Mandatory filter in store client]
F --> ANN[ANN]
ANN --> Assert[Assert all hits match tenant]
Assert --> Pack[Pack for LLM]
Never trust the client to pass tenant_id alone — bind it from the session on the server.
Tenancy decision tree
flowchart TD
T{Tenant count + size skew?}
T -->|Few large enterprise| Sep[Index / collection per tenant]
T -->|Many small similar| Shared[Shared + mandatory filter + CI]
T -->|Whale + long tail| Hybrid[Isolate whales; share long tail]
Shared --> Test[Isolation tests in CI]
Sep --> Cost[Watch empty-index cost]
Hybrid --> Complexity[Two code paths — document them]
Cost and lock-in without drama
- Persist canonical text + metadata in your own object store / Postgres — the vector DB holds a derived index.
- Version collections:
docs_v3_modelX_dim. - Script
export_payloads.py/rebuild_index.pyon day one of production. - Track $/1k queries and $/1M vectors / month in the same dashboard as LLM spend.
class VectorStore(Protocol):
def upsert(self, points: list[Point]) -> None: ...
def search(self, vector: list[float], k: int, filters: dict) -> list[Hit]: ...
def delete(self, ids: list[str]) -> None: ...
# Swap Chroma → Qdrant → Pinecone behind the protocol; keep ingest identicalCost shape questions for design review
| Question | Why it matters |
|---|---|
| Always-on QPS vs bursty demos? | Managed minimums punish spiky traffic |
| Metadata size per vector? | Some vendors bill storage aggressively |
| Re-embed frequency? | Backfill can dominate year-1 cost |
| Need replicas for HA? | Doubles index footprint |
| Cross-region readers? | Data transfer + duplicate indexes |
Rough TCO mental model
flowchart LR
subgraph Managed
M1[Index hours]
M2[Storage]
M3[Ops eng ≈ low]
end
subgraph SelfHost
S1[VMs / k8s]
S2[On-call eng]
S3[Backup drills]
end
subgraph Shared
R[Re-embed compute]
E[Embed API $]
end
Managed is not “more expensive” in the abstract — it is expensive when idle capacity dominates or when storage of fat metadata balloons. Self-host is expensive when on-call dominates.
Migration playbook (store A → store B)
- Keep SoT in Postgres / object store (already)
- Stand up B; backfill from SoT with same ids + model
- Shadow traffic: compare hit ids / scores on eval queries
- Flip read path behind a flag
- Keep A read-only for rollback window
- Decommission A after metrics + incident silence
Ship rule: never “migrate by changing the SDK in place” without a dual-read period.
sequenceDiagram
participant SoT
participant A as Store A
participant B as Store B
participant App
SoT->>B: Backfill same ids
App->>A: Production reads
App->>B: Shadow reads compare
Note over App: Flip flag
App->>B: Production reads
App->>A: Rollback window only
Dual-write vs rebuild
| Strategy | When | Risk |
|---|---|---|
| Rebuild from SoT | You have complete text + model stamp | Rebuild time; temporary lag |
| Dual-write | Live writes must appear in both | Two failure modes; drift |
| CDC / outbox | Docs DB is busy OLTP | Need idempotent upserts |
Prefer rebuild when you can afford a maintenance window; prefer outbox when deletes/updates must stay consistent continuously.
Worked example: 10-tenant SaaS RAG MVP
Constraints: 10 tenants, ~200k chunks total, team knows Postgres, need ACL filters, one engineer ops budget.
Reasonable pick: pgvector on existing Postgres — filters + joins, one backup. Add Redis exact cache in front. Revisit Qdrant/Pinecone when ANN p95 or index size stresses the primary.
Alternative: Qdrant Docker in staging with Postgres as SoT for docs/ACL; dual-write embeddings — if you expect to outgrow pgvector in months.
Five-bullet rationale (paste into design doc):
- SoT already Postgres → avoid second consistency story
- ACL joins are SQL we already audit
- 200k vectors is well inside HNSW comfort on a sized box
- Protocol wrapper keeps exit to Qdrant/Pinecone open
- Re-eval when p95 ANN or vacuum/bloat threatens OLTP SLOs
Worked counterexample: 50M chunks, 5 regions
pgvector-on-primary loses. Prefer managed ANN or self-hosted Qdrant/Weaviate near embedders, Postgres remains SoT, CDC/dual-write for embeddings, regional read replicas or regional collections for latency.
flowchart TB
subgraph Regions
R1[Region A ANN]
R2[Region B ANN]
R3[Region C ANN]
end
SoT[(Global SoT Postgres / object store)] --> Workers[Embed workers]
Workers --> R1
Workers --> R2
Workers --> R3
UsersA[Users A] --> R1
UsersB[Users B] --> R2
Worked example: university lab / portfolio
Constraints: one developer, demo traffic, need something on a resume that is not vapor.
Pick: Qdrant Compose + Postgres SoT + Redis cache, or pgvector if the whole app is already SQL. Chroma is fine for the first notebook week — graduate before the public demo.
Worked example: company already on Elastic
Constraints: SRE team lives in Elastic; BM25 product search exists; leadership skeptical of “another SaaS.”
Pick: add dense vectors / knn in Elastic or OpenSearch; keep Postgres for transactional ACL hydrate if needed. Prove recall with the same eval harness you would use for Pinecone.
Anti-patterns
- Picking Pinecone for a 5k-vector homework project
- Putting pgvector on an undersized Postgres already on fire
- Switching embedding models without a reindex budget
- Choosing purely on “highest GitHub stars”
- No export path for payloads / chunk text
- Hybrid “later” when your corpus is full of SKUs and ticket IDs
- One shared collection with optional
tenant_idfilter “for now” - Letting the frontend pass raw filter JSON to the store
- Storing only vectors in the vendor (no SoT text) — citations die on key rotation
- Benchmarking without filters (prod always has filters)
- Comparing vendors on unfiltered recall only
Interface contract (staff bar)
| Inputs | Stable chunk ids, embedding vectors, payload with tenant/ACL, query vector + server-bound filters |
| Outputs | Ranked hits with ids, scores, citation fields |
| Invariants | Same model/dim/metric for query and docs; no cross-tenant hits; deletes eventually invisible to search |
Three measurable metrics
- p95 search latency under realistic filters
- recall@10 on frozen eval set
- Cross-tenant isolation test = 0 leaks
Two degrade modes
- Dedicated store down → serve BM25 from Elastic/Postgres
tsvectoror cached FAQ - Embed API down → refuse semantic path; exact cache only
flowchart TD
Fail{Failure?}
Fail -->|ANN store down| BM25[Keyword / FAQ fallback]
Fail -->|Embed API down| Cache[Exact cache only]
Fail -->|Both| Soft[Soft error + support path]
BM25 --> User[User still gets something]
Cache --> User
Threat note
Attacker or buggy client omits tenant filter → another tenant’s chunks enter the LLM prompt. Catch at server-bound filters + assert-on-hits + eval gate, not at “please remember to filter” comments.
Production readiness checklist
- Abstract
VectorStoreprotocol in app code - SoT for text outside the vector vendor
- Tenancy tests in CI
- Backup/restore or vendor RPO/RTO understood
- Cost model spreadsheet attached to design doc
- Reindex / model-upgrade runbook
- Hybrid plan if corpus is ID-heavy
- Filter selectivity tested on sparse tenants
- Delete / GDPR path rehearsed once
- Shadow-read plan if you might migrate in 12 months
Interview prompts
- When would you pick pgvector over Pinecone?
- How do you avoid vendor lock-in for embeddings?
- Shared index vs per-tenant collections — tradeoffs?
- What breaks first when ANN and OLTP share one Postgres primary?
- How do you validate filter-aware search under 0.1% selectivity?
- Walk through a store A → store B migration without downtime.
- What belongs in the vector payload vs the relational SoT?
Strong answer shapes
| Question | Shape of a strong answer |
|---|---|
| pgvector vs Pinecone | SoT, team skills, scale threshold, exit |
| Lock-in | Derived index + model stamp + rebuild script |
| Tenancy | Isolation vs ops cost + mandatory tests |
| Shared primary | Vacuum, buffer cache, p95 OLTP vs ANN |
Feature matrix (deeper)
| Concern | pgvector | Pinecone | Qdrant | Weaviate | Chroma | Elastic/OS |
|---|---|---|---|---|---|---|
| SQL joins | Native | App-side | App-side | Limited | App-side | App-side |
| Hybrid BM25 | Via tsvector / app |
Limited / partner | Payload + sparse opts | Strong | Weak | Strong |
| Filter-aware ANN | SQL WHERE + index | Namespaces + metadata | Strong payload | Strong | Basic | kNN + filters |
| Local Docker DX | Postgres image | Cloud-first | Excellent | Good | Excellent | Heavy |
| Managed option | RDS/Aurora + ext | Yes | Cloud | Cloud | Limited | Elastic Cloud |
| Team skill fit | Backend/SQL | Product/AI | Platform | Search | Students | Search SRE |
| Backup story | PG dump / PITR | Vendor RPO | Snapshots / cloud | Modules / cloud | DIY | Snapshots |
| Multi-region | Hard on one primary | Vendor regions | You design | You design / cloud | Weak | Cluster design |
Scoring rubric for a design review
Score 1–5 each; pick the highest total that clears “must haves”:
- Fits existing SoT (Postgres?)
- Hybrid needs covered
- Ops staffing match
- Filter/ACL confidence
- Cost at 12-month projected QPS
- Exit/rebuild pain
- Latency geography
- Compliance / residency fit
Must-haves that fail → eliminate the option even if the total is high.
Example scored shortlist
| Criterion | pgvector | Qdrant Cloud | Pinecone |
|---|---|---|---|
| SoT fit | 5 | 4 | 4 |
| Hybrid | 3 | 3 | 2 |
| Ops match | 4 | 5 | 5 |
| ACL confidence | 5 | 4 | 4 |
| 12-mo cost | 5 | 3 | 2 |
| Exit | 5 | 4 | 3 |
| Geography | 2 | 4 | 4 |
| Total | 29 | 27 | 24 |
Numbers are illustrative — fill with your constraints. The value is forcing the debate onto a grid.
Local → staging → prod path
flowchart LR
Local[Chroma or Qdrant Compose] --> Staging[Same API as prod]
Staging --> Prod[Managed or HA self-host]
Ship rule: do not prototype on an API shape you will throw away. Prefer Qdrant/pgvector local if prod will be those; use Chroma only when the lesson is the mental model.
Environment parity checklist
| Env | Store | Embeddings | Data |
|---|---|---|---|
| Local | Compose / pgvector | Cheap/local model OK if dim matches | Synthetic tenants |
| Staging | Same engine as prod | Prod model | Anonymized subset |
| Prod | Managed or HA | Prod model + version stamp | Full + backups |
Dim mismatch between local and prod is a classic silent footgun.
Vendor questions to ask (or self-ask)
- Exact behavior of metadata filters under 0.1% selectivity?
- Backup / PITR / point-in-time restore of an index?
- Dimension change story?
- Sparse / hybrid vectors roadmap?
- Data residency regions?
- Export API for all payloads + vectors?
- Max metadata size per vector and billing impact?
- Hot/cold tiering or only full-RAM indexes?
- SLO credits and incident communication?
- How deletes propagate to ANN segments (visibility lag)?
Tools toolkit (what you actually install)
| Job | Tools |
|---|---|
| Local spike | Docker Compose (Qdrant/Weaviate/Chroma), Postgres+pgvector |
| Protocol wrapper | Python Protocol / interface in your language |
| Eval | Frozen JSONL queries; recall@k script; optional Ragas/custom |
| Load test | k6 / Locust against filtered search, not only health checks |
| Cost | Spreadsheet: vectors × replicas × QPS tiers |
| Observability | Store metrics + your p95 + embed latency + LLM latency |
| Secrets | Vendor API keys in secret manager — never in notebooks committed to git |
| IaC | Terraform/Pulumi for indexes where supported |
| Backup drill | Quarterly restore into a scratch project |
flowchart TB
Dev[Dev laptop] --> Compose[Compose vector store]
CI[CI] --> Tests[Isolation + recall tests]
Staging[Staging] --> SameAPI[Prod-shaped API]
Prod[Prod] --> Metrics[p95 / errors / cost]
SoT[(SoT)] --> Rebuild[rebuild_index job]
Rebuild --> Prod
Minimal VectorStore spike (shape only)
# Illustrative — wire real SDKs behind this in your repo
from typing import Protocol
class Hit(dict):
pass
class VectorStore(Protocol):
def upsert(self, points: list[dict]) -> None: ...
def search(self, vector: list[float], k: int, filters: dict) -> list[Hit]: ...
def delete(self, ids: list[str]) -> None: ...
def assert_tenant(hits: list[Hit], tenant_id: str) -> None:
bad = [h for h in hits if h.get("tenant_id") != tenant_id]
if bad:
raise RuntimeError("cross-tenant leak")The protocol is the product; the SDK is an adapter.
Portfolio / homework guidance
| Stage | Store |
|---|---|
| First RAG notebook | Chroma or Qdrant Compose |
| Capstone with Postgres ACL | pgvector |
| Resume bullet “prod-shaped” | Qdrant Compose + Postgres SoT + Redis cache |
| Company already on Elastic | kNN there before new SaaS |
Interviewers care more that you can explain filters, ids, evals, exit than that you used the trendiest managed service.
What to put on a resume bullet
Weak: “Used Pinecone for RAG.”
Strong: “Designed multi-tenant retrieval with server-bound filters, frozen recall@10 evals, and a rebuild-from-Postgres exit path across Qdrant and pgvector.”
Failure story bank (mention these)
- Chose Pinecone, stored only vectors, lost chunk text when rotating keys — could not cite
- Shared index, forgot filter in one code path — cross-tenant leak in staging
- IVFFlat default probes after bulk load — recall collapsed silently
- Migrated embed model in place — mixed dimensions; queries returned noise
- Benchmarked without filters — prod p95 5× worse under ACL predicates
- Prototyped on Chroma API, rewrote everything for prod Pinecone in a crunch
- Per-tenant indexes for 2,000 free-tier tenants — ops and cost exploded
- Deleted from SoT but forgot vector delete — stale chunks kept answering
Glossary
| Term | Meaning |
|---|---|
| SoT | System of record (canonical data) |
| Namespace | Vendor partition for tenancy/versioning |
| Dual-write | Write two indexes during migration |
| Filter-aware ANN | Search constrained by metadata during ANN |
| Exit plan | Ability to rebuild elsewhere from SoT |
| Payload | Metadata stored with the vector |
| Hydrate | Fetch full text/fields after ANN returns ids |
| Shadow read | Compare two stores on live traffic without serving B |
| RPO / RTO | Backup freshness / restore time objectives |
| Collection / index | Top-level vector container in most stores |
Decision tree (printable)
Already Postgres + <~5M vectors + team knows SQL?
YES → pgvector (revisit when OLTP SLOs hurt)
NO ↓
Need managed ops and budget OK?
YES → Pinecone / Qdrant Cloud / Weaviate Cloud
NO ↓
Need strong payload filters self-hosted?
YES → Qdrant
NO ↓
Already Elastic/OpenSearch?
YES → kNN + BM25 there
NO → Qdrant or Weaviate self-host; wrap VectorStore protocolTeaching/lab only → Chroma OK; still keep an exit protocol.
flowchart TD
Start[Start] --> PG{Postgres SoT + SQL team + modest scale?}
PG -->|Yes| PGv[pgvector]
PG -->|No| Man{Need managed ops + budget?}
Man -->|Yes| Cloud[Pinecone / Qdrant Cloud / Weaviate Cloud]
Man -->|No| Filt{Payload filters self-host?}
Filt -->|Yes| Qdrant
Filt -->|No| El{Already Elastic/OS?}
El -->|Yes| kNN[kNN + BM25 there]
El -->|No| Self[Qdrant or Weaviate self-host]
TCO sketch fields
| Line item | pgvector | Managed ANN |
|---|---|---|
| Extra cluster $ | Often $0 | Always-on index $ |
| Eng hours / month | Vacuum, indexes | Integration + cost watch |
| Re-embed jobs | Same | Same |
| Backup complexity | One DB | DB + vector snapshots |
| Incident surface | Shared with OLTP | Separate failure domain |
| Multi-region | Hard / replica tricks | Often clearer SKUs |
Pick with a 12-month spreadsheet, not a blog post title.
Sample spreadsheet rows
- Vector count now / at 12 months
- Avg metadata bytes × vectors × replicas
- Query QPS p50 / p95 / peak
- Re-embed jobs per year × embed $
- Eng hours for on-call / upgrades
- Cost of a 4-hour outage (business)
If row 6 dwarfs row managed fees, managed often wins.
Reference architectures (copy into Notion)
A — Postgres-centric MVP
Postgres (docs, ACL, pgvector) → Redis exact cache → LLM. Hybrid via tsvector + RRF in app when IDs matter.
flowchart LR
User --> API[API]
API --> Redis[(Redis)]
Redis -->|miss| PG[(Postgres + pgvector)]
PG --> API
API --> LLM
B — Split ANN
Postgres SoT → async embed worker → Qdrant/Pinecone → Redis → LLM. CDC or outbox for deletes.
flowchart LR
Docs[(Postgres SoT)] --> Outbox[Outbox / CDC]
Outbox --> Worker[Embed worker]
Worker --> ANN[(Qdrant / Pinecone)]
API[API] --> ANN
API --> Docs
API --> LLM
C — Search-platform-centric
OpenSearch/Elastic for BM25+kNN → Postgres for ACL joins on hydrate → LLM.
flowchart LR
API --> ES[(Elastic / OpenSearch)]
ES -->|ids| API
API --> PG[(Postgres ACL hydrate)]
API --> LLM
Draw your traffic arrows and failure domains before debating brands.
D — Lab / teaching
Chroma embedded or Qdrant single container; SQLite/Postgres optional; emphasize protocol + evals over HA.
FAQ (choosing stores)
Is managed always more expensive?
Not if eng time is scarce. It is if traffic is spiky and you pay for idle capacity — model the curve.
Can I use Chroma in production?
Only with eyes open on HA, backup, and tenancy. Many teams prototype in Chroma and productionize on Qdrant/pgvector/Pinecone behind the same protocol.
When is Elastic the obvious answer?
When the company already runs it well and needs BM25+vector with existing analyzers and ops.
Should I use the vendor’s built-in embedding module?
Usually no for products — BYO model keeps quality, cost, and exit under your control. Modules are fine for demos.
How many vectors until pgvector is “wrong”?
No universal number. Watch OLTP p95, autovacuum, index RAM, and ANN QPS. Many teams are fine into low millions of chunks on a sized primary; others split earlier because of ops politics, not math.
Do I need hybrid on day one?
If users search SKUs, error codes, emails, or ticket IDs — yes, or you will look broken. See hybrid + rerank.
Namespace per tenant or metadata filter?
Namespaces help isolation and drop-tenant; filters scale to many small tenants. Combine with tests either way.
Deep dive: lock-in is about data shape, not SDKs
SDKs are easy to wrap. Painful lock-in looks like:
- Chunk text only inside the vendor
- Proprietary sparse vector formats you cannot export
- Tenancy only via a vendor namespace feature with no portable equivalent
- Filters expressed in a DSL that does not map to SQL
- Dimension or metric choices that force a full rewrite of eval baselines
Countermeasures: SoT ownership, portable metadata schema, protocol wrapper, periodic restore drill into a second engine in staging.
flowchart TB
Bad[Bad lock-in] --> T1[Text only in vendor]
Bad --> T2[Proprietary sparse only]
Bad --> T3[Tenant only via vendor namespace]
Good[Good posture] --> S1[SoT owns text]
Good --> S2[Portable payload schema]
Good --> S3[Protocol + rebuild job]
Good --> S4[Quarterly restore drill]
Deep dive: filter selectivity and why demos lie
Unfiltered ANN is the happy path vendors demo. Production queries look like:
tenant = X AND role IN (…) AND doc_type = policy AND updated_at > …
If the filter matches 0.1% of vectors, engines that post-filter candidates can return < k hits or scan huge candidate sets. Engines with filter-aware indexing behave better — but you must measure on your skew.
| Test | How |
|---|---|
| Sparse tenant | Create 1 tiny tenant amid a huge shared index; query it |
| Strict ACL | Overlap embeddings across tenants; assert zero leaks |
| Hot filter | 90% of queries share doc_type=faq — partial indexes / partitions? |
Deep dive: write path and deletes
Choosing a store is also choosing a write machine.
sequenceDiagram
participant User
participant API
participant SoT
participant Queue
participant Emb as Embed worker
participant VS as Vector store
User->>API: Upload doc
API->>SoT: Insert doc + chunks text
API->>Queue: Enqueue embed
Queue->>Emb: Job
Emb->>VS: Upsert vectors
User->>API: Delete doc
API->>SoT: Soft/hard delete
API->>Queue: Enqueue delete vectors
Queue->>VS: Delete by ids
Ship rules
- Upserts are idempotent on stable ids
- Deletes are scheduled and retried — never “fire and forget” only in the request thread without a durable queue for prod
- Re-embeds create a new collection or stamp
embedding_model_id— do not mix dims
Deep dive: latency budget
ANN is one hop in a larger budget:
| Hop | Typical concern |
|---|---|
| Auth / tenant bind | Miss = security bug |
| Embed query | Often dominates cold path |
| ANN + filters | Vendor/engine choice shows here |
| Hydrate from SoT | Chatty ORM can ruin p95 |
| Rerank | Extra model call |
| LLM | Dominates total UX time |
Optimizing only ANN while ignoring embed + hydrate is common theater. Cache exact query hashes in Redis when repeats are common.
How-it-works: side-by-side query paths
flowchart TB
subgraph pgvectorPath[pgvector]
Q1[SQL] --> H1[HNSW / IVF]
Q1 --> J1[JOIN docs ACL]
end
subgraph qdrantPath[Qdrant]
Q2[gRPC search + filter] --> H2[HNSW + payload idx]
H2 --> H3[Hydrate text from Postgres]
end
subgraph pinePath[Pinecone]
Q3[HTTPS query + metadata filter] --> H4[Managed ANN]
H4 --> H5[Hydrate from SoT]
end
Same user question; different failure domains. pgvector fails with the DB. Pinecone fails as a third-party dependency. Qdrant self-host fails as your cluster. Design degrade modes for the one you pick.
Tradeoffs summary poster
| If you optimize for… | Lean toward… |
|---|---|
| One system, SQL ACLs | pgvector |
| Zero vector ops staffing | Pinecone / cloud Qdrant / Weaviate Cloud |
| Filter-heavy multi-tenant self-host | Qdrant |
| Schema + hybrid modules | Weaviate |
| Existing search SRE | Elastic / OpenSearch |
| Teaching / first week | Chroma |
| Offline eval at scale | FAISS (+ metadata DB) |
Micro-project
Pick a store for a 10-tenant SaaS RAG MVP. Write five bullets of rationale and one paragraph on the exit plan (how you would rebuild on another store in a weekend).
Stretch goals
- Draw architecture A or B with failure domains labeled.
- Implement a fake
VectorStorewith an in-memory backend and a Qdrant adapter behind the same protocol. - Write a CI test that fails if any hit’s
tenant_idmismatches the session. - Fill the scoring rubric for your side project and paste it into the README.
Related
Flagship: Vector databases. Guided RAG; Key Tech Chroma. Next: Postgres and pgvector, chunking, and hybrid + rerank.