AWS Builder Center
Your API Calls the Same Lambda 1,000 Times a Minute (Enable Caching Already) πŸš€

Your API Calls the Same Lambda 1,000 Times a Minute (Enable Caching Already) πŸš€

API Gateway without caching means every request hits your backend - even for identical data. Enable caching with Terraform and cut Lambda invocations by 90%.

Series: Save on AWS with Terraform (21 articles)

  1. …
  2. 12
    Your API Calls the Same Lambda 1,000 Times a Minute (Enable Caching Already) πŸš€ This article

API Gateway without caching means every request hits your backendβ€”even for identical data. Enable caching with Terraform and cut Lambda invocations by 90%.

Question: How many times do your users request the same data?
Answer: Way more than you think.
Every API call without caching triggers:
  • Lambda invocation
  • Database query
  • API response serialization
  • All the compute costs
Even when the data hasn’t changed.
Here’s a typical high-traffic API:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Requests: 10 million/month
Cacheable: 80% (product info, configs, public data)
Cache hit rate: 90%

Without caching:
- 10M Lambda invocations
- Cost: $200/month

With caching:
- 1M Lambda invocations (9M served from cache)
- Cost: $20/month + $28 (cache)
- Total: $48/month

Savings: $152/month = $1,824/year (76% reduction!) πŸ’°
Let me show you how to enable API Gateway caching with Terraform.

πŸ’Έ The Cost of Not Caching

API Gateway pricing:
  • Requests: $3.50 per million
  • Lambda: $0.20 per 1M requests + duration
  • Cache: $0.02/hour per GB (flat fee)
Example: Public API serving product catalog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Traffic: 5M requests/month
Cacheable data: 90%
Average Lambda duration: 100ms
Lambda memory: 512MB

Without cache:
- 5M Lambda invocations
- 5M Γ— 0.1s Γ— $0.0000083/GB-s = $41.50
- API Gateway: 5M Γ— $0.0000035 = $17.50
- Total: $59/month

With cache (0.5GB, 1 hour TTL):
- Cache cost: 0.5GB Γ— $0.02/hr Γ— 730hrs = $7.30/month
- Lambda (10% requests): $4.15
- API Gateway: $17.50
- Total: $28.95/month

Savings: $30/month = $360/year (51% reduction!)

🎯 When to Use Caching

Perfect for:
  • βœ… Product catalogs
  • βœ… Configuration endpoints
  • βœ… Public reference data
  • βœ… Weather/stock prices (stale for 1-5 min is fine)
  • βœ… User profiles (change infrequently)
  • βœ… Search results
  • βœ… Rate-limited external APIs
Don’t cache:
  • ❌ User-specific authenticated data (unless per-user cache)
  • ❌ Real-time data requiring sub-second freshness
  • ❌ POST/PUT/DELETE requests
  • ❌ Payment processing

πŸ› οΈ Terraform Implementation

Basic REST API with Caching

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# api-gateway-cache.tf

resource "aws_api_gateway_rest_api" "api" {
name = "products-api"
}

resource "aws_api_gateway_resource" "products" {
rest_api_id = aws_api_gateway_rest_api.api.id
parent_id = aws_api_gateway_rest_api.api.root_resource_id
path_part = "products"
}

resource "aws_api_gateway_method" "get_products" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.products.id
http_method = "GET"
authorization = "NONE"
}

resource "aws_api_gateway_integration" "lambda" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.products.id
http_method = aws_api_gateway_method.get_products.http_method

integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.products.invoke_arn

# Enable caching for this integration
cache_key_parameters = []
cache_namespace = "products"
}

# Enable caching at the stage level
resource "aws_api_gateway_deployment" "api" {
rest_api_id = aws_api_gateway_rest_api.api.id

depends_on = [
aws_api_gateway_integration.lambda
]

lifecycle {
create_before_destroy = true
}
}

resource "aws_api_gateway_stage" "prod" {
deployment_id = aws_api_gateway_deployment.api.id
rest_api_id = aws_api_gateway_rest_api.api.id
stage_name = "prod"

# Enable caching!
cache_cluster_enabled = true
cache_cluster_size = "0.5" # 0.5GB cache

# Cache settings
variables = {
cacheEnabled = "true"
}
}

resource "aws_api_gateway_method_settings" "products_cache" {
rest_api_id = aws_api_gateway_rest_api.api.id
stage_name = aws_api_gateway_stage.prod.stage_name
method_path = "${aws_api_gateway_resource.products.path_part}/${aws_api_gateway_method.get_products.http_method}"

settings {
caching_enabled = true
cache_ttl_in_seconds = 300 # 5 minutes
cache_data_encrypted = true

# Optional: Require cache key
require_authorization_for_cache_control = true
}
}

HTTP API with Caching (Simpler, Cheaper)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# Note: HTTP APIs don't support built-in caching
# Use CloudFront instead for caching HTTP APIs

