惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 聂微东
S
Secure Thoughts
P
Palo Alto Networks Blog
Google DeepMind News
Google DeepMind News
AI
AI
H
Hacker News: Front Page
Schneier on Security
Schneier on Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
Cloudbric
Cloudbric
TaoSecurity Blog
TaoSecurity Blog
T
Tor Project blog
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
V2EX - 技术
V2EX - 技术
Google DeepMind News
Google DeepMind News
GbyAI
GbyAI
S
Security @ Cisco Blogs
S
Security Affairs
P
Privacy International News Feed
I
Intezer
S
SegmentFault 最新的问题
F
Full Disclosure
H
Heimdal Security Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
S
Securelist
V
Vulnerabilities – Threatpost
C
CERT Recently Published Vulnerability Notes
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
Security Latest
Security Latest
C
Cybersecurity and Infrastructure Security Agency CISA
Attack and Defense Labs
Attack and Defense Labs
Forbes - Security
Forbes - Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
I
InfoQ
WordPress大学
WordPress大学
Vercel News
Vercel News
小众软件
小众软件
大猫的无限游戏
大猫的无限游戏
美团技术团队
IT之家
IT之家
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
aimingoo的专栏
aimingoo的专栏
Y
Y Combinator Blog
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
D
Docker
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Hugging Face - Blog
Hugging Face - Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Your AWS bill is lying to you — it shows services, not features
Arpit Gupta · 2026-05-11 · via DEV Community

Our AWS bill crossed $180K in a single month.

We had Datadog. We had CloudWatch dashboards. We had a PagerDuty integration, three runbooks, and a Grafana board that took an intern two weeks to build. We had observability.

What we didn't have — what I didn't realize we were missing until our CFO sent a Slack message on a Tuesday afternoon — was any idea which product feature was responsible for which dollar.

She didn't ask why our p99 latency was 230ms. She asked:

"Which feature drove the $34K jump in compute costs last month?"

I had no answer.

This is that story. And if you're running a B2B SaaS product with more than a handful of microservices and an AI component or two, I'd bet it's your story too — you just haven't hit the Tuesday Slack message yet.


The false confidence of having "monitoring"

Here's the trap: modern observability tooling is extraordinarily good at answering infrastructure questions. It is almost completely blind to product questions.

When our bill jumped 34% in a month, I went straight to CloudWatch Cost Explorer. I could see:

Service Change
EC2 up $8,200
Lambda invocations up 1.1M calls
RDS up $4,400
OpenAI API (via internal proxy) up $19,600

Clean data. Perfectly granular by service. Completely useless for answering the CFO's question.

Because the business doesn't care that Lambda invocations went up. The business cares whether that jump came from the new AI-assisted onboarding flow we shipped in week 2, or from the document summarization feature we'd been quietly scaling to enterprise accounts.

Those two answers have completely different business implications. One is a bug. One is growth. Our monitoring couldn't tell us which.


What our stack actually looked like

Before I walk through what we did, some context on the architecture — because this problem scales with complexity and I want you to recognize your own system in here.

We had 12 services at the time:

api-gateway          → Kong, sitting in front of everything
auth-service         → Node.js, JWT issuance + refresh
user-service         → Python/FastAPI, profile + preferences
document-service     → Python/FastAPI, upload + parsing (heavy S3 + Textract)
ai-service           → Python, wraps OpenAI + Anthropic calls
search-service       → Go, Elasticsearch-backed
notification-service → Node.js, SES + SNS
billing-service      → Node.js, Stripe integration
analytics-service    → Python, ClickHouse writes
export-service       → Python, async PDF + CSV generation
webhook-service      → Go, outbound delivery + retry
admin-service        → Node.js, internal tooling

Enter fullscreen mode Exit fullscreen mode

Standard enough setup. The problem wasn't that the architecture was unusual — it's that cost was never a first-class concern in how these services communicated.

Every service had structured logs going to CloudWatch. Every Lambda had a function name. Every ECS task had a cluster and a service tag. We could filter by service. We could not filter by what the user was doing when that service ran.

The document-service processed uploads for three different product features:

  • Onboarding — auto-importing documents when a new company signed up
  • Manual upload — a user explicitly uploads a file
  • Integration sync — documents pulled in from Google Drive / Dropbox via OAuth

From AWS's perspective, it's all the same Lambda. From our perspective, these three features had wildly different unit economics.


