Skip to main content
INS // Insights

AWS Lambda Cost Optimization Strategies That Work

Updated September 2026 · 8 min read
AWS Lambda Cost Optimization Strategies That Work

AWS Lambda cost optimization strategies that actually move a bill are boring: memory (which is also CPU), architecture (x86 vs arm64), timeout, and whether you are paying for provisioned concurrency you do not need. Everything else — renaming functions, arguing about "serverless vs containers" in the abstract — is noise until those four are measured.

AWS bills Lambda primarily on GB-seconds and request count, with extra line items for provisioned concurrency, ephemeral storage, and outbound data. Duration is billed in 1 ms increments. Memory can be set from 128 MB to 10,240 MB, and CPU scales with memory (AWS Lambda pricing, Lambda configuration). That coupling is the whole game: under-memory a CPU-bound function and you pay more because it runs longer.

Rutagon runs this tuning on production SaaS estates — dozens of functions, mixed APIs and workers — as part of a two-week cost diagnostic. We do not promise a generic percentage. We name the functions.

Memory Tuning Is CPU Tuning

The first pass is Compute Optimizer plus a week of Duration, MemoryUtilization (via Lambda Insights or a custom metric), and Cost Explorer by function tag. A 1024 MB function that peaks at 180 MB RAM and is I/O-bound to a database can often drop. A 512 MB function that is CPU-bound on JSON and crypto often wants more memory so it finishes sooner. The bill is the product, not the memory slider.

# Cost-shaped canary: run the same payload at several memory sizes
CANDIDATES = [256, 512, 1024, 1769, 3008]

def estimate_cost(mem_mb, duration_ms, invokes):
    gb_seconds = (mem_mb / 1024) * (duration_ms / 1000) * invokes
    # Use the published GB-second rate for the architecture you run
    return gb_seconds, duration_ms

We pick the memory that minimizes gb_seconds for p95, not the one that looks tidy. 1769 MB is a known breakpoint where a function gets a full vCPU on the current Lambda CPU model; we treat that as a hypothesis to test, not folklore.

Timeouts are a hidden multiplier. A 15-minute timeout on an API handler does not cost money until a dependency hangs and you pay for the wait. We set timeouts to "failure is cheaper than waiting" and put retries on the queue, not on the bill.

AWS Lambda Cost Optimization Strategies: Graviton

arm64 (Graviton) is priced lower per GB-second than x86 on the public price list, and AWS states Graviton-based compute can deliver better price performance — up to 40 percent on applicable instance families, with Lambda following the same architecture split (AWS Graviton). The migration is a runtime and dependency problem, not a checkbox.

We flip architecture in Terraform per function after the test suite runs on arm64. Native extensions, old layers, and container images built for amd64 are the usual blockers. For compliance-bound workloads, treat the flip as a controlled change: see AWS Graviton migration for compliance workloads.

Provisioned Concurrency Is a Product Decision

Provisioned concurrency removes cold starts by keeping execution environments warm. You pay for that warmth whether traffic arrives or not. It is correct for a latency SLO on a customer-facing auth or checkout path. It is waste on a nightly batch.

We look at ProvisionedConcurrencyUtilization. If you are paying for 20 and using 2, that is not an SLO. That is a leftover from a launch. Scheduled scaling (up before a known peak, down after) is the adult version.

resource "aws_lambda_function" "api" {
  function_name = "public-api"
  architectures = ["arm64"]
  memory_size   = 1769
  timeout       = 8
  # reserved_concurrent_executions set only if a noisy neighbor exists
}

resource "aws_lambda_provisioned_concurrency_config" "api" {
  function_name                     = aws_lambda_function.api.function_name
  qualifier                         = aws_lambda_alias.live.name
  provisioned_concurrent_executions = var.api_provisioned # often 0
}

Related: Lambda provisioned concurrency cost, Lambda cold start optimization, RDS Proxy connection pooling when the real cost is the database behind the function. Capability: AWS cloud infrastructure.

What We Do Not Optimize First

We do not start with "rewrite in Rust." We do not start with moving everything to Fargate. We do not start with Savings Plans until the GB-second shape is honest — a Savings Plan on a bloated memory setting just locks in the waste.

We also do not treat Lambda cost as a security article. If a function has an IAM user key in the environment, that is a credential elimination ticket, not a FinOps win.

Layers, Snapshots, and the Silent Multiplier

A function that looks cheap at 128 MB can still dominate the bill if every deploy publishes a new version and old aliases keep a slice of provisioned concurrency. We inventory aliases, published versions, and leftover $LATEST traffic before we tune memory. Snapshots of Lambda versions are not free in operational complexity: a rollback alias pointing at an x86 image after a Graviton flip is correct; five forgotten aliases each holding provisioned concurrency is a leak.

Ephemeral storage (/tmp) above the default 512 MB is another line item teams enable for a one-off unzip and never turn down. We treat extra storage like memory: prove the working set, then cut. Outbound data from Lambda to the public internet — especially through a NAT gateway — often dwarfs GB-seconds. That is not a Lambda pricing problem, but it shows up in the same diagnostic. We follow the packet: if the function talks to S3 or Secrets Manager, use a gateway endpoint; if it talks to a public API, say so in the scorecard instead of blaming the runtime.

Init duration is the last quiet multiplier. A fat container image or a global import that pulls a 200 MB SDK on every cold start burns duration you never see in a warm canary. We measure InitDuration separately from Duration and we split "make the image smaller" from "buy provisioned concurrency." Buying warmth to hide a 1.4 GB image is how FinOps theater starts.

Diagnostic Shape

Two weeks: tag the expensive functions, run memory canaries on the top spenders, propose architecture flips, and list provisioned concurrency that is idle. You get a scorecard, not a migration novel. The scorecard names functions, not "serverless" as a category — CFOs sign function-level changes.

Talk through a two-week AWS cost diagnostic →

907-841-8407 · contact@rutagon.com

Frequently Asked Questions

Should every function move to Graviton?

Every function that builds and passes tests on arm64 should be considered. Functions with unsupported native deps stay on x86 until the dep is replaced. Mixed architectures in one account are fine.

Does more memory always cost more?

No. If duration drops faster than memory rises, GB-seconds fall. Measure. The public pricing page is the rate; your duration is the other half.

When is provisioned concurrency cheaper than accepting cold starts?

When the revenue or SLO cost of a cold start exceeds the warmth you buy. We do that math with p95 latency and the provisioned line item, not with a blog default.

Can Compute Optimizer be trusted alone?

It is a good first pass. It does not know your SLO or your native dependencies. We treat it as a candidate list.

Will this affect SOC 2 change management?

Memory and architecture changes are production changes. Use the same change ticket you already use. Rutagon does not hold a SOC 2 report; we keep the change evidence easy to attach if you do.