gsantana.dev

Cost Transparency

If nobody can say what a request, a tenant or an order costs, the bill is just a rumor. Design the solution so its price tag is visible.

27 min read

Introduction

Picture the scene. It's the first Monday of the month, the coffee is still hot, and someone from Finance drops a screenshot in the team channel: the cloud bill doubled. No launch, no marketing campaign, no Black Friday. Just a number twice as big as last month's and a very polite question: "Can someone explain this?"

What follows is a classic. The platform team says the cluster didn't grow. The data team swears the warehouse is "the same as always". The product team points out that the number of customers barely moved. Two engineers spend three days digging through the billing console, filtering by service, by region, by resource group, and at the end they have a spreadsheet full of guesses and one honest conclusion: nobody knows why the bill doubled.

That is exactly the situation the Cost Transparency principle exists to prevent. The organizational side of this story (the FinOps culture, Cloud Financial Management, showback and chargeback) is covered in depth in Cost Optimization, and I won't repeat it here. This article is about the other half, the one that lives inside the architecture: designing the solution so that its cost is visible per value stream and per technical unit. Not "how much does the cloud cost us", but "how much does one checkout cost", "how much does tenant ACME cost", "how much does one AI conversation cost".

When a team ignores this principle, the symptoms are pretty consistent:

  • The bill is only readable by service type ("compute", "storage", "networking"), never by product, feature or customer;
  • Shared infrastructure (clusters, databases, message brokers) is a black hole where cost goes in and no owner comes out;
  • Nobody can tell whether a customer or a pricing plan is profitable, because nobody knows what it costs to serve;
  • Cost regressions are discovered by Finance, weeks after the deploy that caused them;
  • Every architecture discussion about cost is based on opinion, because there's no number to argue about;
  • Tags exist, but half the resources don't have them, and the other half have three different spellings of environment;

Yep, it's rare, but it happens all the time... Who hasn't opened a billing report and seen a line called "Other" that was bigger than the product itself?

Confused junior dev
Naive Junior
"But isn't cost a Finance problem? I write code, they pay the bill. Why would I care what a request costs?"

Easy there, Junior! In the cloud, every line of code you write has a price attached to it. The query without an index, the retry loop without backoff, the log statement inside a hot path, the prompt that sends the whole conversation history to the model on every turn: all of these show up on the invoice. Finance can see that the money is gone, but only engineering can see why. If the architecture doesn't expose cost at the level where engineers make decisions, nobody can connect the two, and the bill becomes a rumor instead of a signal.

Cost Transparency is not about spending less. It's about making the cost of the solution observable, attributed to the things the business and the engineers actually care about, so that every other cost decision (including optimization) is made with data instead of guesses.

From the invoice to the architecture

A raw cloud invoice is a list of line items: this many vCPU hours, this many GB stored, this many GB transferred between zones. It is precise and almost useless, because it describes resources, while your business thinks in products, customers and transactions.

Cost transparency is the chain that turns one into the other. I like to think of it in layers, each one adding context the previous one doesn't have:

From the cloud invoice to decisions Five layers from left to right: the raw cloud bill, allocation through tags and accounts, splitting shared costs, unit cost from application telemetry, and decisions in ADRs and pull requests, with a feedback loop back to the start. LAYER 1 LAYER 2 LAYER 3 LAYER 4 LAYER 5 Cloud bill raw line items no context Allocation tags, accounts baked into IaC Shared split clusters, DBs fair drivers Unit cost per tenant, order app telemetry Decisions ADR, PR, backlog product owners feedback: design changes that move the unit cost
Figure 1: The layers that turn a raw invoice into architecture decisions
  • Layer 1, the bill: what the provider charges. You don't control its format.
  • Layer 2, allocation: who owns each resource. This comes from tags, labels, accounts, subscriptions and naming, and it has to be designed, not retrofitted.
  • Layer 3, shared split: the resources that serve many owners at once (a Kubernetes cluster, a shared database, a NAT gateway) need a rule for dividing their cost.
  • Layer 4, unit cost: divide the attributed cost by a business driver (requests, orders, tenants, conversations) that only the application knows.
  • Layer 5, decisions: the numbers show up where decisions are made: architecture decision records, pull requests, product reviews.

Notice that layers 2, 3 and 4 are architecture work. No billing tool will invent a tag you never applied, split a cluster you never labeled, or count orders your application never emitted. That's why this is a solution principle, and not only a FinOps one.

Unit economics: the number that actually matters

The single most useful idea in cost transparency is the unit cost: total cost of a scope, divided by the business unit that scope delivers.

unit cost = attributed cost of the scope ÷ units of value delivered

