DEV Community

Muskan _zop
Muskan _zop

Posted on

Cloud Cost Per Customer: Unit Economics Without Rebuilding Your Data Warehouse

At some point a board deck needs a number nobody has: what it costs to serve customer X. Or its sharper cousin: which ten customers have the worst gross margin. Engineering opens the cloud bill and finds it organized by service and region, not by customer, and the honest first answer is "we don't know."

The reflex answer is a project: pipe the billing export into the warehouse, join it against tenant telemetry, hire the allocation logic into existence, six months, two data engineers. Sometimes that's eventually right. But pricing and margin decisions rarely need it, because a defensible cost-per-customer is mostly an allocation policy plus two small tables, and you can stand it up in weeks on the tooling you already have.

Why the bill can't answer this by itself

Cloud costs attach to resources. Customers, in any multi-tenant architecture, live inside shared resources: one Aurora cluster serving four hundred tenants, one Kubernetes cluster running everyone's workloads, one load balancer in front of all of it. The bill knows what the cluster costs. It cannot know which tenant caused which fraction, because the cloud provider never sees your tenants.

So per-customer cost is always a blend of two different problems:

  • Dedicated resources (a customer's own database, their VPC, their SFTP endpoint): attribution is a tagging exercise, and it's exact.
  • Shared resources (the multi-tenant everything else): attribution requires an allocation key, and it's a modeled estimate no matter who builds it or what it costs.

Accepting that second sentence early saves the whole project. Cost per customer is a management number, not a billing number. It needs to be consistent, explainable, and directionally right; it does not need to be exact to the cent, and pretending it can be is how six-month projects happen.

The three-bucket method

Split the monthly bill into exactly three buckets, and give each its own rule.

Bucket 1: dedicated. Anything that belongs to one customer gets a tenant tag (or label) with the customer id, enforced at provision time. Even in deeply multi-tenant products, dedicated resources are often 20-40% of spend (single-tenant databases for enterprise tiers, per-customer storage prefixes, dedicated compute for the big accounts). This bucket is exact, and it's the anchor that makes the rest credible.

Bucket 2: shared-variable. The multi-tenant systems whose cost scales with usage: the shared database, the API tier, the job runners, egress. Each system gets one usage driver, chosen from telemetry you already collect:

  • API tier: requests per tenant, from gateway or load balancer logs.
  • Shared database: read/write units or query volume per tenant, from a nightly query against your own usage tables.
  • Kubernetes: if tenants map to namespaces, CPU and memory requests per namespace; if they don't, requests per tenant at the ingress.
  • Storage: bytes per tenant, from a nightly listing or your application's own accounting.
  • Batch and jobs: job-minutes per tenant, from the scheduler you already run.

The key realization: what you need is a tiny per-tenant usage table, shaped like (day, tenant_id, system, units). It's a handful of scheduled queries against logs and databases you already have. This is the thing people think requires a warehouse rebuild, and it's usually a few hundred rows a day.

Bucket 3: shared-fixed. The platform tax: control planes, NAT gateways, observability, security tooling, the staging environment. No usage driver honestly divides this, so don't pretend one does. Pick a policy (evenly per customer, or proportional to revenue, or proportional to buckets 1+2) and label it as policy on every report. Keeping it as its own visible line, rather than smearing it into the variable rate, is what keeps the whole model defensible when someone challenges a number.

The math is one join

For each shared system, each day:

tenant share = system cost that day × (tenant units ÷ total units)

Worked example: the shared Aurora cluster costs $9,000 a month, about $300 a day. Tenant A generated 42% of read/write units on Tuesday: their share is $126 for that day. Sum across days and systems, add their dedicated bucket, add their fixed-share line, and that's the monthly statement:

  • Dedicated (tagged): $2,210
  • Shared-variable (allocated by drivers): $3,804
  • Platform share (policy: even split): $310
  • Cost to serve, September: $6,324

Put revenue next to it and gross margin per customer exists for the first time. The whole computation is a 20-line SQL job or a small script: billing data grouped by system on one side, the usage table on the other, one join.

Two rules keep it honest:

  1. The 100% rule. The sum of all customer allocations must equal the actual bill, every month, to the dollar. No orphan costs, no "unallocated" leak that quietly grows. If a cost fits no bucket, it goes to bucket 3 visibly, not nowhere.
  2. One driver per system. The temptation is blended drivers (40% requests, 60% storage, weighted by moon phase). Every added term makes the number less explainable and no more actionable. If a system genuinely has two cost dimensions, split it into two systems.

What this buys, and what it doesn't

At this fidelity you get numbers that are roughly ±10-15% on shared costs, exact on dedicated ones, and consistent month over month. That answers the questions that matter: which customers are margin-negative, whether the new enterprise tier is priced above its cost to serve, which tenant's usage grew 4x while their bill grew 0x, what onboarding another hundred mid-size tenants does to infrastructure spend.

What it doesn't do: per-query attribution, per-feature costing, real-time per-tenant meters. Those need heavier machinery (eBPF-level metering, request-scoped cost tracing), and almost every team that builds them does so after the three-bucket model has already changed two pricing decisions, not before.

The anti-patterns, from teams that learned expensively:

  • Allocating by customer count. Even splits on variable systems punish small customers and hide whales. Count-based allocation is only ever acceptable in bucket 3.
  • Letting the shared buckets rot. Bucket 2 and 3 percentages should be reviewed quarterly; a healthy trajectory moves spend from bucket 3 toward buckets 1 and 2 as tagging and telemetry improve.
  • Warehouse-first. Standing up the full billing-export-to-warehouse pipeline before proving the usage drivers means six months of plumbing before the first margin conversation. Prove the model on last month's bill and a spreadsheet-scale usage table; industrialize after it changes a decision.
  • Precision theater. Reporting $6,324.17 implies the model knows cents. Report $6.3k and keep the trust.

FAQ

How do I calculate cloud cost per customer?

Split the bill three ways: dedicated resources (tag them with a tenant id; exact), shared-variable systems (allocate each system's cost by one usage driver per tenant, like requests or read units), and shared-fixed platform costs (divide by a stated policy). Sum the three per customer, and enforce that all allocations total the actual bill.

How do I attribute costs in a multi-tenant or shared architecture?

You can't observe it from the bill; you model it with an allocation key. Build a small per-tenant usage table from telemetry you already have (gateway logs, database usage stats, namespace requests) and prorate each shared system's cost by each tenant's share of one driver. It's an estimate; consistency and explainability matter more than decimal precision.

What tags do I need for per-customer cost?

One: a tenant (or customer_id) tag on every resource that serves exactly one customer, enforced at provision time. Multi-tenant resources shouldn't carry a tenant tag at all; they're allocated by driver instead. A tag audit that asks only "dedicated or shared?" is a one-day exercise.

Do I need the billing export in a data warehouse for this?

Not to start. The model needs monthly (or daily) cost per system, which the cost console or a simple export gives you, plus a small usage table. Move to a proper billing-export pipeline when the model is changing decisions and you want daily granularity and history, not before.

How accurate is cost-per-customer allocation?

Dedicated costs are exact. Driver-based allocations are typically within 10-15% of any more sophisticated method, and the error is stable month over month, which is what pricing and margin decisions need. If two allocation methods disagree enough to change a decision, the decision was too close to be made on cost alone.

Is cloud cost per customer the same as COGS?

It's the largest infrastructure component of it. True cost-to-serve adds third-party services (email, payments, LLM APIs), support load, and sometimes onboarding effort. The three-bucket structure extends to those cleanly: most SaaS vendors' invoices are dedicated or driver-allocatable the same way.

Top comments (0)