The investigation

When I started digging, I pulled Cost Explorer data into a spreadsheet and tried to correlate it with our feature release schedule manually.

This was a mistake. Not because the data wasn't there — it was — but because the correlation surface area was too large. In the month in question, we had shipped:

  • A complete overhaul of the onboarding flow (week 1)
  • AI-assisted document tagging (week 2)
  • Bulk export to PDF for enterprise accounts (week 3)
  • A search relevance improvement that doubled our Elasticsearch query volume (week 4)

Trying to attribute cost jumps to feature releases by eyeballing a timeline is how you spend two days producing a narrative that sounds plausible but is mostly wrong.

I needed actual attribution. So I started building it.


Step 1: Get granular cost data out of AWS

Cost Explorer has an API. Most engineers I talk to have never touched it. It's genuinely useful.

import boto3
from datetime import datetime, timedelta

ce = boto3.client('cost-explorer', region_name='us-east-1')

def get_service_costs_by_tag(start_date: str, end_date: str, tag_key: str):
    response = ce.get_cost_and_usage(
        TimePeriod={'Start': start_date, 'End': end_date},
        Granularity='DAILY',
        Filter={
            'Tags': {
                'Key': tag_key,
                'MatchOptions': ['PRESENT']
            }
        },
        GroupBy=[
            {'Type': 'TAG', 'Key': tag_key},
            {'Type': 'DIMENSION', 'Key': 'SERVICE'}
        ],
        Metrics=['UnblendedCost']
    )
    return response['ResultsByTime']

