Cost Engineering

Cost per Service and the Attribution Problem

Breaking the bill down by service turns "infrastructure costs too much" into a conversation someone can act on. The mechanism is tagging, the hard part is the shared resources that resist attribution, and the honest output is an allocation rule everybody agreed to rather than a precise truth.

The question this answers

Infrastructure question

Which service is responsible for which part of the bill, and who can act on it?

Application requirement

Six teams deploy into shared infrastructure. The bill grew 40% last quarter and the only artifact anyone can point at is a total. Each team believes their part is small. Until spend can be attributed to the service that caused it, no engineer can act and the conversation stays with finance.

What it provides

A per-service breakdown with an agreed treatment of shared costs, so each team sees a number they recognize, can influence, and are accountable for.

Application RequirementInfrastructure RequirementComputeNetworkStorageIdentityDeploymentScalingReliabilityObservabilitySecurityCostTrade-offs

A breakdown someone can argue with

The point of per-service cost is behavioural, not accounting. A total tells an engineer nothing they can act on. "The search service is 31% of the bill, and 60% of that is the vector index instance running at 8% utilization" tells them exactly what to do on Monday. The precision required is much lower than people assume — you need the ranking and the rough proportions to be right, not the last percent.

The panel below is a conceptual breakdown of a platform, split the way an engineering organization would want to read it. Every value is simulated to demonstrate the shape. Two features of it are worth naming. First, the observability line is charged to the platform rather than distributed, because no team can influence it individually and charging it out produces arguments instead of changes. Second, there is an explicit unattributed bucket, and it is not small. Pretending it is zero is the most common way these reports lose credibility.

Notice also what the breakdown reveals that a total conceals: the cache costs more than the API it accelerates. That is not automatically wrong — it may be preventing a much larger database — but it is a question worth asking, and it is a question that only exists once the bill is split.

SIMULATED per-service breakdown for a mid-sized platform. Relative weights, invented values, illustrative proportions only.SIMULATED
API service fixed
driven by instance-hours + load balancer GB processed · The largest line and the one most teams assume is everything. It is usually a third to a half.
Database fixed
driven by instance-hours + storage + IOPS + standby replica · The standby doubles the instance portion and serves no traffic — the availability requirement, priced.
Cache · surprisefixed
driven by node-hours, always on · Costs more than the API it fronts here. Justified if it removes database load; pure overhead if the queries were already cheap.
Bandwidth · surpriseusage
driven by GB out to the internet, cross-zone and cross-region · Attributable per service only where the traffic can be tagged, which is rarely true for shared egress paths.
Observability spiky
driven by log GB ingested + metric series retained · Charged to the platform, not distributed. No single team can influence the shared pipeline.
Async workers spiky
driven by instance-hours × queue depth-driven scaling · Spiky by nature; a monthly average hides the pattern that drives the cost.
Unattributed shared plumbing · surprisefixed
driven by NAT, cluster control plane, shared load balancers, VPN · Real, unavoidable, and dishonest to report as zero. Allocate by an agreed rule and say which rule.

Bars are relative weights, not currency. Real rates depend on provider, region, commitment and volume.

Tagging is the mechanism, and it only works at creation time

Attribution is mechanically simple: every resource carries tags, the provider's cost dataset groups by tag, and the report writes itself. The difficulty is entirely organizational. Tags applied by hand are applied inconsistently and then not at all; retroactive tagging campaigns never complete because the untagged resources are the ones nobody remembers creating. The only approach that holds is enforcement at creation, in the infrastructure-as-code module, so an untagged resource is impossible rather than discouraged — see Modules: Reuse Without Hiding.

Keep the tag set small and mandatory. Four keys cover nearly everything: environment, service, team and cost-centre. Every additional key halves the chance the whole set is applied. And decide the vocabulary centrally, because service: checkout, service: checkout-api and Service: Checkout are three services as far as the cost dataset is concerned.

Some resources cannot be tagged meaningfully no matter how disciplined you are, and this is where honest reporting is won or lost. A NAT gateway serves every workload in its zone. A cluster control plane serves every namespace. A shared load balancer fronts several services. Cross-zone traffic is billed at the account level with no service attribution available at all. For each of these, pick an allocation rule, publish it, and re-derive it when the shape changes. An agreed approximation everyone understands is worth far more than a precise number nobody believes.

CostAttribution mechanismConfidenceWhen it cannot be attributed
Instances and containersTag at creation; enforce in the IaC moduleHighUntagged legacy resources — sweep weekly and assign an owner
Managed databaseOne database per service, or tag the instanceHighShared database: allocate by schema size or measured query share
Object storageBucket-level tags, or prefix-level usage reportsMediumA shared bucket needs prefix reporting, which is not always available
Load balancerDedicated balancer per serviceMediumA shared balancer: allocate by request count or GB processed per target group
Cross-zone and cross-region transferRarely attributable — billed at account levelLowAllocate by measured traffic share from your own telemetry, not from the bill
NAT gatewayNot attributable by the providerLowAllocate by outbound bytes per workload from flow logs, or split evenly and say so
Cluster control planeNot attributableNoneCharge to the platform; distributing it produces arguments, not savings
Observability pipelineLog volume per source is measurableMediumCharge to the platform but publish per-service volume, which is what changes behaviour
What can be attributed, how, and what to do when it cannot be

The shared components that resist attribution

Draw the topology and the unattributable costs are exactly the boxes with many arrows pointing at them. That is not a coincidence — a resource is shared precisely because several services depend on it, and shared is what makes attribution hard. The topology below flags the four usual suspects, and the useful observation is that they are also the four components teams most often forget exist at all when estimating their own cost.

There is a structural alternative to allocation rules, and for organizations past a certain size it is the better answer: give each team its own account or project. Attribution then becomes a boundary rather than a report, since the bill arrives already separated. It costs duplicated shared components — each account needs its own NAT, its own load balancers, possibly its own cluster — which is a genuine increase in total spend traded for perfect attribution and much better blast-radius containment. Whether that trade is worth it depends on how much the arguments are costing you.

Whichever route you take, the deliverable that changes behaviour is not the absolute number. It is cost per unit of business value — per thousand requests, per active tenant, per job processed. That is the only framing that distinguishes "we cost more because we grew" from "we cost more because we got less efficient", and it is the number worth putting on a dashboard that engineers see weekly.

The four components every attribution model argues aboutILLUSTRATIVE
Shared load balancerpublic
Checkout serviceprivate— Tagged at creation: service, team, environment, cost-centre. Attribution is clean.
Search serviceprivate— Cleanly tagged; the vector index instance under it dominates its line.
Notification workersprivate— Spiky; a monthly average misrepresents it, so report peak and total separately.
Cluster control planeinternal
NAT gatewaypublic
Shared databaseprivate
Observability pipelineinternal
Shared load balancerCheckout service· attributable by target group
Shared load balancerSearch service· attributable by target group
Checkout serviceShared database· shared — allocate by query share
Search serviceShared database· shared — allocate by query share
Notification workersNAT gateway· outbound GB, attributable only via flow logs
Checkout serviceObservability pipeline· log volume measurable per source
Cluster control planeCheckout service· shared control plane, unattributable

Key points

  • Per-service cost exists to change engineering behaviour, so the ranking and rough proportions matter far more than precision.
  • Tagging only works when enforced at resource creation in the IaC module; retroactive tagging campaigns never finish.
  • Keep the mandatory tag set to about four keys with a centrally-defined vocabulary, or the values fragment and the report becomes noise.
  • Shared components — NAT, control plane, shared balancers, cross-zone transfer — cannot be attributed by the provider. Publish the allocation rule instead of hiding the gap.
  • Report an explicit unattributed bucket. Reporting it as zero is how the whole exercise loses credibility.
  • Account-per-team turns attribution into a boundary rather than a report, at the cost of duplicating shared infrastructure.
  • Cost per thousand requests or per active tenant is the number that separates growth from decay in efficiency.

The loop, answered

Every field is required, which is why no lesson here can recommend something without saying what it costs and what simpler thing to consider first.

How it works
  • Tags are key-value pairs attached to resources at creation and propagated into the provider's detailed cost and usage dataset.
  • The cost dataset can be grouped by tag, service, region and account, typically with a one-to-two-day delay.
  • Untaggable shared costs appear in the dataset at account scope with no service dimension, which is where allocation rules take over.
  • Allocation rules apply a proportional split derived from a measurable proxy: request counts, storage share, outbound bytes from flow logs, log volume per source.
  • Unit economics divide the allocated cost by a business metric the team already tracks, producing a number that is comparable over time.