resource "aws_cloudfront_distribution" "api_cache" {
enabled = true

origin {
domain_name = "${aws_apigatewayv2_api.http_api.id}.execute-api.${var.region}.amazonaws.com"
origin_id = "api-gateway"

custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}

default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "api-gateway"
viewer_protocol_policy = "redirect-to-https"

cache_policy_id = aws_cloudfront_cache_policy.api.id
}

restrictions {
geo_restriction {
restriction_type = "none"
}
}

viewer_certificate {
cloudfront_default_certificate = true
}
}

resource "aws_cloudfront_cache_policy" "api" {
name = "api-cache-policy"
default_ttl = 300
max_ttl = 3600
min_ttl = 60

parameters_in_cache_key_and_forwarded_to_origin {
cookies_config {
cookie_behavior = "none"
}
headers_config {
header_behavior = "whitelist"
headers {
items = ["Accept", "Authorization"]
}
}
query_strings_config {
query_string_behavior = "all"
}
}
}

Production Module with Per-Endpoint Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# modules/cached-api/main.tf

variable "api_name" {
description = "API name"
type = string
}

variable "cache_size" {
description = "Cache size (0.5, 1.6, 6.1, 13.5, 28.4, 58.2, 118, 237)"
type = string
default = "0.5"
}

variable "endpoints" {
description = "API endpoints with cache configuration"
type = map(object({
path = string
method = string
lambda_arn = string
cache_ttl = number
cache_enabled = bool
}))
}

resource "aws_api_gateway_rest_api" "api" {
name = var.api_name
}

resource "aws_api_gateway_resource" "endpoints" {
for_each = var.endpoints

rest_api_id = aws_api_gateway_rest_api.api.id
parent_id = aws_api_gateway_rest_api.api.root_resource_id
path_part = each.value.path
}

resource "aws_api_gateway_method" "methods" {
for_each = var.endpoints

rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.endpoints[each.key].id
http_method = each.value.method
authorization = "NONE"
}

resource "aws_api_gateway_integration" "integrations" {
for_each = var.endpoints

rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.endpoints[each.key].id
http_method = aws_api_gateway_method.methods[each.key].http_method

integration_http_method = "POST"
type = "AWS_PROXY"
uri = each.value.lambda_arn

cache_namespace = each.key
}

resource "aws_api_gateway_deployment" "api" {
rest_api_id = aws_api_gateway_rest_api.api.id

depends_on = [
aws_api_gateway_integration.integrations
]

lifecycle {
create_before_destroy = true
}
}

resource "aws_api_gateway_stage" "prod" {
deployment_id = aws_api_gateway_deployment.api.id
rest_api_id = aws_api_gateway_rest_api.api.id
stage_name = "prod"

cache_cluster_enabled = true
cache_cluster_size = var.cache_size
}

resource "aws_api_gateway_method_settings" "cache_settings" {
for_each = {
for k, v in var.endpoints : k => v if v.cache_enabled
}

rest_api_id = aws_api_gateway_rest_api.api.id
stage_name = aws_api_gateway_stage.prod.stage_name
method_path = "${each.value.path}/${each.value.method}"

settings {
caching_enabled = true
cache_ttl_in_seconds = each.value.cache_ttl
cache_data_encrypted = true
}
}

output "api_endpoint" {
value = aws_api_gateway_stage.prod.invoke_url
}

Usage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
module "products_api" {
source = "./modules/cached-api"

api_name = "products-api"
cache_size = "0.5" # 0.5GB cache

endpoints = {
products = {
path = "products"
method = "GET"
lambda_arn = aws_lambda_function.get_products.invoke_arn
cache_ttl = 300 # 5 minutes
cache_enabled = true
}
categories = {
path = "categories"
method = "GET"
lambda_arn = aws_lambda_function.get_categories.invoke_arn
cache_ttl = 600 # 10 minutes
cache_enabled = true
}
orders = {
path = "orders"
method = "POST"
lambda_arn = aws_lambda_function.create_order.invoke_arn
cache_ttl = 0
cache_enabled = false # Don't cache POST
}
}
}

πŸ’‘ Pro Tips

1. Choose the Right Cache Size

1
2
3
4
5
# Cache sizes and costs
cache_cluster_size = "0.5" # $0.02/hr = $14.60/month
cache_cluster_size = "1.6" # $0.038/hr = $27.74/month
cache_cluster_size = "6.1" # $0.20/hr = $146/month
cache_cluster_size = "13.5" # $0.25/hr = $182.50/month
Start with 0.5GB. Monitor CacheHitCount and CacheMissCount metrics.

2. Set Appropriate TTL

1
2
3
4
5
# Based on data freshness requirements
cache_ttl_in_seconds = 60 # 1 minute (near real-time)
cache_ttl_in_seconds = 300 # 5 minutes (common)
cache_ttl_in_seconds = 3600 # 1 hour (static data)
cache_ttl_in_seconds = 86400 # 24 hours (rarely changes)

3. Use Cache Keys for Variations

1
2
3
4
5
6
resource "aws_api_gateway_integration" "lambda" {
# Cache by query parameters
cache_key_parameters = ["method.request.querystring.category"]

# Each category gets its own cache entry
}

