DEV Community

Vinicius Fagundes
Vinicius Fagundes

Posted on

You saved $80K self-hosting Spark. It cost you $400K.

Then he walked me through the team.

Two senior engineers, 30% of their time, on Spark cluster maintenance. One person on-call for jobs that fail at 3am. A third spent last quarter upgrading the Kubernetes operator. The lead data engineer quit in April — burnout, the team said.

They didn't save $80K. They spent $400K in engineer-hours and called it freedom.

Nobody runs this math. So let's actually run it — with code you can drop your own numbers into, because the values are illustrative and the model is the point.

The math nobody runs

The pitch for self-hosting is always the invoice: the managed platform costs $80K/year more, so you self-host and "save" $80K. That $80K is real. It's also the only number anyone counts.

Here's the rest of the bill — the part that shows up as people, not line items:

# Fully-loaded annual cost of one senior data engineer.
# Salary + benefits + payroll tax + overhead + equipment. Adjust to your market.
LOADED_SENIOR = 200_000

# What "self-hosting" saves on the invoice — the number that sold the decision.
managed_premium = 80_000   # you pay this much MORE per year for the managed platform

# What self-hosting actually costs in people, from the team we walked through:
ops = {
    "2 seniors @ 30% on cluster maintenance":            2 * 0.30 * LOADED_SENIOR,
    "1 engineer on-call (the 3am / weekend tax)":        0.20 * LOADED_SENIOR,
    "1 engineer, a quarter on the K8s operator upgrade": 0.25 * LOADED_SENIOR,
}

# The lead quit in April. Replacing a senior = recruiting + ~6mo ramp.
attrition = 0.95 * LOADED_SENIOR

hidden = sum(ops.values()) + attrition
net    = managed_premium - hidden   # what self-hosting "saved" minus what it cost

for k, v in ops.items():
    print(f"  {k:<50} ${v:>10,.0f}")
print(f"  {'attrition: backfilling the lead who burned out':<50} ${attrition:>10,.0f}")
print(f"  {'-'*63}")
print(f"  {'hidden cost of self-hosting':<50} ${hidden:>10,.0f}")
print(f"  {'invoice savings that sold the decision':<50} ${managed_premium:>10,.0f}")
print(f"  {'NET':<50} ${net:>10,.0f}")
Enter fullscreen mode Exit fullscreen mode
  2 seniors @ 30% on cluster maintenance             $   120,000
  1 engineer on-call (the 3am / weekend tax)         $    40,000
  1 engineer, a quarter on the K8s operator upgrade  $    50,000
  attrition: backfilling the lead who burned out     $   190,000
  ---------------------------------------------------------------
  hidden cost of self-hosting                        $   400,000
  invoice savings that sold the decision             $    80,000
  NET                                                $  -320,000
Enter fullscreen mode Exit fullscreen mode

$80K saved. $320K underwater. And that's before you count the roadmap you never shipped, because four engineers were quietly moonlighting as a platform team instead of building the product.

"But at scale, self-hosting wins" — fine, so where's the line?

I'm not here to tell you managed always wins. It doesn't. There's a real crossover, and it's worth finding instead of arguing about in the abstract.

Self-hosting wins when the premium a managed vendor charges grows bigger than the cost of a team that runs the platform well — and "well" means a real team whose entire job is the platform, not four people running it on the side of their actual roadmap.

LOADED_SENIOR = 200_000

# A platform team that runs this WELL — a real team, not 4 people moonlighting.
platform_team = 4 * LOADED_SENIOR   # $800,000/yr

# Managed premium scales with your footprint (~35% platform tax on raw compute).
# Team cost is roughly fixed. Find where they cross.
print(f"  a real platform team costs ~${platform_team:,.0f}/yr\n")
for annual_compute in [200_000, 1_000_000, 3_000_000, 8_000_000]:
    premium = 0.35 * annual_compute
    verdict = "self-host wins" if premium > platform_team else "pay for managed"
    print(f"  compute ${annual_compute:>9,}/yr   managed premium ${premium:>10,.0f}   -> {verdict}")

