Skip to main content
Cloud Cost Engineering

Showback vs Chargeback

Ravinder··6 min read
Cloud CostFinOpsAWSShowbackChargebackFinOps Culture
Share:
Showback vs Chargeback

A showback report tells a team what they spent. A chargeback system makes them pay for it. The difference sounds administrative. It is not — it determines whether engineers treat cost as their problem or as someone else's. Most organizations implement neither model correctly, then wonder why their FinOps program produces dashboards but not decisions.

The Accountability Spectrum

flowchart LR A[No Visibility] --> B[Showback - Inform] B --> C[Soft Chargeback - Influence] C --> D[Hard Chargeback - Enforce] A1[No one knows what anything costs] --> A B1[Teams see their costs, budgets not affected] --> B C1[OKRs include cost efficiency metrics] --> C D1[Overspend debited from team budget] --> D classDef bad fill:#ffcccc,stroke:#cc0000 classDef ok fill:#fff3cc,stroke:#ccaa00 classDef good fill:#ccffcc,stroke:#009900 classDef best fill:#cce5ff,stroke:#0066cc class A bad class B ok class C good class D best

Most organizations live in zone A or B. Zone C — soft chargeback with OKR integration — is where cost behavior actually changes. Zone D works only when budget ownership is genuinely decentralized to team level.

Showback: The Minimum Viable Model

Showback requires three things: allocation, a report, and a recurring review cadence.

-- Weekly showback report — cost per team, variance vs prior week
WITH current_week AS (
  SELECT
    resource_tags_user_team                        AS team,
    resource_tags_user_product                     AS product,
    resource_tags_user_env                         AS env,
    ROUND(SUM(line_item_unblended_cost), 2)        AS cost_usd
  FROM cur_db.cur_table
  WHERE line_item_usage_start_date >= CURRENT_DATE - INTERVAL '7' DAY
    AND line_item_line_item_type NOT IN ('Credit', 'Tax', 'Refund')
    AND resource_tags_user_team IS NOT NULL
  GROUP BY 1, 2, 3
),
prior_week AS (
  SELECT
    resource_tags_user_team AS team,
    ROUND(SUM(line_item_unblended_cost), 2) AS cost_usd
  FROM cur_db.cur_table
  WHERE line_item_usage_start_date >= CURRENT_DATE - INTERVAL '14' DAY
    AND line_item_usage_start_date <  CURRENT_DATE - INTERVAL '7' DAY
    AND line_item_line_item_type NOT IN ('Credit', 'Tax', 'Refund')
    AND resource_tags_user_team IS NOT NULL
  GROUP BY 1
)
SELECT
  c.team,
  c.product,
  c.env,
  c.cost_usd                                                AS this_week,
  COALESCE(p.cost_usd, 0)                                  AS last_week,
  ROUND(c.cost_usd - COALESCE(p.cost_usd, 0), 2)          AS delta_usd,
  ROUND(
    100.0 * (c.cost_usd - COALESCE(p.cost_usd, 0))
    / NULLIF(p.cost_usd, 0)
  , 1)                                                      AS delta_pct
FROM current_week c
LEFT JOIN prior_week p ON c.team = p.team
ORDER BY this_week DESC;

Automate delivery. A Slack message with this table, sent every Monday morning, does more for cost culture than a quarterly review.

Shared Resource Allocation

Shared costs (platform, networking, observability) cannot be tagged to a single team. Use a split model:

def allocate_shared_costs(
    shared_cost: float,
    team_costs: dict[str, float],
    method: str = "proportional"   # or "equal" or "fixed"
) -> dict[str, float]:
    """
    Distribute shared infrastructure cost across product teams.
    
    proportional: allocate based on each team's direct cloud spend ratio
    equal:        split evenly across all teams
    fixed:        use pre-defined allocation percentages
    """
    if method == "proportional":
        total_direct = sum(team_costs.values())
        return {
            team: round(shared_cost * (cost / total_direct), 2)
            for team, cost in team_costs.items()
        }
 
    elif method == "equal":
        per_team = round(shared_cost / len(team_costs), 2)
        return {team: per_team for team in team_costs}
 
    elif method == "fixed":
        # Define in config — sum must equal 1.0
        fixed_pct = {
            "payments":  0.40,
            "analytics": 0.30,
            "growth":    0.20,
            "platform":  0.10,
        }
        return {
            team: round(shared_cost * pct, 2)
            for team, pct in fixed_pct.items()
        }
 
    raise ValueError(f"Unknown method: {method}")
 
# Example
team_direct = {"payments": 45000, "analytics": 30000, "growth": 15000}
shared = 10000
 