4. Monitor Cache Performance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
resource "aws_cloudwatch_dashboard" "api_cache" {
dashboard_name = "api-gateway-cache"

dashboard_body = jsonencode({
widgets = [
{
type = "metric"
properties = {
metrics = [
["AWS/ApiGateway", "CacheHitCount", { stat = "Sum" }],
[".", "CacheMissCount", { stat = "Sum" }]
]
period = 300
region = var.region
title = "Cache Hit vs Miss"
}
}
]
})
}

# Alert on low cache hit rate
resource "aws_cloudwatch_metric_alarm" "low_cache_hit" {
alarm_name = "api-low-cache-hit-rate"
comparison_operator = "LessThanThreshold"
evaluation_periods = 2
threshold = 70 # Alert if < 70% hit rate

metric_query {
id = "hit_rate"
expression = "(hits / (hits + misses)) * 100"
label = "Cache Hit Rate %"
return_data = true
}

metric_query {
id = "hits"
metric {
metric_name = "CacheHitCount"
namespace = "AWS/ApiGateway"
period = 300
stat = "Sum"
}
}

metric_query {
id = "misses"
metric {
metric_name = "CacheMissCount"
namespace = "AWS/ApiGateway"
period = 300
stat = "Sum"
}
}
}

5. Invalidate Cache When Needed

1
2
3
4
5
6
7
8
9
10
11
12
13
# In Lambda that updates data
import boto3

api_gateway = boto3.client('apigateway')

def handler(event, context):
# Update data...

# Invalidate cache
api_gateway.flush_stage_cache(
restApiId='abc123',
stageName='prod'
)

πŸ“Š Real-World Example

E-commerce API with product catalog
Before caching:
1
2
3
4
5
6
7
8
9
Traffic: 20M requests/month
- 18M product lookups
- 2M order creation

Lambda costs:
- 20M invocations Γ— $0.20/1M = $4
- Duration: 100ms Γ— 20M Γ— $0.0000083 = $166

Total: $170/month
After caching (5-min TTL, 90% hit rate):
1
2
3
4
5
6
7
8
9
10
11
Cache cost: 0.5GB Γ— $0.02/hr Γ— 730hrs = $14.60/month

Lambda costs:
- Product lookups: 1.8M invocations (90% cached)
- Orders: 2M invocations (not cached)
- Total: 3.8M invocations
- Cost: 3.8M Γ— 100ms Γ— $0.0000083 = $31.50

Total: $14.60 + $31.50 = $46.10/month

Savings: $123.90/month = $1,487/year (73% reduction!) πŸŽ‰

πŸš€ Quick Start

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. Enable caching on existing API
terraform apply

# 2. Test cache is working
curl -I https://api.example.com/products

# Response headers show cache status:
# X-Cache: Hit from cloudfront
# X-Amz-Cf-Pop: IAD89-C1

# 3. Monitor cache hit rate
aws cloudwatch get-metric-statistics \
--namespace AWS/ApiGateway \
--metric-name CacheHitCount \
--dimensions Name=ApiName,Value=products-api \
--start-time 2024-01-01T00:00:00Z \
--end-time 2024-01-31T23:59:59Z \
--period 86400 \
--statistics Sum

# 4. Watch Lambda invocations drop πŸ’°

⚠️ Common Gotchas

1. HTTP APIs Don’t Support Native Caching

Use CloudFront instead:
1
2
3
4
5
# REST API: Native caching βœ…
cache_cluster_enabled = true

# HTTP API: Use CloudFront βœ…
resource "aws_cloudfront_distribution" "api_cache" { ... }

2. Cache Keys Must Include Variations

1
2
3
4
5
# WRONG - All users get same cached response
cache_key_parameters = []

# RIGHT - Cache per user
cache_key_parameters = ["method.request.header.Authorization"]

3. Default Cache Size May Be Small

1
2
3
4
5
# Too small for high traffic
cache_cluster_size = "0.5" # Only 0.5GB

# Monitor and increase if needed
cache_cluster_size = "1.6" # 1.6GB

🎯 Summary

The Problem:
  • Every API call hits Lambda (even for identical data)
  • High Lambda costs for cacheable endpoints
  • Unnecessary database queries
The Solution:
  • Enable API Gateway caching
  • Set appropriate TTL (5-60 minutes)
  • Monitor cache hit rate
The Result:
  • Typical savings: 70-90% on Lambda costs
  • Faster response times (cache < Lambda)
  • Reduced backend load
Implementation:
  • Add cache_cluster_enabled = true
  • Set cache size (start with 0.5GB)
  • Configure per-endpoint TTL
  • Cost: $15-30/month for most APIs
Stop invoking Lambda for data that hasn’t changed. Enable caching and cut your API costs by 70%+. πŸš€

Enabled API Gateway caching? What’s your cache hit rate? Share in the comments! πŸ’¬
Follow for more AWS cost optimization with Terraform! ⚑

Series: Save on AWS with Terraform (21 articles)

  1. …
  2. 12
    Your API Calls the Same Lambda 1,000 Times a Minute (Enable Caching Already) πŸš€ This article
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article