What you still own
  • Define the tag schema once, enforce it in shared IaC modules, and reject resource creation without it — a policy that blocks is worth ten that report.
  • Publish the allocation rules for shared costs alongside the report. A rule people can argue with is a rule people accept.
  • Sweep untagged resources weekly and assign an owner; the untagged set grows silently and is disproportionately made of forgotten things.
  • Report monthly to the teams, not only to finance. The report that changes spend is the one an engineer reads.
  • Track cost per unit of business value, not only absolute cost, so growth and inefficiency are distinguishable.
  • Re-derive allocation rules when the architecture changes; a split agreed two years ago describes an older system.
How it fails
  • Tag drift: three spellings of the same service name, so the report shows three services and none of them look large.
  • A large unattributed bucket that grows over time, quietly making every per-service number an underestimate.
  • Attribution used as blame, after which teams optimize for a smaller reported number rather than a smaller total — moving costs into shared buckets is the usual method.
  • Allocation rules that were reasonable once and now describe a system that no longer exists.
  • Reports produced for finance and never seen by engineers, so the data exists and nothing changes.
  • Precision theatre: months spent building exact allocation when the ranking was already clear and the top item was obvious.
How it scales
  • Attribution difficulty grows with sharing, not with size: a large estate of independent services is easier to attribute than a small, tightly shared one.
  • The unattributed fraction grows with the number of shared components, which is one of the quieter costs of a service mesh or a shared cluster.
  • Account-per-team scales attribution perfectly and multiplies the fixed shared components each account needs.
  • The dimension that runs out first is agreement: past a handful of teams, the argument about the allocation rule costs more than the allocation.
Security
  • Cost reports describe architecture — service names, sizes, regions, dependencies. Treat them as internal production information.
  • Tags are useful for security too: the same environment and service labels drive policy conditions and incident scoping.
  • A service whose cost jumps without a traffic change is a security signal as often as a cost one — compromised compute and exfiltration both show up here first.
Cost shape
  • The attribution machinery itself costs little; detailed cost datasets are usually free, and third-party platforms are priced as a share of the spend they analyse.
  • The real expense is engineering discipline: tagging enforcement, rule maintenance and the meetings where allocation is argued.
  • Account separation for perfect attribution duplicates NAT gateways, load balancers and control planes, which is a genuine increase in total spend.
  • Every value in this lesson is simulated. Real proportions vary enormously by workload shape.
What to watch
  • Percentage of spend that is attributable, tracked over time as the health metric of the tagging discipline.
  • Cost per service per month with the unattributed bucket shown explicitly beside it.
  • Cost per thousand requests or per active tenant, which is the only view that separates growth from inefficiency.
  • Month-over-month change per service, which surfaces a step change faster than any absolute number.
  • The signal that lies: a service whose reported cost fell because its dependencies moved into a shared, unattributed component.
Simpler alternatives
  • For one team and one product, skip attribution entirely. The total is the number, and splitting it is pure overhead.
  • Account or project per team gives exact attribution with no tagging discipline at all, paid for in duplicated shared infrastructure.
  • Report only the top five line items rather than a full allocation. It takes an hour, it is accurate, and it drives most of the same decisions.
  • Showback before chargeback: showing teams their cost changes behaviour almost as much as billing them, without the internal accounting apparatus.
  • If the bill is small relative to salaries, the honest answer is to stop here and spend the time on the product.
What adopting this costs
  • Buys accountability and engineer-visible cost signals; costs tagging discipline, allocation arguments and a report somebody maintains.
  • Precise allocation of shared costs consumes effort disproportionate to the decisions it changes — approximate and publish the rule.
  • Chargeback creates strong incentives and rewards gaming, especially the strategy of pushing costs into shared buckets.
  • Account separation is the cleanest attribution and the most expensive one, because every account needs its own copy of the shared plumbing.

What people believe, and what is true

Claim

Tagging solves attribution.

Reality

It solves attribution for taggable resources. NAT gateways, control planes, shared balancers and cross-zone transfer need agreed allocation rules, and that bucket is not small.

Claim

We need exact numbers before we can act.

Reality

The ranking and rough proportions are what change decisions. Chasing the last few percent costs more than it ever saves.

Claim

Chargeback makes teams cost-conscious.

Reality

It makes teams conscious of their reported number. Without shared-cost rules everyone accepts, the reliable optimization is moving costs into shared buckets.

Claim

Total spend is the metric.

Reality

Total spend rises with growth. Cost per request or per tenant is what tells you whether efficiency improved or decayed underneath the growth.

Apply it