crossover = platform_team / 0.35
print(f"\n  crossover ~= ${crossover:,.0f}/yr in compute")
Enter fullscreen mode Exit fullscreen mode
  a real platform team costs ~$800,000/yr

  compute $  200,000/yr   managed premium $    70,000   -> pay for managed
  compute $1,000,000/yr   managed premium $   350,000   -> pay for managed
  compute $3,000,000/yr   managed premium $ 1,050,000   -> self-host wins
  compute $8,000,000/yr   managed premium $ 2,800,000   -> self-host wins

  crossover ~= $2,285,714/yr in compute
Enter fullscreen mode Exit fullscreen mode

The line sits around $2.3M/year in compute. Below it — where the overwhelming majority of teams actually live — you're paying a platform team to lose money against a bill. Above it, a real platform org starts to pay for itself. The mistake was never self-hosting. It's self-hosting at a tenth of the scale where it makes sense, with a tenth of the team it needs.

The real question isn't "cloud vs self-host"

So the 2026 question isn't build-vs-buy in the abstract. It's narrower and far more useful: which layer is your edge actually in? Build the layer that's yours. Pay for every layer that isn't.

Here's the map I use:

Layer Commodity or edge? Call Why
Object storage (S3/GCS) commodity buy Nobody has ever won a market on a better bucket.
Warehouse (Snowflake/BigQuery) commodity buy Hundreds of vendor engineers make it fast so you don't have to.
Orchestration (Airflow/Prefect) commodity buy A scheduler is undifferentiated heavy lifting.
Compute engine (Spark) commodity buy You will not out-tune the people who wrote it.
Vector DB commodity buy It's an index. Managed until proven otherwise.
Foundation model commodity buy Rent the weights. Your edge is never the base model.
Transformation logic (dbt, business rules) edge build This encodes what your business actually knows.
Features / feature logic edge build The signal is yours; it's the whole game.
Retrieval strategy + eval harness edge build RAG quality lives here, not in the vector DB.
The product on top edge build Obviously. This is the only thing customers pay for.

Read it top to bottom and the pattern falls out. Everything that's undifferentiated heavy lifting — storage, warehouse, orchestration, the engine, the model — is a commodity someone else already runs better than you will. Everything that's yours — the transformation logic, the features, the retrieval strategy, the product — is where engineering time actually compounds.

Most teams do the exact opposite. They build their own warehouse to avoid lock-in, then YOLO their prompts straight into a model API with no eval harness and no retrieval strategy. They protect the commodity and surrender the differentiator.

What you actually inherit when you self-host

Invoice "savings" hide a transfer of work, not an elimination of it. Self-host the orchestrator and you now own: version upgrades, CVE patching, autoscaling, HA and failover, backup and restore, the on-call rotation, and a values file nobody remembers writing.

Managed, the whole ops surface collapses to a config:

# Managed orchestration — upgrades, scaling, HA, patching: not your problem.
orchestration:
  provider: managed
  version: pinned-latest
Enter fullscreen mode Exit fullscreen mode

Self-hosted, it's the part that never makes the pitch slide:

# The same "run Airflow", except now it's yours forever:
helm upgrade airflow apache-airflow/airflow \
  --values values.prod.yaml \        # 400 lines you now own and must reason about
  --set images.airflow.tag=2.9.3     # ...after you test the DAG-parsing regression
# then: patch the CVE, resize the workers, chase KubernetesExecutor pod churn,
#       restore the metadata DB someone truncated, and do it again next quarter.
Enter fullscreen mode Exit fullscreen mode

Neither is wrong. But one of them is a dropdown, and the other is two senior engineers at 30% plus a Slack channel called #pipelines-help. The decision is fine — as long as you price both sides of it, which is exactly the step that got skipped.

The principle

Pay for the commodity. Build the differentiator. Every hour your seniors spend keeping someone else's undifferentiated infrastructure alive is an hour not spent on the one thing your competitor can't just buy — the logic, the features, the product that's actually yours.

And notice this was never really a cloud-vs-self-host argument. It's a data-engineering allocation problem wearing an infrastructure costume: where does your team's finite senior time actually go, and does that map to where your edge actually is?

What's the most expensive thing your team built that you should have just paid for?


The numbers here are illustrative placeholders — the loaded salary, the managed premium, the 35% tax are all knobs. Plug in your own before you make a real build-vs-buy call; the model is the point, not the values.

I'm Vinicius Fagundes — principal data engineer, independent, and an MBA lecturer in São Paulo. I spend my time deciding which layers a team should own and which they should rent, then building the ones that are theirs. That work lives at vf-insights.com.

Top comments (0)