Module 6: Decision Matrix for AI Engineers
Capsule 05: Cost and ROI Analysis
🎯 Capsule objective
Master the real financial analysis of vector databases: calculate TCO (Total Cost of Ownership) at different scales, identify hidden costs, model migration break-even, and predict when the free tier runs out and costs spike.
By the end of this capsule:
- ✅ You'll calculate the real TCO (not just the sticker price) of each option
- ✅ You'll identify the 5 hidden costs nobody mentions on pricing pages
- ✅ You'll model migration break-even for self-hosted → managed (and vice versa)
- ✅ You'll project costs at 3 scales: 10K, 100K, and 1M vectors
Estimated time: 25-35 minutes
Capsule description
The most expensive financial mistake when choosing a vector database isn't overpaying — it's not knowing how much you're actually paying. Pricing pages show the cost of the service, but that's only 30-40% of the total cost. The remaining 60-70% hides in engineering hours, data egress, incidents, opportunity cost, and the unexpected jump when you cross a pricing threshold.
This capsule gives you the tools to calculate the real Total Cost of Ownership (TCO) across four dimensions: infrastructure, operations, risk, and integration. You'll model costs at three scales (10K, 100K, and 1M vectors) for each modality (managed vs self-hosted), identify the "cliff edges" where cost spikes, and build a break-even model to decide whether migrating is worth the investment.
The goal isn't to find "the cheapest". It's to find the option that maximizes value per dollar in your context. Sometimes that means paying more for managed; sometimes it means investing in ops for self-hosted. The numbers will tell you which.
The complete TCO formula
Most teams compare only the price of the service. The real TCO has four dimensions:
def calculate_tco(
infra_cost: float,
ops_hours: float,
engineer_rate: float,
incident_probability: float,
incident_cost: float,
integration_hours: float = 0,
integration_rate: float = 0
) -> dict:
"""
Calculate the monthly Total Cost of Ownership.
Args:
infra_cost: Monthly cost of the service/infrastructure
ops_hours: Monthly operations/maintenance hours
engineer_rate: Engineer's hourly rate (USD)
incident_probability: Monthly incident probability (0-1)
incident_cost: Estimated cost per incident (USD)
integration_hours: Initial integration hours (amortized over 12 months)
integration_rate: Engineer's rate for integration
"""
ops_cost = ops_hours * engineer_rate
risk_cost = incident_probability * incident_cost
integration_amortized = (integration_hours * integration_rate) / 12
tco = infra_cost + ops_cost + risk_cost + integration_amortized
return {
"infra": round(infra_cost, 2),
"ops": round(ops_cost, 2),
"risk": round(risk_cost, 2),
"integration_amortized": round(integration_amortized, 2),
"total_monthly": round(tco, 2),
"total_annual": round(tco * 12, 2),
"breakdown_pct": {
"infra": round((infra_cost / tco) * 100, 1) if tco > 0 else 0,
"ops": round((ops_cost / tco) * 100, 1) if tco > 0 else 0,
"risk": round((risk_cost / tco) * 100, 1) if tco > 0 else 0,
"integration": round((integration_amortized / tco) * 100, 1) if tco > 0 else 0,
}
}
Example: managed vs self-hosted
# Scenario: 200K vectors, 1536 dimensions, mid-level team
managed_tco = calculate_tco(
infra_cost=200, # Managed tier for 200K vectors
ops_hours=2, # Only integration monitoring
engineer_rate=70, # Mid-level engineer
incident_probability=0.05, # 5% chance of incident/month
incident_cost=500, # Estimated cost per incident
integration_hours=16, # 2 days of initial integration
integration_rate=70
)
self_hosted_tco = calculate_tco(
infra_cost=80, # VM t3.large + storage
ops_hours=12, # Monitoring, backups, updates, debugging
engineer_rate=70,
incident_probability=0.15, # 15% — more incidents without managed
incident_cost=800, # More expensive: debugging + recovery
integration_hours=40, # 5 days of setup + config
integration_rate=70
)
print("=== Monthly TCO: Managed vs Self-hosted ===\n")
for name, tco in [("Managed", managed_tco), ("Self-hosted", self_hosted_tco)]:
print(f"{name}:")
print(f" Infrastructure: ${tco['infra']:>8.2f} ({tco['breakdown_pct']['infra']}%)")
print(f" Operation: ${tco['ops']:>8.2f} ({tco['breakdown_pct']['ops']}%)")
print(f" Risk: ${tco['risk']:>8.2f} ({tco['breakdown_pct']['risk']}%)")
print(f" Integration (12m):${tco['integration_amortized']:>8.2f} ({tco['breakdown_pct']['integration']}%)")
print(f" ---")
print(f" TOTAL MONTHLY: ${tco['total_monthly']:>8.2f}")
print(f" TOTAL ANNUAL: ${tco['total_annual']:>8.2f}")
print()
saving = self_hosted_tco["total_monthly"] - managed_tco["total_monthly"]
print(f"Monthly difference: ${saving:.2f}/month in favor of {'managed' if saving > 0 else 'self-hosted'}")
print(f"Annual difference: ${saving * 12:.2f}/year")
Typical output:
Managed:
Infrastructure: $ 200.00 (43.6%)
Operation: $ 140.00 (30.5%)
Risk: $ 25.00 (5.5%)
Integration (12m):$ 93.33 (20.4%)
---
TOTAL MONTHLY: $ 458.33
TOTAL ANNUAL: $ 5500.00
Self-hosted:
Infrastructure: $ 80.00 (6.3%)
Operation: $ 840.00 (66.0%)
Risk: $ 120.00 (9.4%)
Integration (12m):$ 233.33 (18.3%)
---
TOTAL MONTHLY: $ 1273.33
TOTAL ANNUAL: $15280.00
Conclusion: The "expensive" $200/month managed option is $815/month cheaper than the "cheap" $80/month self-hosted one once you count your team's hours.
The 5 hidden costs nobody mentions
hidden_costs = {
"1_egress": {
"name": "Network egress (data transfer)",
"description": "Every query sends data from the server to the client. "
"If your vector DB is in a different region than your app, you pay egress.",
"typical_cost": "$0.05-0.12 per GB",
"trap": "5K queries/day × 10KB response = 50MB/day = 1.5GB/month. "
"Seems small, but if you do massive re-embedding, you can transfer 100GB+.",
"mitigation": "Place the vector DB in the same region/VPC as your app. "
"Avoid cross-region queries."
},
"2_embedding_generation": {
"name": "Embedding generation cost",
"description": "It's not a vector DB cost, but it's inseparable. "
"Every new document needs an embedding before ingestion.",
"typical_cost": "OpenAI text-embedding-3-small: $0.02/1M tokens",
"trap": "Re-embedding due to a model change: 200K docs × 1000 tokens = "
"200M tokens = $4. Seems cheap, but ×4 times a year = $16 just in re-embed.",
"mitigation": "Store embeddings so you don't regenerate. "
"Choose a stable embedding model (don't change every quarter)."
},
"3_overprovisioning": {
"name": "Over-provisioning out of fear of latency",
"description": "You contract a higher tier 'just in case' and pay 2-3x what you need.",
"typical_cost": "Difference between tiers: $50-300/month",
"trap": "A 1M-vector tier when you have 100K → you pay $300/month for capacity you use at 10%.",
"mitigation": "Start at the lowest tier. Scale when p95 > threshold, not preemptively."
},
"4_migration_cost": {
"name": "Future migration cost",
"description": "If you choose wrong, migrating later costs far more than the pricing difference.",
"typical_cost": "$5,000-15,000 (engineering + downtime + re-embedding)",
"trap": "Choosing by price today → migrating in 12 months → the '$100/month' saving is lost in 1 week of migration.",
"mitigation": "Invest in the initial evaluation (this module). "
"A good decision now saves $10K+ later."
},
"5_opportunity_cost": {
"name": "Opportunity cost",
"description": "Hours your team spends on vector DB ops = hours it does NOT spend on product features.",
"typical_cost": "Hard to quantify but real",
"trap": "12h/month of ops × 12 months = 144 hours = ~1 month of a developer's work. "
"What feature would you have built with that month?",
"mitigation": "Include opportunity cost in the TCO. "
"If your team is small, the impact is greater."
}
}
print("=== 5 Hidden Costs of Vector Databases ===\n")
for key, cost in hidden_costs.items():
print(f"💰 {cost['name']}")
print(f" {cost['description']}")
print(f" Typical cost: {cost['typical_cost']}")
print(f" ⚠️ Trap: {cost['trap']}")
print(f" ✅ Mitigation: {cost['mitigation']}")
print()
Cost model by scale
Costs don't scale linearly. Here's a model for three scales:
def cost_model_by_scale(provider: str, vectors: int, dimensions: int = 1536) -> dict:
"""
Simplified cost model by scale.
Based on public pricing (subject to change).
"""
models = {
"pinecone": {
"tiers": [
{"max_vectors": 100_000, "monthly": 0, "name": "Free"},
{"max_vectors": 1_000_000, "monthly": 70, "name": "Starter"},
{"max_vectors": 5_000_000, "monthly": 200, "name": "Standard"},
{"max_vectors": 50_000_000, "monthly": 800, "name": "Enterprise"},
],
"ops_hours": 2,
},
"qdrant_cloud": {
"tiers": [
{"max_vectors": 100_000, "monthly": 0, "name": "Free"},
{"max_vectors": 500_000, "monthly": 50, "name": "Starter"},
{"max_vectors": 2_000_000, "monthly": 150, "name": "Business"},
{"max_vectors": 20_000_000, "monthly": 500, "name": "Enterprise"},
],
"ops_hours": 3,
},
"weaviate_cloud": {
"tiers": [
{"max_vectors": 50_000, "monthly": 0, "name": "Sandbox"},
{"max_vectors": 500_000, "monthly": 75, "name": "Starter"},
{"max_vectors": 5_000_000, "monthly": 250, "name": "Business"},
{"max_vectors": 50_000_000, "monthly": 900, "name": "Enterprise"},
],
"ops_hours": 3,
},
"chromadb_self": {
"tiers": [
{"max_vectors": 100_000, "monthly": 20, "name": "t3.small"},
{"max_vectors": 500_000, "monthly": 60, "name": "t3.medium"},
{"max_vectors": 2_000_000, "monthly": 150, "name": "m5.large"},
{"max_vectors": 10_000_000, "monthly": 400, "name": "m5.xlarge"},
],
"ops_hours": 12,
},
"milvus_zilliz": {
"tiers": [
{"max_vectors": 100_000, "monthly": 0, "name": "Free"},
{"max_vectors": 1_000_000, "monthly": 65, "name": "Starter"},
{"max_vectors": 10_000_000, "monthly": 200, "name": "Standard"},
{"max_vectors": 100_000_000, "monthly": 600, "name": "Enterprise"},
],
"ops_hours": 5,
}
}
if provider not in models:
return {"error": f"Provider '{provider}' not found"}
model = models[provider]
tier = None
for t in model["tiers"]:
if vectors <= t["max_vectors"]:
tier = t
break
if tier is None:
tier = model["tiers"][-1]
tier["monthly"] *= 1.5 # estimate for a higher scale
return {
"provider": provider,
"vectors": vectors,
"tier": tier["name"],
"service_cost": tier["monthly"],
"ops_hours": model["ops_hours"],
}
# Comparison at 3 scales
scales = [10_000, 100_000, 1_000_000]
providers = ["pinecone", "qdrant_cloud", "weaviate_cloud", "chromadb_self", "milvus_zilliz"]
engineer_rate = 70
print("=== Cost by scale (service + ops, USD/month) ===\n")
header = f"{'Provider':<20}"
for scale in scales:
header += f" {scale//1000}K vectors "
print(header)
print("-" * 80)
for provider in providers:
row = f"{provider:<20}"
for scale in scales:
model = cost_model_by_scale(provider, scale)
total = model["service_cost"] + (model["ops_hours"] * engineer_rate)
row += f" ${total:>7,.0f} ({model['tier']:<10})"
print(row)
The "Cliff Edge": when the free tier runs out
def find_cliff_edges(provider: str, growth_rate_monthly: float,
starting_vectors: int) -> list[dict]:
"""
Identify the points where cost jumps to the next tier.
"""
tiers_map = {
"pinecone": [
(100_000, 0, "Free"), (1_000_000, 70, "Starter"),
(5_000_000, 200, "Standard"), (50_000_000, 800, "Enterprise")
],
"qdrant_cloud": [
(100_000, 0, "Free"), (500_000, 50, "Starter"),
(2_000_000, 150, "Business"), (20_000_000, 500, "Enterprise")
],
"weaviate_cloud": [
(50_000, 0, "Sandbox"), (500_000, 75, "Starter"),
(5_000_000, 250, "Business"), (50_000_000, 900, "Enterprise")
],
"chromadb_self": [
(100_000, 20, "t3.small"), (500_000, 60, "t3.medium"),
(2_000_000, 150, "m5.large"), (10_000_000, 400, "m5.xlarge")
],
}
if provider not in tiers_map:
return []
tiers = tiers_map[provider]
cliffs = []
current_tier_idx = 0
vectors = starting_vectors
# Find the current tier
for i, (max_v, _, _) in enumerate(tiers):
if vectors <= max_v:
current_tier_idx = i
break
import math
for next_idx in range(current_tier_idx + 1, len(tiers)):
next_max = tiers[next_idx - 1][0] if next_idx > 0 else 0
prev_cost = tiers[next_idx - 1][1]
next_cost = tiers[next_idx][1]
# In how many months do you reach that tier?
if growth_rate_monthly > 0 and vectors < next_max:
months = math.log(next_max / vectors) / math.log(1 + growth_rate_monthly)
else:
months = float('inf')
cost_jump = next_cost - prev_cost
jump_pct = (cost_jump / prev_cost * 100) if prev_cost > 0 else float('inf')
cliffs.append({
"from_tier": tiers[next_idx - 1][2],
"to_tier": tiers[next_idx][2],
"at_vectors": next_max,
"months_to_reach": round(months, 1) if months != float('inf') else "∞",
"cost_jump": cost_jump,
"cost_jump_pct": round(jump_pct, 0) if jump_pct != float('inf') else "∞",
"new_monthly_cost": next_cost
})
return cliffs
# Example: startup with 50K vectors, 15%/month growth
print("=== Cliff Edges: when does cost jump? ===\n")
for provider in ["pinecone", "qdrant_cloud", "weaviate_cloud"]:
cliffs = find_cliff_edges(provider, 0.15, 50_000)
print(f"{provider}:")
for cliff in cliffs:
print(f" 📈 {cliff['from_tier']} → {cliff['to_tier']}: "
f"+${cliff['cost_jump']}/month ({cliff['cost_jump_pct']}% more) "
f"in ~{cliff['months_to_reach']} months "
f"(at {cliff['at_vectors']:,} vectors)")
print()
Migration break-even
When is migrating worth it? When the accumulated savings exceed the migration cost:
def migration_break_even(
current_monthly_tco: float,
new_monthly_tco: float,
migration_cost: float,
risk_buffer_pct: float = 0.2
) -> dict:
"""
Calculate the break-even of a migration.
Args:
current_monthly_tco: Current monthly TCO
new_monthly_tco: Expected monthly TCO after migrating
migration_cost: Total migration cost (one-time)
risk_buffer_pct: Risk buffer (20% by default)
"""
monthly_savings = current_monthly_tco - new_monthly_tco
if monthly_savings <= 0:
return {
"viable": False,
"reason": "The new provider is more expensive or equal",
"monthly_savings": round(monthly_savings, 2)
}
migration_with_buffer = migration_cost * (1 + risk_buffer_pct)
payback_months = migration_with_buffer / monthly_savings
year_1_net = (monthly_savings * 12) - migration_with_buffer
year_2_net = (monthly_savings * 24) - migration_with_buffer
return {
"viable": True,
"monthly_savings": round(monthly_savings, 2),
"migration_cost": round(migration_cost, 2),
"migration_with_buffer": round(migration_with_buffer, 2),
"payback_months": round(payback_months, 1),
"year_1_net_savings": round(year_1_net, 2),
"year_2_net_savings": round(year_2_net, 2),
"recommendation": (
"✅ Migrate now" if payback_months < 6 else
"🟡 Plan migration" if payback_months < 9 else
"🟠 Evaluate quarterly" if payback_months < 12 else
"🔴 Don't migrate yet"
)
}
# Scenario 1: ChromaDB self-hosted → Qdrant Cloud
scenario_1 = migration_break_even(
current_monthly_tco=1200, # ChromaDB self-hosted with high ops
new_monthly_tco=350, # Qdrant Cloud managed
migration_cost=8000, # 2 weeks of engineering + re-embedding
risk_buffer_pct=0.25
)
# Scenario 2: Pinecone → Qdrant Cloud (smaller saving)
scenario_2 = migration_break_even(
current_monthly_tco=500,
new_monthly_tco=350,
migration_cost=6000,
risk_buffer_pct=0.20
)
# Scenario 3: Managed → Self-hosted (is it worth it?)
scenario_3 = migration_break_even(
current_monthly_tco=350, # Current managed
new_monthly_tco=1200, # Self-hosted with ops
migration_cost=5000
)
for name, result in [
("ChromaDB Self → Qdrant Cloud", scenario_1),
("Pinecone → Qdrant Cloud", scenario_2),
("Managed → Self-hosted", scenario_3)
]:
print(f"\n=== {name} ===")
if result["viable"]:
print(f" Monthly savings: ${result['monthly_savings']}/month")
print(f" Migration cost (+buffer): ${result['migration_with_buffer']}")
print(f" Payback: {result['payback_months']} months")
print(f" Net savings year 1: ${result['year_1_net_savings']}")
print(f" Net savings year 2: ${result['year_2_net_savings']}")
print(f" → {result['recommendation']}")
else:
print(f" ❌ {result['reason']}")
print(f" Monthly difference: ${result['monthly_savings']}/month")
12-month cost projection
def project_costs_12m(
starting_vectors: int,
growth_rate_monthly: float,
provider: str,
engineer_rate: float = 70
) -> list[dict]:
"""Project monthly costs over 12 months."""
projections = []
for month in range(1, 13):
vectors = int(starting_vectors * (1 + growth_rate_monthly) ** month)
model = cost_model_by_scale(provider, vectors)
ops_cost = model["ops_hours"] * engineer_rate
total = model["service_cost"] + ops_cost
projections.append({
"month": month,
"vectors": vectors,
"tier": model["tier"],
"service_cost": model["service_cost"],
"ops_cost": ops_cost,
"total": total
})
return projections
# Compare projections for 3 providers
print("=== 12-month projection (50K vectors, 15% growth/month) ===\n")
for provider in ["pinecone", "qdrant_cloud", "chromadb_self"]:
proj = project_costs_12m(50_000, 0.15, provider)
total_12m = sum(p["total"] for p in proj)
print(f"{provider}:")
for p in [proj[0], proj[2], proj[5], proj[8], proj[11]]:
print(f" Month {p['month']:>2}: {p['vectors']:>10,} vectors | "
f"Tier: {p['tier']:<10} | ${p['total']:>7,.0f}/month")
print(f" TOTAL 12 months: ${total_12m:,.0f}")
print()
Scenario model: conservative, expected, aggressive
Don't project a single number. Model three scenarios:
def three_scenario_projection(
starting_vectors: int,
provider: str,
engineer_rate: float = 70
) -> dict:
"""
Project TCO over 12 months across three growth scenarios.
"""
scenarios = {
"conservative": {"growth": 0.08, "label": "8%/month growth"},
"expected": {"growth": 0.15, "label": "15%/month growth"},
"aggressive": {"growth": 0.25, "label": "25%/month growth"},
}
results = {}
for scenario_name, config in scenarios.items():
proj = project_costs_12m(starting_vectors, config["growth"], provider, engineer_rate)
total_12m = sum(p["total"] for p in proj)
final_vectors = proj[-1]["vectors"]
final_tier = proj[-1]["tier"]
final_monthly = proj[-1]["total"]
results[scenario_name] = {
"label": config["label"],
"growth_rate": config["growth"],
"total_12m": round(total_12m, 0),
"final_vectors": final_vectors,
"final_tier": final_tier,
"final_monthly": round(final_monthly, 0),
"avg_monthly": round(total_12m / 12, 0)
}
return results
print("=== 3 Scenarios: Qdrant Cloud (start: 50K vectors) ===\n")
scenarios = three_scenario_projection(50_000, "qdrant_cloud")
print(f"{'Scenario':<20} {'Vectors 12m':<15} {'Final tier':<12} "
f"{'Last month':<12} {'Total 12m':<12} {'Avg/month'}")
print("-" * 85)
for name, data in scenarios.items():
print(f"{data['label']:<20} {data['final_vectors']:>12,} {data['final_tier']:<12} "
f"${data['final_monthly']:>9,.0f} ${data['total_12m']:>9,.0f} ${data['avg_monthly']:>9,.0f}")
spread = scenarios["aggressive"]["total_12m"] - scenarios["conservative"]["total_12m"]
print(f"\nConservative-aggressive spread: ${spread:,.0f}/year")
print("The spread tells you how much financial uncertainty you have. If it's > 50% of the budget, plan for scenarios.")
When "free" becomes expensive
def free_tier_analysis(starting_vectors: int, growth_rate: float) -> dict:
"""Analyze when each provider charges for the first time."""
import math
free_limits = {
"Pinecone": 100_000,
"Qdrant Cloud": 100_000,
"Weaviate Cloud": 50_000,
"Milvus (Zilliz)": 100_000,
"ChromaDB": float('inf'), # Open source, always "free" (but you pay for the VM)
}
first_paid_cost = {
"Pinecone": 70,
"Qdrant Cloud": 50,
"Weaviate Cloud": 75,
"Milvus (Zilliz)": 65,
"ChromaDB": 20, # Minimum VM cost
}
results = {}
for provider, limit in free_limits.items():
if starting_vectors >= limit:
months_free = 0
elif limit == float('inf'):
months_free = float('inf')
else:
months_free = math.log(limit / starting_vectors) / math.log(1 + growth_rate)
results[provider] = {
"free_limit": limit if limit != float('inf') else "∞",
"months_free": round(months_free, 1) if months_free != float('inf') else "∞",
"first_paid_cost": first_paid_cost[provider],
"vectors_at_transition": (
int(starting_vectors * (1 + growth_rate) ** months_free)
if months_free != float('inf') and months_free > 0
else starting_vectors
)
}
return results
print("=== When does the free tier run out? (start: 30K vectors, 20%/month) ===\n")
free_analysis = free_tier_analysis(30_000, 0.20)
print(f"{'Provider':<20} {'Free limit':<15} {'Months free':<15} "
f"{'First payment':<15}")
print("-" * 65)
for provider, data in free_analysis.items():
limit = f"{data['free_limit']:,}" if isinstance(data["free_limit"], int) else data["free_limit"]
months = data["months_free"]
cost = f"${data['first_paid_cost']}/month"
print(f"{provider:<20} {limit:<15} {months:<15} {cost}")
print("\n⚠️ Plan your budget BEFORE the free tier runs out.")
print(" The worst time to negotiate pricing is when you're already in production.")
Correct-decision ROI calculator
def roi_good_decision(
bad_choice_monthly: float,
good_choice_monthly: float,
evaluation_hours: float,
engineer_rate: float,
months: int = 12
) -> dict:
"""
Calculate the ROI of investing time in evaluating correctly.
Args:
bad_choice_monthly: Monthly TCO of choosing wrong
good_choice_monthly: Monthly TCO of choosing well
evaluation_hours: Hours invested in evaluation
engineer_rate: Engineer's rate
months: Time horizon
"""
evaluation_cost = evaluation_hours * engineer_rate
monthly_savings = bad_choice_monthly - good_choice_monthly
total_savings = monthly_savings * months
net_roi = total_savings - evaluation_cost
roi_pct = (net_roi / evaluation_cost) * 100 if evaluation_cost > 0 else 0
return {
"evaluation_cost": round(evaluation_cost, 2),
"monthly_savings": round(monthly_savings, 2),
"total_savings_period": round(total_savings, 2),
"net_roi": round(net_roi, 2),
"roi_percentage": round(roi_pct, 0),
"payback_hours": round(evaluation_cost / monthly_savings, 1) if monthly_savings > 0 else float('inf')
}
roi = roi_good_decision(
bad_choice_monthly=1200, # ChromaDB self-hosted with high ops
good_choice_monthly=400, # Well-chosen managed
evaluation_hours=20, # 2.5 days of evaluation (this module)
engineer_rate=70,
months=12
)
print("=== ROI of evaluating correctly ===\n")
print(f"Evaluation cost: ${roi['evaluation_cost']} ({20}h × $70/hr)")
print(f"Monthly savings: ${roi['monthly_savings']}/month")
print(f"Total savings (12 months): ${roi['total_savings_period']}")
print(f"Net ROI: ${roi['net_roi']}")
print(f"ROI %: {roi['roi_percentage']}%")
print(f"\nThe {20} hours of evaluation pay for themselves in "
f"{roi['payback_hours']} months of savings.")
🔧 Troubleshooting
Problem 1: "Managed always looks more expensive when comparing sticker price"
Symptom: You compare $200/month managed vs $60/month VM and pick self-hosted.
Solution: Never compare sticker price. Calculate the full TCO: $60 VM + 12h × $70/hr ops = $900/month. The $200 managed with 2h × $70 ops = $340/month. Managed is 62% cheaper in TCO.
Problem 2: "I don't know how much an hour of my team is worth"
Symptom: You don't have an internal rate to calculate ops cost.
Solution: Use this rule: annual salary / 2000 = approximate hourly rate. For $120K/year → $60/hr. If you don't know salaries, use $50/hr as a floor and $100/hr as a ceiling. Run the model with both — if the conclusion doesn't change, the exact rate doesn't matter.
Problem 3: "The free tier is enough forever for my PoC"
Symptom: You believe you'll never exceed 100K vectors.
Solution: Project your growth. At 15% monthly, 50K vectors reach 100K in 5 months. Plan the budget for the first paid tier BEFORE you hit the limit. The worst time to negotiate is when you're already in production with real data.
Problem 4: "I want to migrate for performance but I don't know if the ROI justifies it"
Symptom: Your latency rose but you haven't quantified the business impact.
Solution: Tie latency to business metrics: how many users churn due to slowness? If 100ms extra = 1% churn and you have 10K queries/day, calculate the cost of that churn vs the migration cost.
Problem 5: "Prices change every quarter"
Symptom: Your cost model becomes outdated quickly.
Solution: Don't try to predict future prices. Model with current prices and add a 15-20% buffer for inflation. Re-evaluate quarterly with real prices. If a provider drops prices significantly, that triggers your re-evaluation trigger.
🏋️ Exercises
Exercise 1: Calculate your real TCO
Calculate the monthly TCO for your current project (or the DocBot project from the previous capsule) with 3 different providers. Include all 4 dimensions.
Solution
# DocBot: 200K vectors, team of 5, no DevOps, $400/month budget
providers_tco = {}
for provider_name, config in [
("Pinecone", {"infra": 200, "ops": 2, "incident_prob": 0.05, "incident_cost": 500, "integration": 16}),
("Qdrant Cloud", {"infra": 150, "ops": 3, "incident_prob": 0.08, "incident_cost": 600, "integration": 20}),
("ChromaDB Self", {"infra": 60, "ops": 12, "incident_prob": 0.15, "incident_cost": 800, "integration": 40}),
]:
tco = calculate_tco(
infra_cost=config["infra"],
ops_hours=config["ops"],
engineer_rate=70,
incident_probability=config["incident_prob"],
incident_cost=config["incident_cost"],
integration_hours=config["integration"],
integration_rate=70
)
providers_tco[provider_name] = tco
print(f"{provider_name}: ${tco['total_monthly']}/month (${tco['total_annual']}/year)")
print(f" Infra: ${tco['infra']} ({tco['breakdown_pct']['infra']}%)")
print(f" Ops: ${tco['ops']} ({tco['breakdown_pct']['ops']}%)")
print(f" Risk: ${tco['risk']} ({tco['breakdown_pct']['risk']}%)")
print()
Exercise 2: Identify cliff edges for your project
With 80K current vectors and 20% monthly growth, in how many months do you reach the first cliff edge of each provider? How much does the cost jump?
Solution
print("Cliff edges from 80K vectors, 20%/month growth:\n")
for provider in ["pinecone", "qdrant_cloud", "weaviate_cloud"]:
cliffs = find_cliff_edges(provider, 0.20, 80_000)
print(f"{provider}:")
for cliff in cliffs:
print(f" {cliff['from_tier']} → {cliff['to_tier']}")
print(f" In ~{cliff['months_to_reach']} months ({cliff['at_vectors']:,} vectors)")
print(f" Jump: +${cliff['cost_jump']}/month")
print()
# Example result:
# Pinecone: Free → Starter in ~1.2 months (+$70/month)
# Weaviate: Sandbox → Starter very soon (50K limit, you're almost there)
# Plan: have the budget approved BEFORE the cliff
Exercise 3: Migration break-even
Your team uses ChromaDB self-hosted (TCO: $1,100/month). You're considering migrating to Qdrant Cloud (estimated TCO: $350/month). The migration cost is $7,500. Is it worth it? What's the payback?
Solution
result = migration_break_even(
current_monthly_tco=1100,
new_monthly_tco=350,
migration_cost=7500,
risk_buffer_pct=0.25 # 25% buffer for unexpected issues
)
print("=== Break-even: ChromaDB Self → Qdrant Cloud ===\n")
print(f"Monthly savings: ${result['monthly_savings']}/month")
print(f"Migration with buffer (25%): ${result['migration_with_buffer']}")
print(f"Payback: {result['payback_months']} months")
print(f"Net savings year 1: ${result['year_1_net_savings']}")
print(f"Net savings year 2: ${result['year_2_net_savings']}")
print(f"Recommendation: {result['recommendation']}")
# Expected result:
# Monthly savings: $750/month
# Payback: ~12.5 months with buffer
# Year 1 net: ~-$375 (you haven't recovered yet)
# Year 2 net: ~$8,625
# Recommendation: 🔴 Don't migrate yet
# The 12.5-month payback is long. Valid only if you plan
# to stay 2+ years with the new provider.
Exercise 4: 3-scenario projection
Project Pinecone's cost over 12 months across three growth scenarios (8%, 15%, 25% monthly) starting from 50K vectors. In which scenario do you exceed your $400/month budget?
Solution
scenarios = three_scenario_projection(50_000, "pinecone", engineer_rate=70)
print("=== Pinecone projection: 3 scenarios ===\n")
budget = 400
for name, data in scenarios.items():
over_budget = data["final_monthly"] > budget
status = "🔴 OVER BUDGET" if over_budget else "✅ Within budget"
print(f"{data['label']}:")
print(f" Vectors at month 12: {data['final_vectors']:,}")
print(f" Final tier: {data['final_tier']}")
print(f" Month 12 cost: ${data['final_monthly']}/month {status}")
print(f" Total 12 months: ${data['total_12m']:,.0f}")
print(f" Average monthly: ${data['avg_monthly']}/month")
print()
# Identify in which month of each scenario you exceed $400/month
for name, data in scenarios.items():
proj = project_costs_12m(50_000, data["growth_rate"], "pinecone")
for p in proj:
if p["total"] > budget:
print(f"{data['label']}: you exceed ${budget}/month in month {p['month']}")
break
else:
print(f"{data['label']}: you don't exceed ${budget}/month in 12 months")
Exercise 5: ROI of this module
Calculate the ROI of having invested 8 hours in this evaluation module, assuming it saved you from choosing wrong (ChromaDB self-hosted with $1,200/month TCO vs managed at $400/month).
Solution
module_roi = roi_good_decision(
bad_choice_monthly=1200,
good_choice_monthly=400,
evaluation_hours=8, # This whole module
engineer_rate=70,
months=12
)
print("=== ROI of investing 8 hours in evaluation ===\n")
print(f"Evaluation cost: ${module_roi['evaluation_cost']}")
print(f"Monthly savings: ${module_roi['monthly_savings']}")
print(f"12-month savings: ${module_roi['total_savings_period']}")
print(f"Net ROI: ${module_roi['net_roi']}")
print(f"ROI %: {module_roi['roi_percentage']}%")
print(f"Payback: {module_roi['payback_hours']} months")
print()
print(f"Each hour invested in evaluation saves you "
f"${module_roi['net_roi'] / 8:.0f} in the first year.")
# Result: ~$9,040 net ROI for 8 hours of work.
# That's ~$1,130/hour of return.
# Conclusion: evaluating correctly is one of the best
# time investments you can make.
🔗 Project connection: Decision Questionnaire
In your Decision Questionnaire, the cost analysis is the section that turns your technical recommendation into a financial recommendation. Your questionnaire should:
- Ask for the budget and team composition (to calculate the hourly rate)
- Calculate the TCO of the 2-3 finalist options (not just the sticker price)
- Project over 12 months with the three growth scenarios
- Identify cliff edges and alert the user before the cost jumps
- Generate an ROI section that justifies the decision to non-technical stakeholders
Reuse calculate_tco(), cost_model_by_scale(), find_cliff_edges(), and migration_break_even() directly.
Summary
- The sticker price is 30-40% of the real cost. The TCO includes ops, risk, integration, and opportunity.
- Operational cost dominates in most cases. 12h/month × $70/hr = $840/month of "invisible" cost.
- The 5 hidden costs: egress, embeddings, over-provisioning, future migration, opportunity.
- Model at 3 scales (10K, 100K, 1M) to understand the cost curve.
- Cliff edges are real: today's free tier is $70-200/month in 3-6 months.
- Migration break-even: only migrate if the payback is < 9 months.
- Project across 3 scenarios (conservative, expected, aggressive) to manage uncertainty.
- The ROI of evaluating correctly is massive: 8 hours of analysis can save $9K+/year.
Additional resources
- AWS Pricing Calculator — AWS infrastructure cost estimator
- GCP Pricing Calculator — Google Cloud estimator
- Pinecone Pricing — Pinecone pricing and calculator
- Qdrant Cloud Pricing — Qdrant managed tiers and prices
- Weaviate Pricing — Weaviate Cloud Services pricing
- OpenAI Embeddings Pricing — Embedding generation costs
- FinOps Foundation — Framework for cloud cost management
- Zilliz Cloud Pricing — Managed Milvus (Zilliz) pricing
Estimated time: 25-35 minutes
Next: 06-recommendations-by-scenario.md