The trick is choosing the right unit. It should be something the business understands, that grows when the business grows, and that the architecture can measure reliably. Some examples:

Solution typeUseful unitWhy it works
Public APICost per 1,000 requestsDirectly tied to traffic, easy to compare across versions and regions.
E-commerceCost per orderConnects infrastructure to revenue; you can compare it with the average order margin.
B2B SaaSCost per tenant (per month)Tells you which customers and plans are profitable, and feeds pricing decisions.
Consumer appCost per monthly active userUseful for growth planning and for comparing against revenue per user.
Data platformCost per pipeline run or per GB processedExposes expensive transformations and wasteful full reloads.
LLM productCost per conversation, per 1,000 tokens, per resolved ticketModel usage is often the largest variable cost, and it varies wildly per user.
Confused junior dev
Naive Junior
"Why all this math? If the monthly total goes down, we're doing well. If it goes up, we're doing badly. Simple!"

Not so fast, Junior! The monthly total is the most misleading number in the whole report. If your business is growing, the bill should grow. What matters is whether it grows slower than the value it produces. Look at this:

Total bill versus cost per order Over six months the total cloud bill, shown as bars, grows by about 133 percent, while the cost per order, shown as a line, falls by about 38 percent because orders grew faster than cost. SAME SIX MONTHS, TWO STORIES M1 M2 M3 M4 M5 M6 Total bill: +133% Cost per order: -38%
Figure 2: The bill more than doubled, and the solution got cheaper to run

If you only watched the bars, you'd call an emergency meeting. If you watch the line, you see a solution that became almost 40% more efficient per order while the business took off. Now flip it around: a flat bill with a rising cost per order means you're losing efficiency while nobody notices, because the total "looks fine".

A few rules of thumb for unit metrics:

  • Pick one or two primary units per value stream, not twenty. A dashboard with fifty ratios is as opaque as the raw invoice.
  • Separate fixed from variable cost. A tenant's unit cost in a pooled system has a fixed share (the platform) and a variable share (what their usage consumes). They behave very differently as you scale.
  • Track the trend, not the absolute value. The first number you compute will be imprecise. That's fine. What matters is that the method is stable, so the trend is real.
  • Pair it with a revenue number when possible. Cost per order means much more next to margin per order.

Design practices for cost transparency

The FinOps practices in Cost Optimization tell the organization what to measure. The practices below are about building a solution that can be measured.

1. Design the allocation model before the first resource

Goal: Every resource is born knowing who owns it, which product it belongs to and which environment it runs in.

Tagging after the fact is archaeology. By the time someone decides to "clean up the tags", there are thousands of resources, nobody remembers who created half of them, and the audit takes a quarter. The cheap moment to decide the allocation model is before the first terraform apply.

Define a small, mandatory tag taxonomy and bake it into your IaC modules, so developers can't forget it even if they try:

TagExamplePurpose
productcheckoutThe value stream that the resource serves.
componentpayment-apiThe technical unit inside the product.
ownerteam-paymentsWho answers for the cost (and gets paged for anomalies).
environmentprodSeparates production from everything else.
cost-centercc-4410The Finance mapping, when the organization needs it.
tenantshared or acmeOnly for resources dedicated to a customer.

In Terraform, for example, provider-level default tags guarantee that every resource the module creates inherits the taxonomy:

provider "aws" {
  default_tags {
    tags = {
      product     = var.product
      component   = var.component
      owner       = var.owner
      environment = var.environment
    }
  }
}

Then enforce it with Policy as Code (the governance part is covered in Cost Optimization) so a resource without the mandatory tags simply can't be created. Keep the allowed values in a list, not free text, or you'll end up with prod, Prod, production and prd as four different environments.

Benefit: Allocation becomes a property of the platform instead of a cleanup project, and coverage stays close to 100% as the solution grows.

2. Use account and subscription boundaries per workload

Goal: Let the strongest, cheapest allocation mechanism do most of the work.

Tags are great, but not everything can be tagged: some charges (support plans, certain data transfer, marketplace fees, some managed service overheads) never carry your tags. An AWS account, an Azure subscription or a GCP project, on the other hand, attributes every cent it contains without any effort. A dedicated account per workload and environment (for example, checkout-prod, checkout-staging) gives you an allocation floor that's impossible to forget.