# Pull last 30 days grouped by our 'feature' tag
costs = get_service_costs_by_tag(
    start_date=(datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d'),
    end_date=datetime.now().strftime('%Y-%m-%d'),
    tag_key='feature'
)

Enter fullscreen mode Exit fullscreen mode

The key word in there is tag_key='feature'.

AWS Cost Allocation Tags let you slice your bill by any tag you've applied to your resources. The catch: you have to have applied those tags in the first place.

We had tags. We just hadn't been tagging by feature — only by service, environment, and team. So the API gave me cost by document-service but not by onboarding-flow vs manual-upload vs integration-sync.

This is the core of the visibility problem. Not a tooling limitation. A tagging discipline failure that compounded silently for eighteen months until the bill was large enough for someone to ask the question.


Step 2: Retrofit feature context into the request path

I couldn't retroactively fix historical data. But I could fix the attribution model going forward — and then backfill from logs.

Here's the pattern we landed on. Every inbound request to api-gateway now carries a X-Feature-Context header, set by the frontend client:

// In the React app, every API call goes through this wrapper
const apiFetch = (path, options = {}) => {
  const featureContext = getCurrentFeatureContext(); // e.g., "onboarding.document_import"

  return fetch(`${API_BASE}${path}`, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${getToken()}`,
      'X-Feature-Context': featureContext,
      'X-Session-Id': getSessionId(),
    }
  });
};

Enter fullscreen mode Exit fullscreen mode

Kong (our gateway) propagates this header downstream. Every service receives it. Every service logs it.

Then, in each service's structured log output:

import structlog
import functools

log = structlog.get_logger()

def with_cost_context(func):
    """Decorator that ensures feature context is present in all log lines."""
    @functools.wraps(func)
    async def wrapper(request, *args, **kwargs):
        feature_ctx = request.headers.get('X-Feature-Context', 'unknown')
        session_id  = request.headers.get('X-Session-Id', 'unknown')

        with structlog.contextvars.bound_contextvars(
            feature_context=feature_ctx,
            session_id=session_id,
        ):
            return await func(request, *args, **kwargs)
    return wrapper


@app.post("/process")
@with_cost_context
async def process_document(request: Request, body: DocumentBody):
    log.info("document.processing_started",
             doc_size_bytes=body.size,
             doc_type=body.mime_type)

    result = await run_textract(body)

    log.info("document.processing_complete",
             pages_processed=result.pages,
             textract_units=result.units_consumed,
             duration_ms=result.duration_ms)

    return result

Enter fullscreen mode Exit fullscreen mode

Now every log line for every Textract call carries feature_context. When I query CloudWatch Logs Insights:

fields @timestamp, feature_context, textract_units, duration_ms
| filter ispresent(textract_units)
| stats
    sum(textract_units) as total_textract_units,
    count(*)            as call_count,
    avg(duration_ms)    as avg_duration
  by feature_context
| sort total_textract_units desc

Enter fullscreen mode Exit fullscreen mode

I get this:

feature_context total_textract_units call_count avg_duration
onboarding.document_import 1,847,302 12,483 4,201ms
integration.google_drive_sync 934,110 8,921 3,890ms
manual_upload.user_initiated 221,004 4,102 2,340ms

The onboarding flow was consuming 61% of our Textract spend while generating approximately 12% of our active users. The unit economics were broken — we were spending more per onboarded user than we were in the previous billing period, and we hadn't noticed because the cost was invisible inside the document-service aggregate.


Step 3: Do the same for AI spend

AI API costs are the fastest-growing line item for most SaaS products right now, and they're also the most opaque. OpenAI doesn't know what feature triggered your completion call. You have to tell yourself.

We run all AI calls through an internal ai-service. Here's a simplified version of the instrumentation we added:

import openai
import anthropic
import time
from dataclasses import dataclass

@dataclass
class AICallRecord:
    feature_context:    str
    model:              str
    provider:           str
    input_tokens:       int
    output_tokens:      int
    duration_ms:        int
    estimated_cost_usd: float
    request_id:         str

PRICING = {
    "gpt-4o":                    {"input": 0.0000025,  "output": 0.00001},
    "gpt-4o-mini":               {"input": 0.00000015, "output": 0.0000006},
    "claude-3-5-haiku-20241022": {"input": 0.0000008,  "output": 0.000004},
    "claude-sonnet-4-20250514":  {"input": 0.000003,   "output": 0.000015},
}

def tracked_completion(
    messages: list,
    model: str,
    feature_context: str,
    max_tokens: int = 1000,
    **kwargs
) -> tuple[str, AICallRecord]:

    provider = "anthropic" if "claude" in model else "openai"
    start    = time.time()

    if provider == "openai":
        client    = openai.OpenAI()
        response  = client.chat.completions.create(
            model=model, messages=messages, max_tokens=max_tokens, **kwargs
        )
        content       = response.choices[0].message.content
        input_tokens  = response.usage.prompt_tokens
        output_tokens = response.usage.completion_tokens

    else:
        client    = anthropic.Anthropic()
        response  = client.messages.create(
            model=model, messages=messages, max_tokens=max_tokens, **kwargs
        )
        content       = response.content[0].text
        input_tokens  = response.usage.input_tokens
        output_tokens = response.usage.output_tokens

    duration_ms = int((time.time() - start) * 1000)
    pricing     = PRICING.get(model, {"input": 0, "output": 0})
    cost        = (input_tokens * pricing["input"]) + (output_tokens * pricing["output"])

    record = AICallRecord(
        feature_context=feature_context,
        model=model,
        provider=provider,
        input_tokens=input_tokens,
        output_tokens=output_tokens,
        duration_ms=duration_ms,
        estimated_cost_usd=cost,
        request_id=getattr(response, 'id', 'unknown')
    )

    log.info("ai.completion",
             feature_context    =record.feature_context,
             model              =record.model,
             provider           =record.provider,
             input_tokens       =record.input_tokens,
             output_tokens      =record.output_tokens,
             estimated_cost_usd =round(record.estimated_cost_usd, 6),
             duration_ms        =record.duration_ms)

    return content, record

Enter fullscreen mode Exit fullscreen mode

Every AI call now emits a structured log line with the feature context, model, token counts, and estimated cost. This flows into CloudWatch → Logs Insights → internal cost dashboard, refreshed nightly.

After two weeks running this in production, we ran the numbers:

AI spend attribution  trailing 14 days

feature_context                      | cost_usd | % of total
-------------------------------------|----------|----------
ai_tagging.document_auto_tag         |  $6,841  |   34.9%
search.query_expansion               |  $4,203  |   21.4%
onboarding.welcome_email_personalize |  $3,112  |   15.9%
export.pdf_summary_generation        |  $2,891  |   14.7%
support.ticket_classification        |  $2,570  |   13.1%

Enter fullscreen mode Exit fullscreen mode

The document auto-tagging feature — which we had shipped three weeks prior as a "small AI enhancement" — was consuming 35% of our entire AI budget. It was running GPT-4o on every document on ingest, including documents that the user never actually opened.

We switched it to GPT-4o-mini for the initial pass and only escalated to GPT-4o on user-initiated tagging reviews. Monthly AI cost for that feature dropped from ~$13,500 to ~$2,100. Same user-facing quality on the 95% of documents that get the light model. Better quality on the 5% where it actually matters.

This specific gap — knowing which feature is running which model at what cost — is exactly what I've been building CostReveal to surface automatically, without requiring teams to wire up their own logging pipeline from scratch.


The actual problem was never the cost

Here's what I want you to take away from this — and it's the thing I've said to every CTO I've talked to since we went through this.

The $47K month wasn't a cost problem. It was a visibility problem.

The costs were legitimate. Infrastructure runs money. AI APIs cost money. Textract costs money. None of those bills were wrong in isolation. But without the ability to connect those costs to the product decisions that drove them, we had no mechanism to evaluate whether we were spending intelligently.

We were flying on instruments that told us altitude and airspeed but not fuel consumption per destination.

The fix wasn't to spend less. The fix was to be able to see what we were spending on — at the feature level, not the service level.

Once we had that visibility:

  • We made a targeted optimization that saved ~$11K/month on AI spend alone
  • We identified that our Google Drive sync was 4× more expensive per document than manual uploads, and adjusted our pricing for integration-heavy enterprise accounts accordingly
  • We found two Lambda functions running on m5.xlarge instances that had been over-provisioned since a Black Friday scare eighteen months ago — neither team remembered provisioning them

None of that required a cost-cutting initiative. It required knowing where to look.


What I'd do differently from day one

If I were starting the same architecture fresh:

1. Make feature context a first-class concern in your API contract

Don't leave it optional. Every request that enters your system should carry a feature identifier. Enforce it at the gateway level. Log it everywhere.

2. Tag AWS resources by feature, not just by service

Cost Allocation Tags are free. The discipline to apply them is not — but it compounds. A service tag tells you document-service costs $8K. A feature tag tells you onboarding.document_import costs $5K of that $8K.

resource "aws_lambda_function" "document_processor" {
  # ... other config

  tags = {
    Environment = var.environment
    Team        = "platform"
    Service     = "document-service"
    Feature     = "onboarding.document_import"  # ← this is the one that matters
    CostCenter  = "product-engineering"
  }
}

Enter fullscreen mode Exit fullscreen mode

3. Build a token budget per feature, not per model

For AI features, define expected token ranges based on input size. Alert when a feature consistently runs 40%+ over budget. This is the AI equivalent of a memory leak — silent until it's expensive.

FEATURE_TOKEN_BUDGETS = {
    "ai_tagging.document_auto_tag": {
        "input_per_call":   2000,
        "output_per_call":  400,
        "alert_multiplier": 1.4,           # alert if 40% over budget
        "model_default":    "gpt-4o-mini",
        "model_escalate":   "gpt-4o",
    },
    "search.query_expansion": {
        "input_per_call":   500,
        "output_per_call":  150,
        "alert_multiplier": 1.5,
        "model_default":    "gpt-4o-mini",
        "model_escalate":   None,          # never escalate — cheap by design
    }
}

Enter fullscreen mode Exit fullscreen mode

4. Treat cost spikes like production incidents

We have runbooks for latency spikes and error rate spikes. We now have the same for cost spikes. The runbook isn't "reduce costs" — it's "identify which feature drove the change and assess whether it's expected."

Sometimes the answer is: expected, this is growth.
Sometimes it's: unexpected, this is a bug.
Both are important to know quickly.


Closing thought

Your observability stack is almost certainly excellent at telling you that something is running. It is probably telling you almost nothing about why specific product decisions cost what they cost.

That gap is manageable when your bill is $10K/month. It becomes genuinely dangerous when you're at $100K+ — not because the absolute numbers are large, but because decisions about AI features, scaling strategies, and enterprise pricing all depend on unit economics you cannot calculate without feature-level attribution.

The good news: the technical lift to get there is real but not enormous. A consistent context header, a tagging discipline, and a few structured log fields get you 80% of the way. The remaining 20% — aggregating it into something queryable and surfacing anomalies automatically — is where it gets harder, but you can start with CloudWatch Logs Insights and a spreadsheet and still get signal that changes your decisions.

Start with the one feature you're most uncertain about. Instrument it this week. Run it for two billing cycles. I'd be genuinely surprised if you didn't find something worth knowing.

If you'd rather skip building the pipeline and go straight to the attribution layer, that's what CostReveal is for.