Module 5: Vector Database Landscape for AI Engineers
Capsule 07: Anti-patterns When Choosing a Vector DB
Capsule description
So far you've learned to compare providers, calculate costs, and follow a decision framework. But even with the best analysis tools, teams make predictable mistakes. These mistakes are not technical — they're cognitive and organizational. Choosing by hype, over-engineering for a scale that doesn't exist, ignoring operational complexity, falling into vendor lock-in without realizing it, or optimizing prematurely.
In this capsule you'll study the 7 most destructive anti-patterns in vector database selection. For each one you'll see the pattern, a real (anonymized) case, the warning signs to detect it early, and the concrete fix. The goal is not that you never make these mistakes — it's that you recognize them in the first few days, not after 6 months and $30,000 invested.
The difference between a junior team and a senior one is not that the senior never makes mistakes. It's that the senior has a vocabulary of known errors and mechanisms to detect them before they become costly.
Anti-pattern 1: "Choosing by hype"
The pattern
You choose the vector database that appears most on Twitter, YouTube, or HackerNews. The justification is "everyone uses it" or "it has more GitHub stars".
Real case
An e-commerce startup. The CTO reads on HackerNews that Pinecone is "the best vector DB". Without evaluating alternatives, they buy the Standard plan ($200/mo). Their case: 15K products with embeddings, 30 queries/day. ChromaDB local would have been free and enough for 12+ months.
Cost of the anti-pattern: $2,400/year wasted + 2 weeks of integration that could have been 2 hours with ChromaDB.
Warning signs
□ The decision is based on "X famous company uses it" without checking if your context is similar
□ You can't name at least 2 alternatives you evaluated
□ The main argument is "it's the most popular" or "it has more stars"
□ There's no benchmark or PoC with your real data
□ The decision was made in less than 1 day without formal analysis
The fix
# Before choosing by hype, answer these 4 questions:
evaluation_checklist = {
"1_problem_fit": "Does this provider solve MY specific problem, "
"not a generic problem?",
"2_alternatives": "Did I evaluate at least 2 alternatives with a 1-day PoC?",
"3_data_driven": "Is my decision based on benchmarks with MY data, "
"not on generic benchmarks?",
"4_context_match": "Do the companies that use it have a context similar "
"to mine (scale, team, budget)?"
}
# If any answer is "no", the decision is premature
for key, question in evaluation_checklist.items():
print(f" {question}")
Golden rule
A provider's popularity has no correlation with its fit for your case. Pinecone is excellent for 1M+ vectors with 1000 queries/second. It's overkill for 15K vectors with 30 queries/day.
Anti-pattern 2: "Feature checklist without context"
The pattern
You create a table with 20+ features, mark which ones each provider has, and choose the one with the most checkmarks. The problem: you treat all features as equally important.
Real case
An ML team at a financial company. They compare 5 vector databases across 25 features. Milvus wins with 23/25 checkmarks. They choose Milvus. Result: 3 months of setup, a complex Kubernetes cluster, 2 engineers dedicated to operations. Of the 25 features, they use only 6. The 6 they use are also available in ChromaDB and Qdrant.
Cost of the anti-pattern: $45,000 in engineering (3 months × 2 engineers × $7,500/mo) + permanent operational complexity.
Warning signs
□ Your comparison table has more than 10 features
□ All features have the same weight (or have no weight)
□ You don't distinguish between "nice to have" and "must have"
□ The winning option is the most complex/complete one
□ You didn't ask "which of these features will I use in the first 6 months?"
The fix
# Instead of a flat checklist, use weights
features_weighted = {
# Feature: (weight 1-5, justification)
"vector_search_basic": (5, "Core. Nothing works without this"),
"metadata_filtering": (4, "Needed to filter by category"),
"hybrid_search": (2, "Nice-to-have, we don't need it today"),
"multi_tenancy": (1, "Only 1 tenant for now"),
"gpu_acceleration": (0, "We have no GPUs and don't need them"),
"distributed_sharding": (0, "15K vectors, irrelevant"),
"graphql_api": (1, "REST is enough"),
"built_in_ml_modules": (1, "We use our own models"),
}
# Only features with weight >= 3 are decisive
must_have = {k: v for k, v in features_weighted.items() if v[0] >= 3}
print(f"Decisive features: {len(must_have)} of {len(features_weighted)}")
# Decisive features: 2 of 8
# Evaluate ONLY with the decisive features
# If both options meet the must-haves, break the tie by cost/simplicity
Golden rule
Choose the option that has the 3-5 features you really need TODAY, not the one with the 25 features you might need someday.
Anti-pattern 3: "Infrastructure first, product later"
The pattern
You spend weeks (or months) configuring the "perfect architecture" before validating that users want what you're building. A Kubernetes cluster, multi-region replication, full CI/CD, advanced monitoring... for a product that doesn't have users yet.
Real case
A legal-tech startup. Before having a single user, they implement: Milvus with 3 nodes on Kubernetes, Prometheus + Grafana for monitoring, a CI/CD pipeline with 12 stages, cross-region US-EU replication. Total: 2 months of setup. Result: 0 users after 4 months. The company shuts down without validating product-market fit. The infrastructure was perfect; the product was never used.
Cost of the anti-pattern: 2 months of runway ($60,000) spent on infrastructure for a product that never found users.
Warning signs
□ You've spent > 1 week on infrastructure setup without having users
□ Your architecture supports 1M users but you have 0
□ You have more monitoring dashboards than product features
□ The team discusses sharding strategies before having 10K vectors
□ Your deploy pipeline is more complex than your application
The fix
Principle: "Do things that don't scale" (Paul Graham)
Week 1-2: ChromaDB local + minimal API → Demo to 5 users
Month 1: If there's traction → Pinecone Free / Qdrant Cloud Free
Month 3: If there's payment → Paid tier of the managed provider
Month 6+: If there's real scale → Evaluate self-hosted or enterprise
# The "Right-Size Infrastructure" rule
def infrastructure_decision(users: int, vectors: int, revenue: float):
if users == 0:
return "ChromaDB local. Invest in product, not in infra."
elif users < 100:
return "Free tier managed. At most 1 day of setup."
elif users < 1000:
return "Standard tier managed. At most 1 week of setup."
elif users < 10000:
return "Now yes, evaluate serious architecture."
else:
return "Enterprise tier or self-hosted with a dedicated team."
Golden rule
The best infrastructure is the one that lets you validate your business hypothesis as fast as possible. If you spend more time on infra than on product, you have your priorities inverted.
Anti-pattern 4: "Over-engineering for a scale that doesn't exist"
The pattern
Similar to the previous one but more subtle. It's not that you build infra before having users — it's that you size for 10M vectors when you have 50K. Or you implement sharding when a single node handles your load 20x over.
Real case
A customer support SaaS. 80K tickets as a knowledge base. They implement Qdrant with 3 replicated nodes, a load balancer, CPU-based auto-scaling. A single node with 2GB of RAM handles 80K vectors with 5ms latency and 100 QPS. The 3 nodes + infrastructure cost $300/mo instead of $24/mo.
Cost of the anti-pattern: $276/mo extra × 12 months = $3,312/year + unnecessary operational complexity.
Warning signs
□ Your infrastructure uses < 10% of its capacity
□ You have replication but a single node handles your load with a 10x margin
□ You implemented auto-scaling but it has never scaled
□ Your latency is 5ms but your requirement is < 200ms
□ You sized for "what could happen" instead of "what's happening + 3x buffer"
The fix
Principle: Current load × 3-5x buffer, not current load × 100x
# Correct sizing formula
def right_size(current_vectors: int, current_qps: float,
growth_rate: float, months_ahead: int = 6):
projected_vectors = current_vectors * ((1 + growth_rate) ** months_ahead)
buffer_multiplier = 3 # 3x buffer is enough
target_capacity = projected_vectors * buffer_multiplier
print(f"Current vectors: {current_vectors:,}")
print(f"Projection {months_ahead} months ({growth_rate*100:.0f}%/mo): "
f"{projected_vectors:,.0f}")
print(f"Target capacity (3x buffer): {target_capacity:,.0f}")
if target_capacity < 200_000:
print("→ 1 basic node (2-4GB RAM)")
elif target_capacity < 1_000_000:
print("→ 1 medium node (8-16GB RAM)")
elif target_capacity < 5_000_000:
print("→ 1 large node (32GB RAM) or 2 medium nodes")
else:
print("→ Cluster (3+ nodes)")
right_size(80_000, 10, 0.15, 6)
# Current vectors: 80,000
# Projection 6 months (15%/mo): 185,045
# Target capacity (3x buffer): 555,135
# → 1 medium node (8-16GB RAM)
Golden rule
Size for 6 months ahead with a 3x buffer, not for 3 years ahead with a 10x buffer. If you need more, migration is an option; over-engineering is a waste.
Anti-pattern 5: "Not preparing the exit (vendor lock-in)"
The pattern
You choose a provider and couple all your code directly to its SDK. When you need to migrate (pricing, performance, compliance), you discover that the migration costs $10,000-30,000.
Real case
A fintech startup. They use Pinecone for 18 months. Their code has direct calls to the Pinecone SDK in 47 files. When Pinecone raises prices 40%, they want to migrate to Qdrant. Migration estimate: 3 weeks of engineering ($15,000) + 1 week of testing ($5,000) + risk of production bugs.
Cost of the anti-pattern: $20,000 for migration + 4 weeks of lost product velocity + operational risk.
Warning signs
□ Your code imports the provider's SDK in > 5 files
□ You have no interface/abstraction between your logic and the provider
□ You use proprietary features that don't exist in other providers
□ You have no export/backup of your vectors in a portable format
□ You never estimated how much it would cost to switch providers
The fix
Level 1: Basic abstraction layer (2-4 hours)
from abc import ABC, abstractmethod
from typing import List, Dict, Any, Optional
class VectorStore(ABC):
"""Interface that decouples your code from the specific provider."""
@abstractmethod
def upsert(self, ids: List[str], embeddings: List[List[float]],
metadata: List[Dict[str, Any]]) -> None:
pass
@abstractmethod
def query(self, embedding: List[float], top_k: int = 10,
filters: Optional[Dict[str, Any]] = None) -> List[Dict]:
pass
@abstractmethod
def delete(self, ids: List[str]) -> None:
pass
# Implement ChromaStore(VectorStore), QdrantStore(VectorStore), etc.
# Your application uses VectorStore, never the SDK directly
# Migrating = change 1 line of configuration:
# store = ChromaStore("my_collection") # Before
# store = QdrantStore("https://...", "my_col") # After
Level 2: Portable vector backup (1-2 hours)
Implement export_vectors() and import_vectors() functions that serialize to portable JSON. This lets you migrate between providers without re-generating embeddings.
Golden rule
Always have an abstraction layer AND a portable backup. The cost of implementing them (4-6 hours) is 100x lower than the cost of migrating without them ($10,000-30,000).
Anti-pattern 6: "Switching DBs because it's trendy"
The pattern
You migrate providers because a new one came out that "is better", without having a concrete problem with your current provider. There's no hypothesis, no success metrics, just FOMO.
Real case
A team of 5 devs at an edtech company. They use ChromaDB self-hosted without problems: 200K vectors, 50ms latency, $24/mo. Weaviate 2.0 comes out with hybrid search features and modules. The lead engineer proposes migrating "because it's the future". Migration: 2 weeks of work, $8,000 in engineering. Post-migration: 200K vectors, 40ms latency, $80/mo. They never used hybrid search or modules.
Cost of the anti-pattern: $8,000 for migration + $56/mo extra × 12 = $8,672 total to gain 10ms of latency nobody asked for.
Warning signs
□ You can't articulate a specific problem with your current provider
□ The motivation is "X is more modern/better/has more hype"
□ You have no metrics showing that your current provider is insufficient
□ You didn't define success criteria for the migration BEFORE starting
□ The migration trigger was a blog post, not an incident or metric
The fix
Before migrating, complete this template:
## Migration proposal
### Current problem (with data)
- Current p95 latency: ___ms (acceptable limit: ___ms)
- Current TCO cost: $___/mo (acceptable limit: $___/mo)
- Incidents last 3 months: ___ (acceptable limit: ___)
- Missing feature that blocks: ________________
### Hypothesis
"Migrating to [new provider] will solve [specific problem]
by reducing [metric] from [current value] to [target value]."
### Success criteria (measurable)
1. p95 latency < ___ms (currently ___ms)
2. TCO < $___/mo (currently $___/mo)
3. Incidents < ___/quarter (currently ___)
### Estimated migration cost
- Engineering hours: ___ × $___/hr = $___
- Dual-running: ___ months × $___/mo = $___
- Downtime risk: ___h × $___/hr = $___
- Total: $___
### ROI
- Monthly savings/improvement post-migration: $___
- Payback period: ___ months
- If payback > 12 months → do NOT migrate
If you can't fill in this template with real data, don't migrate.
Golden rule
Migrate for data, not for hype. If your current provider meets your requirements, "better" is irrelevant. The cheapest migration is the one you don't do.
Anti-pattern 7: "Premature index optimization"
The pattern
You spend days tuning HNSW parameters (ef_construction, M, ef_search), trying different distance metrics, or implementing quantization... when you have 20K vectors and 10 queries per minute.
Real case
A data scientist at a healthtech company. They spend 1 week trying 15 combinations of HNSW parameters in ChromaDB with 30K vectors. They find that ef_construction=200, M=32 gives 3ms instead of 5ms with the default configuration. The chatbot users never noticed a difference because the total request latency (embedding + search + LLM) is 2,500ms.
Cost of the anti-pattern: 1 week of engineering ($4,000) to optimize 2ms in a 2,500ms pipeline (a 0.08% improvement).
Warning signs
□ You're optimizing a component that isn't the bottleneck
□ The measured improvement is < 10% of the total request time
□ You have < 100K vectors and you're tuning index parameters
□ You didn't profile the complete pipeline before optimizing
□ Your current QPS is < 10% of the default capacity
The fix
# BEFORE optimizing, profile the complete pipeline
import time
def profiled_rag_pipeline(query: str):
timings = {}
t0 = time.time()
embedding = generate_embedding(query)
timings["embedding"] = time.time() - t0
t0 = time.time()
results = vector_store.query(embedding, top_k=5)
timings["vector_search"] = time.time() - t0
t0 = time.time()
context = format_context(results)
timings["formatting"] = time.time() - t0
t0 = time.time()
response = llm_generate(query, context)
timings["llm_generation"] = time.time() - t0
total = sum(timings.values())
print("\n--- Pipeline Profiling ---")
for step, duration in timings.items():
pct = (duration / total) * 100
bar = "█" * int(pct / 2)
print(f"{step:20s}: {duration*1000:7.1f}ms ({pct:5.1f}%) {bar}")
print(f"{'TOTAL':20s}: {total*1000:7.1f}ms")
return response
# Typical result:
# embedding : 150.0ms ( 5.7%) ███
# vector_search : 15.0ms ( 0.6%)
# formatting : 2.0ms ( 0.1%)
# llm_generation : 2450.0ms (93.6%) ███████████████████████████████████████████████
# TOTAL : 2617.0ms
# → Optimizing vector_search from 15ms to 5ms = 0.4% total improvement
# → Optimizing llm_generation (streaming, faster model) = 93.6% of the pipeline
Golden rule
Optimize the bottleneck, not the component you know best. In a typical RAG pipeline, the LLM is 90%+ of the time. Vector search is < 5%. Optimizing vector search before optimizing the LLM is like polishing the steering wheel of a car without an engine.
The universal warning sign
If you can't answer these 4 questions about your decision, the decision is still immature:
maturity_check = {
"1_hypothesis": "What hypothesis does this choice validate? "
"(e.g., 'Qdrant Cloud reduces TCO 40% vs self-hosted')",
"2_risk": "What main risk does this choice reduce? "
"(e.g., 'Eliminates downtime risk due to lack of DevOps')",
"3_metric": "What metric will confirm you were right? "
"(e.g., 'p95 latency < 50ms AND TCO < $300/mo in month 3')",
"4_trigger": "What is the re-evaluation trigger? "
"(e.g., 'Re-evaluate when vectors > 500K OR TCO > $500/mo')"
}
decision_mature = True
for key, question in maturity_check.items():
answer = input(f"\n{question}\nYour answer: ")
if not answer.strip():
print("⚠️ No answer → immature decision")
decision_mature = False
if not decision_mature:
print("\n→ Go back to the decision matrix (Capsule 06)")
Anti-patterns summary table
| # | Anti-pattern | Typical cost | Detection | Prevention |
|---|---|---|---|---|
| 1 | Choosing by hype | $2K-10K wasted | "I didn't evaluate alternatives" | Evaluate ≥ 2 options with a PoC |
| 2 | Feature checklist without weights | $15K-45K in over-engineering | "The most complete one wins" | Weight features, only top 3-5 |
| 3 | Infra first, product later | $30K-60K in runway | "> 1 week on infra without users" | Validate with users before infra |
| 4 | Scale over-engineering | $3K-12K/year wasted | "Infra at < 10% capacity" | Size for 6 months × 3x |
| 5 | Not preparing the exit | $10K-30K for migration | "Direct SDK in > 5 files" | Abstraction layer + backup |
| 6 | Switching because it's trendy | $5K-15K per migration | "No problem with the current one" | Migration template with data |
| 7 | Premature optimization | $2K-8K in wasted time | "I optimize < 5% of the pipeline" | Profile the complete pipeline |
Technical debate troubleshooting
1. "The team can't reach consensus"
Symptom: 3 engineers, 3 different opinions, meetings that don't move forward.
Diagnosis: They're comparing conclusions without agreeing on criteria first.
Step-by-step solution:
- Agree on the criteria before comparing providers (5 variables from Capsule 06)
- Assign weights to each criterion by voting (each person distributes 10 points)
- Evaluate each provider against the weighted criteria
- The highest score wins — not the loudest voice
# Example of weighted voting
criteria_weights = {
"latency": {"alice": 3, "bob": 2, "carol": 4}, # Total: 9
"cost": {"alice": 3, "bob": 4, "carol": 2}, # Total: 9
"ops": {"alice": 2, "bob": 3, "carol": 2}, # Total: 7
"scale": {"alice": 1, "bob": 1, "carol": 1}, # Total: 3
"lock_in": {"alice": 1, "bob": 0, "carol": 1}, # Total: 2
}
# Consensus: latency and cost weigh more than scale and lock-in
# Now evaluate providers with these weights
2. "Stakeholders ask for a specific brand"
Symptom: The VP of Engineering says "let's use Pinecone" without technical analysis.
Solution:
- Thank them for the suggestion (don't dismiss it)
- Include Pinecone as a candidate in your analysis
- Present the complete analysis (TCO, weighted features, fit by variable)
- If Pinecone doesn't win, show the data — not the opinion
- Let the data speak: "Pinecone is excellent for X. Our case is Y. For Y, [alternative] has a better fit because [concrete data]."
3. "We have urgency and an incomplete analysis"
Symptom: You need to decide today, you don't have time for a PoC of each option.
Solution:
- Make an explicit temporary decision — not a "quick permanent decision"
- Document that it's temporary: "We chose X due to urgency. Mandatory review on [date]."
- Choose the most reversible option (managed + abstraction layer)
- Schedule the real review for 30-60 days out
## Temporary decision (ADR-temp-001)
**Date:** [today]
**Decision:** Use Pinecone Free due to delivery urgency
**Reason:** No time for a complete PoC. Pinecone Free
is reversible and free.
**Mandatory review:** [today + 45 days]
**Review criteria:** Evaluate Qdrant Cloud and Weaviate Cloud
with a 1-day PoC each.
4. "We already chose wrong and it's painful to admit"
Symptom: You've been with a non-ideal provider for 6 months. The team doesn't want to admit the mistake because "we already invested a lot".
Diagnosis: Sunk cost fallacy. The money and time already spent are unrecoverable. The right question is not "how much did we invest?" but "how much more will we lose if we don't change?"
Solution:
- Calculate the monthly cost of maintaining the status quo
- Calculate the migration cost
- Calculate the payback period
- If payback < 6 months, migrate. If > 12 months, stay.
5. "The provider we chose was acquired/changed pricing"
Symptom: Your managed provider raises prices 50% or is acquired by a company that changes the product direction.
Solution:
- If you have an abstraction layer → migrate at moderate cost ($2,000-5,000)
- If you do NOT have an abstraction layer → negotiate current pricing for a 12-month contract while you prepare the migration
- For the future: always have the abstraction layer ready (Anti-pattern 5)
Practical exercises
Exercise 1: Anti-pattern audit
Review your current decision (or a hypothetical one) and mark which anti-patterns apply:
□ Anti-pattern 1: Did I choose by popularity without evaluating alternatives?
□ Anti-pattern 2: Did I compare features without weighting their importance?
□ Anti-pattern 3: Did I invest in infra before validating with users?
□ Anti-pattern 4: Did I size for 100x my current scale?
□ Anti-pattern 5: Do I have lock-in without an abstraction layer?
□ Anti-pattern 6: Did I consider migrating without a concrete problem?
□ Anti-pattern 7: Did I optimize a component that isn't the bottleneck?
If you marked 2+, review your decision.
Example solution
A RAG chatbot project with ChromaDB self-hosted, 40K vectors:
☑ Anti-pattern 3: I spent 1 week configuring Docker + monitoring
before having users → pip install chromadb would have been enough
☑ Anti-pattern 4: I deployed on a 16GB RAM instance for 40K vectors
that fit in 0.3GB → A $12 droplet was enough
□ Anti-pattern 5: OK, I have an abstraction layer
□ Anti-pattern 7: OK, I haven't optimized index parameters yet
Corrective actions:
- Migrate to a smaller instance ($100/mo → $12/mo, savings: $88/mo)
- Simplify monitoring (Prometheus stack → simple application logs)
Exercise 2: Migration template with data
Imagine you want to migrate from ChromaDB to Qdrant Cloud. Fill in the migration template:
Example solution
## Migration proposal: ChromaDB → Qdrant Cloud
### Current problem (with data)
- p95 latency: 25ms (acceptable limit: 50ms) → ✅ OK
- TCO: $1,065/mo (acceptable limit: $500/mo) → ❌ EXCEEDS
- Incidents last 3 months: 4 (acceptable limit: 1) → ❌ EXCEEDS
- Missing feature: integrated monitoring, SLA
### Hypothesis
"Migrating to Qdrant Cloud will reduce TCO from $1,065 to ~$250/mo
and eliminate operational incidents."
### Success criteria
1. TCO < $300/mo (currently $1,065)
2. Incidents < 1/quarter (currently 4)
3. p95 latency < 30ms (currently 25ms)
### Migration cost
- Engineering: 20h × $80/hr = $1,600
- Dual-running: 1 month × $250 = $250
- Re-embedding: $0 (same embeddings, just import)
- Testing: 8h × $80/hr = $640
- Total: $2,490
### ROI
- Monthly savings: $1,065 - $250 = $815/mo
- Payback: $2,490 / $815 = 3.1 months ✅
- Year 1 savings: ($815 × 12) - $2,490 = $7,290
Payback of 3 months → migration justified with data.
Exercise 3: Pipeline profiling
Profile a hypothetical RAG pipeline and determine where to optimize:
| Component | Time (ms) | % of total |
|---|---|---|
| Embedding generation | 120 | ? |
| Vector search | 15 | ? |
| Context formatting | 5 | ? |
| LLM generation | 2,800 | ? |
| Response formatting | 10 | ? |
| Total | 2,950 | 100% |
Where should you focus the optimization?
Solution
| Component | Time (ms) | % of total | Priority |
|---|---|---|---|
| Embedding generation | 120 | 4.1% | Medium |
| Vector search | 15 | 0.5% | Low |
| Context formatting | 5 | 0.2% | None |
| LLM generation | 2,800 | 94.9% | HIGH |
| Response formatting | 10 | 0.3% | None |
Where to optimize (in order):
- LLM generation (94.9%): Use streaming, a faster model, a shorter prompt, or a cache of frequent responses
- Embedding generation (4.1%): Use a local model instead of an API, or cache embeddings of frequent queries
- Vector search (0.5%): Do NOT optimize. Even if you make it 10x faster (15ms → 1.5ms), you only gain 0.46% of the total
Optimizing vector search here is Anti-pattern 7.
Exercise 4: Simulated debate — defend your position
Choose a side and prepare 3 arguments with data:
Position A: "Always start with managed, migrate to self-hosted only if necessary." Position B: "Always start with self-hosted open-source, migrate to managed only if necessary."
Solution
Position A (Managed first) — 3 arguments:
-
Lower TCO for teams < 10 people:
- Managed: $250/mo (service + 2h ops)
- Self-hosted: $1,065/mo (infra + 14h ops)
- Savings: $815/mo = $9,780/year
-
3x faster time-to-market:
- Managed: 1-2 weeks to production
- Self-hosted: 4-6 weeks to production
- At a startup, 4 weeks = $60,000 of runway
-
80% lower operational risk:
- Managed: provider's SLA, auto-scaling, backups included
- Self-hosted: You're responsible for everything at 3am
Position B (Self-hosted first) — 3 arguments:
-
Zero lock-in from day 1:
- Open-source: you migrate whenever you want at no extra cost
- Managed: migrating costs $10,000-30,000
-
5-10x lower infra cost at scale:
- Self-hosted 1M vectors: $180/mo (direct infra)
- Managed 1M vectors: $800-1,000/mo
- Savings over 2 years: $15,000-20,000
-
Full control for compliance:
- Self-hosted: data in your infra, auditable, any regulation
- Managed: you depend on the provider's certifications
My recommendation: Position A for 80% of teams. Position B only if you have dedicated DevOps + strict compliance requirements.
Connection with the project: Decision Tree
In your final Decision Tree project, include an anti-pattern validation section:
- For each recommendation your tree generates, check the 7 anti-patterns
- Include automatic warnings (e.g., "if the user has < 10K vectors and chooses Milvus, show an Anti-pattern 4 warning")
- Generate a "health check" of the decision with the 4 maturity questions
Summary
- Anti-pattern 1 (Hype): A provider's popularity doesn't imply fit for your case. Evaluate ≥ 2 alternatives with a PoC.
- Anti-pattern 2 (Feature checklist): Don't compare 25 features with the same weight. Weight the 3-5 you really need.
- Anti-pattern 3 (Infra first): Validate the product with users before investing in infrastructure.
pip install chromadb> a Kubernetes cluster without users. - Anti-pattern 4 (Over-engineering): Size for 6 months × 3x buffer, not for 3 years × 100x.
- Anti-pattern 5 (Not preparing the exit): Always have an abstraction layer + portable backup. It costs 4-6 hours. It saves $10,000-30,000 in future migration.
- Anti-pattern 6 (Migrating because it's trendy): Migrate for data and metrics, not for blog posts. If you can't fill in the migration template, don't migrate.
- Anti-pattern 7 (Premature optimization): Profile the complete pipeline before optimizing. Vector search is usually < 5% of the total time in RAG.
- Universal sign of an immature decision: Not being able to answer "what hypothesis it validates", "what risk it reduces", "what metric confirms it", and "what the re-evaluation trigger is".
Additional resources
- Martin Fowler — Microservice Trade-offs — A trade-offs framework applicable to any architecture decision
- Paul Graham — Do Things That Don't Scale — The philosophy of starting simple
- Architecture Decision Records — Standard format for documenting technical decisions
- Sunk Cost Fallacy in Engineering — Why it's hard to admit technical mistakes
- ANN Benchmarks — To evaluate with real data, not hype
- CNCF Technology Radar — Neutral evaluation of technologies by the community
- Choosing the Right Vector Database — An up-to-date vendor-neutral perspective
- Donald Knuth — Premature Optimization — The root of Anti-pattern 7
Estimated time: 30-40 minutes
Next: 08-project-decision-tree.md