It also brings security and blast radius benefits (that's the Security and Reliability side of the same decision), which is why many landing zone designs already recommend it.

Benefit: A reliable baseline for attribution, where tags only need to refine the picture inside each account instead of carrying the whole weight.

3. Make resource names readable

Goal: Anyone looking at a cost line knows what it is without opening five consoles.

A naming convention like <product>-<component>-<env>-<region>-<nn> (say, checkout-orders-db-prod-weu-01) looks like bureaucracy until the day you're scanning a cost report at 11 p.m. and see db-test-2-final-new costing more than your production database. Names don't replace tags (they're harder to query and change), but they're the human-readable fallback that makes reports and dashboards self-explanatory.

Benefit: Faster investigations, fewer "whose is this?" threads, and orphaned resources that stand out immediately.

4. Split shared costs with explicit rules

Goal: No shared resource turns into a cost black hole.

Shared infrastructure is where transparency usually dies. A Kubernetes cluster running thirty services is billed as a handful of nodes. A PostgreSQL instance serving five products is one line item. If you stop there, the platform team "owns" a huge cost they don't control, and the product teams consume it for free.

The fix is to define a split rule for each shared resource, based on a driver that reflects real consumption:

  • Kubernetes: allocate by namespace and labels, using resource requests (what each workload reserved) or actual usage, whichever is higher. Tools like OpenCost (an open-source CNCF project) and Kubecost read the cluster metrics and the cloud pricing to do exactly this. Make namespaces and labels mirror your tag taxonomy (product, component, owner), so cluster data and cloud data join cleanly.
  • Shared databases: split by a driver the database can report, such as storage per schema, query time per application user or connections per service. Give each consuming service its own database user, and that driver comes almost for free.
  • Network and platform services: gateways, NAT, observability stacks. These are often split proportionally to the direct cost of each consumer, or by request volume when you have it.

And then there's the uncomfortable part: idle capacity. Nodes that nobody requested, headroom for autoscaling, system pods. You can keep idle cost visible as its own line (which puts pressure on the platform team to pack the cluster better) or spread it proportionally across consumers (which reflects the "real" price of running there). Both are valid; the mistake is hiding it.

Splitting the cost of a shared Kubernetes cluster A cluster costing 10,000 dollars a month is first allocated by namespace: checkout 3,800, catalog 2,200, search 1,500, system 1,000 and idle 1,500. The system and idle shares are then spread proportionally, giving checkout 5,067, catalog 2,933 and search 2,000. 1. CLUSTER BILL $10,000 per month: nodes, disks, load balancers 2. BY NAMESPACE (REQUESTS OR USAGE) checkout $3,800 catalog $2,200 search $1,500 system $1,000 idle $1,500 3. SHARED AND IDLE SPREAD PROPORTIONALLY checkout $5,067 catalog $2,933 search $2,000
Figure 3: A shared cluster split by namespace, with system and idle capacity redistributed

Benefit: Every team sees the cost of what it runs, the platform team sees its overhead, and idle capacity becomes a visible target instead of silent waste.

5. Attribute cost per tenant in multi-tenant solutions

Goal: Know what each customer costs to serve, even when they share everything.

In a B2B SaaS, "cost per tenant" is the metric that connects architecture to the business model. And the way you design tenancy decides how hard it is to get:

Tenancy modelHow cost is attributedTransparency
Silo (dedicated stack per tenant)Directly, by account or tenant tag.Excellent, but you pay for idle capacity per tenant and operate many stacks.
Pool (everything shared)Indirectly, by metering each tenant's consumption in the application and splitting the shared cost by it.Only as good as your metering. Without it, you have no idea.
Bridge (shared compute, dedicated data, or premium tenants in silos)A mix: direct for dedicated parts, metered for shared ones.Good, and often the pragmatic middle ground.

The key insight is that in a pooled model the infrastructure cannot tell tenants apart; only the application can. So the application has to record, for every relevant operation, which tenant it served and how much of the expensive resources it consumed: requests, compute time, storage, messages, model tokens. That consumption becomes the driver used to split the shared bill.

This matters beyond accounting. It's very common to discover that 5% of tenants generate 60% of the load, sometimes on the cheapest plan. Without tenant attribution, that customer looks exactly like everyone else. With it, you can price usage tiers, set fair quotas or at least make an informed decision to keep subsidizing them.

Benefit: Pricing, plan limits and sales discounts are grounded in the real cost to serve, and "noisy neighbors" become visible.

6. Emit cost-relevant telemetry from the application

Goal: Treat cost drivers as first-class observability signals.

This is where cost transparency meets Observability First. The application already emits metrics, traces and logs. Add the dimensions and counters that explain cost:

  • Business units: orders placed, documents processed, conversations started. These are the denominators of your unit costs.
  • Cost drivers: payload sizes, bytes written, rows scanned, external API calls, and especially LLM tokens (input, output, cached), tagged by model.
  • Attribution dimensions: tenant, product, feature, plan. Keep cardinality in check: tenant IDs are fine for a SaaS with hundreds of customers, user IDs usually are not.

With OpenTelemetry, for example, recording tokens per tenant and feature is a single counter:

const tokens = meter.createCounter("llm.tokens", { unit: "token" });
 
tokens.add(usage.inputTokens, {
  tenant: ctx.tenantId,
  feature: "support-assistant",
  model: "large",
  direction: "input",
});

LLM products make this urgent. Two users of the same feature can differ in cost by a factor of a hundred, depending on how long their conversations get, how much context is retrieved and which model is called. A monthly invoice from the model provider tells you nothing about that. A token counter per tenant and feature tells you everything: which feature is expensive, which customer is heavy, whether the new prompt template doubled the input size, whether the cache is actually hitting.

Multiply the usage counters by the provider's price list in the dashboard, not in the application. Prices change; your telemetry shouldn't have to be redeployed when they do. Emit quantities, compute money downstream.

Benefit: Cost becomes explainable at the level of a feature, a tenant or a single deploy, with the same tools the team already uses for latency and errors.

7. Put cost in the architecture decision and in the pull request

Goal: Know the price of a change before it ships, not a month after.

Cost that's only visible after the invoice arrives is a history lesson. Transparency means moving it earlier, into the places where the decision is actually made.

In architecture decision records (ADRs): add a "Cost impact" section. Not a precise forecast, just an order of magnitude and, above all, the unit cost effect: "Option A adds about $0.002 per order; option B has a fixed $1,200/month but is cheaper above 600,000 orders per month". That one paragraph changes the quality of the discussion, and it leaves a record for the day someone asks why the bill looks the way it does.

In pull requests: tools like Infracost read Terraform changes and comment the estimated monthly cost difference directly on the PR. The reviewer sees "+$840/month" next to the diff that changed an instance type, before the merge button. It won't catch usage-driven costs (it can't know how many requests you'll get), but it catches the classic "I just bumped the SKU to test something" that lives for a year.

Cost in the delivery loop A cycle of five steps: the ADR estimates the unit cost, the pull request shows the cost difference, the deploy enforces tags, telemetry measures cost per tenant, and the review compares the trend, feeding the next ADR. ADR unit cost estimate Pull request cost diff comment Deploy tags enforced Telemetry cost per tenant Review unit cost trend COST IN THE LOOP every change carries a price tag
Figure 4: Cost visible at every step of delivery, from the decision to the review

Benefit: Cost regressions are caught by the people who cause them, at the moment they're cheapest to fix, and the reasoning behind expensive choices is recorded.

8. Put cost dashboards next to latency dashboards

Goal: Cost is watched with the same frequency and by the same people as performance.

If the service dashboard shows p95 latency, error rate and throughput, it should also show cost per request (or per order, per tenant) for the same service and the same time window. Not in a separate billing tool that only one person has access to. Right there, where the on-call engineer looks every day.

When cost lives beside latency, correlations jump out: the deploy that fixed latency by doubling the cache cluster, the release that cut cost per request by 30% thanks to a better query, the tenant whose traffic pattern changed overnight. Add anomaly alerts on unit cost, not only on total spend. A total-spend alert fires when the business grows; a unit-cost alert fires when something is actually wrong.

Benefit: Cost becomes a normal operational signal, discussed in the same stand-ups and incident reviews as everything else.

Back to the bill that doubled

Let's go back to that Monday. Imagine the same team, but with the practices above in place. The conversation would look more like this:

  1. The unit cost dashboard shows that cost per order was stable for every product except one: the support assistant, whose cost per conversation tripled two weeks ago.
  2. Filtering the token counter by feature and tenant, the team sees that input tokens per conversation jumped right after a deploy.
  3. The PR history for that deploy shows a change to the retrieval step: it started sending twenty documents to the model instead of five, "to improve answer quality".
  4. The ADR for the assistant had estimated about $0.04 per conversation. It's now $0.13. Nobody noticed, because nobody was looking at that number.

Diagnosis in one hour instead of three days, with a clear owner and a concrete decision to make: is the quality gain worth three times the cost per conversation? Maybe it is! That's a product decision, and now it can be made with data. Transparency didn't reduce the bill by itself. It turned a mystery into a choice.

Confused junior dev
Naive Junior
"Okay, I'm convinced. So let's tag every single resource, meter every function call per user and build a dashboard for each microservice, starting today!"

Whoa, hold your horses, Junior! That enthusiasm is great, but transparency has a cost of its own, and chasing perfect precision is one of the fastest ways to burn a quarter and give up. Start with the few units that matter to the business and get them roughly right. Which brings us to the tradeoffs.

Tradeoffs

Like every principle, Cost Transparency pulls against others. Knowing where lets you decide how far to go.

Tradeoffs with Performance Efficiency

Instrumentation overhead: every counter, label and span has a runtime cost. Metering each operation per tenant in a hot path adds CPU, memory and network work, and high-cardinality dimensions (per user, per request ID) can make your metrics backend both slow and expensive.

Mitigation: aggregate in-process and export periodically, sample where exactness isn't needed, and keep attribution dimensions coarse (tenant, feature, plan) instead of per user.

Tradeoffs with Cost Optimization

Transparency isn't free: metrics storage, cost allocation tools, the engineering time to build and maintain dashboards and split rules. For a small solution with a modest bill, a full per-tenant attribution pipeline can easily cost more than the savings it will ever reveal.

Precision vs. effort: getting from "roughly right" to "accurate to the cent" follows a steep curve. An 80% accurate unit cost built in a week is usually far more valuable than a 99% accurate one that takes six months. Be explicit about the precision you need: showing a trend to engineers needs far less than invoicing a customer for usage.

Tradeoffs with Operational Excellence

Account sprawl: one account per workload and environment is fantastic for allocation, but at scale it means hundreds of accounts to secure, patch, network and govern. Without solid automation (landing zones, account vending, centralized policies), the operational burden grows faster than the transparency benefit.

Taxonomy maintenance: tag schemas and split rules drift as the organization reorganizes. Teams get renamed, products merge, cost centers change. Someone has to own the taxonomy, or it slowly rots into the same chaos it was meant to fix.

Tradeoffs with Security

Telemetry carries sensitive context: tenant identifiers, feature usage and consumption patterns are business-sensitive, and in some cases personal data. Cost telemetry needs the same classification and access control as any other observability data.

Cost data itself is sensitive: per-customer cost and margin is exactly the kind of data you don't want widely shared. Transparency inside the engineering team doesn't mean publishing every number to everyone.

Tradeoffs with Reliability

Isolation vs. sharing: dedicated stacks per tenant give perfect attribution and a smaller blast radius, but multiply idle capacity and the number of moving parts. Pooled designs are cheaper and simpler to operate but need careful metering. The tenancy model is a decision that balances cost, Reliability and transparency at the same time, not transparency alone.

Confused junior dev
Naive Junior
"So even measuring cost has a cost? How do I know when to stop?"

Exactly, Junior! The rule I use: stop when the next level of detail no longer changes any decision. If knowing the cost per tenant changes pricing, measure it. If knowing the cost per API endpoint changes nothing anyone will do, don't. Transparency is a means to better decisions, not a trophy.

Conclusion

Cost Transparency is what turns cloud cost from a monthly surprise into an engineering signal. At the solution level, it comes down to a few design choices: an allocation model baked into IaC from day one, account boundaries that attribute cost for free, explicit rules for splitting shared infrastructure, tenant attribution in multi-tenant designs, and application telemetry that counts the units of value and the drivers of cost.

On top of that foundation, unit economics replaces the misleading monthly total with numbers that actually mean something: cost per request, per order, per tenant, per conversation. And when those numbers show up in ADRs, in pull requests and right next to the latency dashboard, cost stops being Finance's problem discovered weeks later and becomes part of how the team builds software every day.

The organizational culture (FinOps, showback, chargeback) described in Cost Optimization depends on all of this. Culture can ask the right questions, but only architecture can make the answers visible. And the next time the bill doubles, the answer to "Can someone explain this?" should take an hour, not a sprint.

Next Steps

  1. Pick your units For each value stream, choose one or two unit metrics the business understands (cost per order, per tenant, per conversation) and agree on how they're calculated.

  2. Define and enforce a tag taxonomy Keep it short and mandatory, bake it into IaC modules as default tags, restrict values to allowed lists and block untagged resources with Policy as Code.

  3. Draw account boundaries Separate workloads and environments into their own accounts, subscriptions or projects wherever the operational cost is acceptable.

  4. Write split rules for shared resources Label Kubernetes namespaces with the same taxonomy, adopt OpenCost or a similar tool, decide how idle and system capacity are treated, and document the driver for each shared database or platform service.

  5. Instrument the application Emit business units and cost drivers (including LLM tokens) as metrics with tenant and feature dimensions, and compute money downstream from quantities.

  6. Bring cost into decisions Add a cost impact section to ADRs, run a cost estimate on infrastructure pull requests, and put unit cost panels and anomaly alerts next to latency and error rate.

Comments

Questions, corrections, or your own take are all welcome. Sign in with GitHub to join in.