allocations = allocate_shared_costs(shared, team_direct, method="proportional")
for team, amount in allocations.items():
    print(f"  {team:12s}  shared allocation: ${amount:,.2f}")

Document the allocation method and publish it. Hidden allocation formulas destroy trust faster than any overcharge.

The Soft Chargeback Model

Soft chargeback ties cost efficiency to engineering OKRs without moving actual budget dollars. It is the most practical step between showback and full chargeback for teams that do not control their own P&L.

Structure:

flowchart TD A[Monthly Cost Report per Team] --> B{Over budget by > 10%?} B -->|Yes| C[Required post-mortem in next sprint] B -->|No| D[No action required] C --> E{Root cause identified?} E -->|Yes| F[Optimization task added to backlog] E -->|No| G[Escalation to EM + FinOps] F --> H[Cost efficiency KR updated] H --> I[Quarterly OKR review includes cost trend]

Suggested OKR structure for engineering teams:

objective: "Operate efficiently at scale"
key_results:
  - kr: "Keep compute cost per request below $0.0008"
    measurement: "Monthly p95 from CUR / total API requests from CloudWatch"
    target_q1: 0.0008
    target_q2: 0.00075  # improve 6% QoQ
 
  - kr: "Maintain tag coverage above 95%"
    measurement: "Weekly CUR tag coverage query"
    target: 95
 
  - kr: "Zero unreviewed Cost Anomaly Detection alerts"
    measurement: "Weekly anomaly alert backlog count"
    target: 0

Building the Weekly Digest Automation

import boto3
import json
from datetime import datetime, timedelta
 
def get_weekly_cost_by_team(ce_client):
    end   = datetime.utcnow().date()
    start = end - timedelta(days=7)
 
    resp = ce_client.get_cost_and_usage(
        TimePeriod={"Start": str(start), "End": str(end)},
        Granularity="MONTHLY",
        Filter={"Not": {"Dimensions": {
            "Key": "RECORD_TYPE",
            "Values": ["Credit", "Refund", "Tax"]
        }}},
        GroupBy=[{"Type": "TAG", "Key": "team"}],
        Metrics=["UnblendedCost"],
    )
 
    results = []
    for group in resp["ResultsByTime"][0]["Groups"]:
        team = group["Keys"][0].replace("team$", "") or "untagged"
        cost = float(group["Metrics"]["UnblendedCost"]["Amount"])
        results.append({"team": team, "cost": round(cost, 2)})
 
    return sorted(results, key=lambda x: x["cost"], reverse=True)
 
def format_slack_message(costs):
    lines = ["*Weekly Cloud Cost by Team*", "```"]
    for row in costs:
        lines.append(f"{row['team']:20s}  ${row['cost']:>10,.2f}")
    lines.append("```")
    return "\n".join(lines)
 
def send_to_slack(webhook_url: str, message: str):
    import urllib.request
    payload = json.dumps({"text": message}).encode()
    req = urllib.request.Request(webhook_url, data=payload,
                                  headers={"Content-Type": "application/json"})
    urllib.request.urlopen(req)
 
# Lambda handler
def lambda_handler(event, context):
    ce = boto3.client("ce", region_name="us-east-1")
    costs = get_weekly_cost_by_team(ce)
    msg = format_slack_message(costs)
 
    webhook = boto3.client("ssm").get_parameter(
        Name="/finops/slack-webhook", WithDecryption=True
    )["Parameter"]["Value"]
 
    send_to_slack(webhook, msg)
    return {"status": "sent", "teams": len(costs)}

Schedule this Lambda weekly with EventBridge. The Slack message creates a shared ritual — everyone sees the number at the same time.

Key Takeaways

  • Showback changes awareness; soft chargeback changes behavior; hard chargeback changes architecture — choose the model that matches your organization's budget ownership structure.
  • Shared cost allocation must be documented and published; any formula applied silently will be disputed when bills rise, destroying the trust that makes cost culture work.
  • Proportional allocation of shared costs (based on direct spend ratios) is the fairest default — it naturally incentivizes efficient direct spend to reduce the shared overhead allocated to a team.
  • The weekly Slack digest is the highest-ROI FinOps ritual; it creates a shared cadence without requiring anyone to pull a dashboard.
  • Tying cost efficiency to OKRs is the bridge between showback and chargeback — teams optimize because it affects their engineering KRs, not because finance is watching.
  • Cost-per-unit metrics (cost per request, cost per active user) are more actionable than raw dollar amounts; they decouple cost from growth and measure efficiency independently.
Share: