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:

  1. Ask for the budget and team composition (to calculate the hourly rate)
  2. Calculate the TCO of the 2-3 finalist options (not just the sticker price)
  3. Project over 12 months with the three growth scenarios
  4. Identify cliff edges and alert the user before the cost jumps
  5. 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

  1. AWS Pricing Calculator — AWS infrastructure cost estimator
  2. GCP Pricing Calculator — Google Cloud estimator
  3. Pinecone Pricing — Pinecone pricing and calculator
  4. Qdrant Cloud Pricing — Qdrant managed tiers and prices
  5. Weaviate Pricing — Weaviate Cloud Services pricing
  6. OpenAI Embeddings Pricing — Embedding generation costs
  7. FinOps Foundation — Framework for cloud cost management
  8. Zilliz Cloud Pricing — Managed Milvus (Zilliz) pricing

Estimated time: 25-35 minutes Next: 06-recommendations-by-scenario.md