The Latency Budget That Cut Our Cloud Bill Without Slowing Users Down
A practical resource optimization framework that ties performance work to user-facing metrics, SLOs, and dollars—so you can stop guessing and start shipping measurable wins.
Performance work that isn’t tied to SLOs and cost-per-request is just expensive self-care for engineers.Back to all posts
The trap: “We need faster and cheaper” (so everyone does nothing)
I’ve watched teams burn months arguing performance versus cost, while customers quietly bounce on slow pages and the AWS bill keeps creeping. The root problem is rarely the tech—it’s the lack of a shared optimization framework.
When you don’t have a framework, every conversation devolves into:
- “CPU is at 80%” (so we scale up)
- “Infra spend is up 30%” (so we scale down)
- “Latency feels worse” (so we panic)
What actually works is tying resource decisions to user-facing metrics and business outcomes. Not vanity metrics. Not “pods per node.” The stuff investors and customers feel: conversion, retention, reliability, and runway.
The rest of this article is the framework I’ve seen survive real production load, incident reviews, and budget scrutiny.
Pick the metrics that customers feel (and finance can price)
Start with three layers of truth, in this order:
- User experience metrics: what users perceive
- APIs:
p95/p99latency, timeouts, error rate - Web: Core Web Vitals (
LCP,INP,CLS) - Mobile: app start time, API error rate, crash-free sessions
- APIs:
- Reliability targets: your SLO (Service Level Objective) and error budget
- Plain English: an SLO is the performance/reliability level you commit to (internally or externally). Error budget is how much “badness” you can tolerate before you stop shipping features and fix reliability.
- Unit economics: what performance costs you
- cost-per-request (or cost per session / active user)
- infra spend per environment (prod vs staging surprises are common)
Concrete example targets that don’t lie:
- API SLO: 99% of requests < 300ms and 5xx < 0.1%
- Frontend: LCP < 2.5s, INP < 200ms on p75 mobile
- Business: +0.5% conversion is worth more than shaving 20ms off an internal endpoint
If you can’t map a metric to “users churn less” or “we can serve more traffic with the same spend,” it’s a dashboard hobby.
Build a resource optimization framework (the part that stops bike-shedding)
Here’s the decision loop I use with teams—simple enough to repeat every sprint, strict enough to prevent random optimizations.
- Define SLOs for the critical path (signup, checkout, search, core API).
- Measure latency distribution (
p50/p95/p99), errors, and saturation (CPU, memory, queue depth). - Quantify cost-per-request by service.
- Prioritize by “impact per engineer-week”:
- user-facing latency win
- error reduction / MTTR improvement
- cost reduction
- Change one thing, ship behind a flag/canary, and verify against SLO + cost.
A practical way to enforce discipline is to treat performance like reliability: with budgets.
- If SLO burn is high, you fix performance/reliability before building net-new features.
- If SLO is healthy, you optimize cost (or ship features) without fear.
Here’s a minimal Prometheus-style SLO alert using burn rate (the kind that prevents “we didn’t notice p95 doubled”):
# prometheus-rule.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: api-slo-burn
spec:
groups:
- name: api-slo
rules:
- alert: ApiLatencySLOBurnFast
expr: |
(
sum(rate(http_request_duration_seconds_bucket{le="0.3",route=~"/api/.*"}[5m]))
/
sum(rate(http_request_duration_seconds_count{route=~"/api/.*"}[5m]))
) < 0.99
for: 10m
labels:
severity: page
annotations:
summary: "API latency SLO burn (fast)"
description: "<99% of API requests are under 300ms for 10m."Is it perfect? No. Is it a lot better than “CPU is high”? Absolutely.
The boring math: cost-per-request and “where the bill actually comes from”
If you want performance and cost aligned, you need a unit metric that makes tradeoffs explicit.
A quick-and-dirty weekly calculation:
- cost-per-1k-requests = (weekly compute + DB + cache + egress) / (weekly requests / 1000)
Then split by service using tags/labels (k8s_namespace, service, pod) via your cloud cost tooling. You don’t need perfect chargeback; you need directional truth.
What I’ve seen in the wild:
- Egress and payload size quietly dwarf compute (especially with media, feeds, analytics-heavy pages).
- Databases are the real bill (RDS/Aurora + IOPS + replicas) long before Kubernetes is.
- Overprovisioning “just in case” becomes permanent because no one trusts rollbacks.
A founder-friendly heuristic:
- If you can cut p95 latency by 30–50% on a conversion path, you often get measurable revenue lift.
- If you can cut cost-per-request by 20–40% while holding SLO, you extend runway without slowing product.
This is where optimization stops being “engineering indulgence” and becomes runway insurance.
Optimization techniques that move both latency and spend (with measurable outcomes)
Below are the plays that consistently pay off. I’m skipping the “use caching” platitudes and focusing on what actually shows up in traces and bills.
Right-size and autoscale on the right signals
CPU-based autoscaling is fine for CPU-bound workloads. Many web stacks are I/O bound (DB, network, downstream APIs), so CPU sits at 30% while latency explodes.
Scale on:
RPSper pod- in-flight requests
- queue depth / lag (Kafka, SQS)
- request latency saturation (careful—feedback loops)
Example HPA scaling on requests per second (via custom metrics):
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 4
maxReplicas: 30
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "50"
behavior:
scaleDown:
stabilizationWindowSeconds: 300Typical outcome when teams stop scaling on CPU alone: fewer replicas during idle (cost down), fewer brownouts during spikes (p95 down).
Fix the top trace spans (usually DB + serialization + N+1)
If you’re not looking at distributed traces, you’re optimizing blind. Use OpenTelemetry and trace the critical path.
DB example: validate with EXPLAIN ANALYZE before adding more replicas.
EXPLAIN (ANALYZE, BUFFERS)
SELECT *
FROM orders
WHERE customer_id = $1
ORDER BY created_at DESC
LIMIT 50;Common win:
- Add the missing composite index:
(customer_id, created_at DESC) - Reduce payload fields (stop
SELECT *)
Real-world results I’ve personally seen: p95 endpoint latency from ~900ms to ~220ms, DB CPU down 30%+, and suddenly you don’t “need” that bigger RDS class.
Cache intentionally (and invalidate like you mean it)
Caching is dangerous when it’s vague. Make it explicit:
- cache key strategy
- TTLs
- invalidation triggers
- fallback behavior
Example nginx microcache for GET endpoints (great for bursty traffic):
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:50m inactive=60m max_size=2g;
server {
location /api/public/ {
proxy_cache api_cache;
proxy_cache_valid 200 30s;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Cache $upstream_cache_status;
proxy_pass http://api_upstream;
}
}Typical outcome: 30–70% fewer origin requests during bursts, flatter p95, and smaller autoscaling peaks.
Reduce payload size and round trips (the sneaky cost killer)
You want performance and cost wins together? Shrink what you ship:
- enable
gzip/brotli - kill accidental overfetch in GraphQL/REST
- paginate correctly (and cap
limit) - move heavy assets behind a CDN with sane cache headers
It’s not glamorous, but cutting 200KB per request across millions of requests is real money—plus better LCP.
Load test like you’re about to be featured on Product Hunt
Don’t argue in Slack. Reproduce it.
# quick baseline
wrk -t4 -c100 -d60s https://example.com/api/search?q=chair
# more realistic scenarios
k6 run --vus 50 --duration 5m loadtest.jsThis is where you catch the stuff that kills you at scale: connection pool starvation, GC churn, thread exhaustion, and “one query per item” loops.
A tight prioritization model: fix vs scale, and where to spend engineer weeks
Here’s what actually works when you’re deciding between throwing hardware at the problem versus fixing code.
Optimize (code/query/cache) when:
- latency is dominated by a few spans (top 3 spans > 50% of trace)
- DB is doing obvious work (
Seq Scan, high buffer reads) - you see
N+1patterns or chatty service calls - cost-per-request is climbing faster than traffic
Scale (infra) when:
- the system is efficient but saturated (clean traces, high utilization)
- you have predictable growth and already fixed the hot spots
- you need time (short-term) while you schedule deeper remediation
Business threshold I use with founders:
- If performance issues are hitting conversion, churn, or enterprise sales diligence, fix now.
- If cost growth is shortening runway by >1–2 months within a quarter, fix now.
This is also where AI-assisted code can be a silent tax. I’ve seen “vibe-coded” endpoints ship with:
- missing indexes
- unbounded queries
- accidental O(n²) loops
- no timeouts/retries
It works in staging. It bleeds money in production.
What GitPlumbers does in the first 7 days (so you stop guessing)
When teams call GitPlumbers, it’s usually after a few “we scaled it… why is it still slow?” cycles. The fastest path is getting facts, not opinions.
- Run Automated Insights (GitHub-integrated) to surface structural risks fast: hot spots, dependency issues, missing timeouts, unsafe retry patterns, and reliability gaps that inflate both latency and cost.
- Book a code audit when the system is at an inflection point (pre-scale, pre-funding, pre-hire). You get a prioritized remediation plan tied to SLOs, user-facing metrics, and cost-per-request—plus the “fix vs rebuild” call if needed.
- If you need hands-on execution, we assemble a fractional team for remediation (senior specialists matched to what the audit finds:
Postgrestuning,Kubernetesscaling,Node.jsprofiling,OpenTelemetry, etc.).
The goal isn’t “make it faster.” It’s: hit the SLO, protect conversion, and reduce cost-per-request—with a plan your team can actually maintain.
If you want the practical next step: run Automated Insights on your repo, then book a code audit to turn findings into an execution plan. That sequence prevents the classic rebuild-or-bust spiral.
Related Resources
Key takeaways
- Tie optimization to **user-facing metrics** (p95 latency, Core Web Vitals, error rate), not “CPU looks high.”
- Use **SLOs + error budgets** to decide when to spend engineering time vs when to ship features.
- Track **cost-per-request** (or cost per active user/session) so cost discussions stop being vibes.
- Right-size safely with **guardrails** (load tests, canaries, rollback plans) and the correct scaling signals.
- The fastest cost wins often come from boring fixes: **indexes, caching, N+1s, connection pools, and payload size**.
Implementation checklist
- Define 2–3 SLOs that map to user experience (API p95 latency, frontend LCP/INP, error rate).
- Instrument end-to-end with `OpenTelemetry` and export to `Prometheus`/`Grafana` (or your stack).
- Compute cost-per-request weekly (or per 1k requests) and break it down by service.
- Set scaling based on saturation signals (queue depth, RPS, in-flight requests), not just CPU.
- Run `k6`/`wrk` load tests against realistic traffic + payloads before and after changes.
- Fix the top 3 hotspots from traces/DB `EXPLAIN ANALYZE` before touching infrastructure.
- Add budgets/alerts: SLO burn rate, cost anomaly, and regression detection in CI/CD.
Questions we hear from teams
- What’s the simplest way to start balancing performance and cost?
- Pick one critical user journey, set a single SLO (e.g., 99% < 300ms), and track cost-per-1k-requests for the services involved. Then optimize the top trace spans before touching infrastructure.
- Is autoscaling enough to solve performance problems?
- Autoscaling helps with saturation, not inefficiency. If traces show DB waits, N+1 queries, or huge payloads, scaling just makes the bill bigger while latency stays bad.
- How do we avoid performance regressions from AI-generated code?
- Require profiling and load testing for critical paths, enforce query limits/timeouts, and run automated analysis in CI. GitPlumbers Automated Insights is designed to flag the common structural footguns early.
- When should we do a code audit versus just optimizing incrementally?
- Do a code audit pre-scale/pre-funding/pre-hire, or anytime cost-per-request is rising faster than traffic, p95 is drifting, or you’re debating a rebuild. A good audit turns opinions into a prioritized execution plan.
Ready to modernize your codebase?
Let GitPlumbers help you transform AI-generated chaos into clean, scalable applications.
