Azure API Management Caching Strategies

Improving Performance with Response Caching


Introduction

Caching is one of the most effective ways to improve API performance and reduce backend load. Azure API Management (APIM) provides built-in caching capabilities that can dramatically reduce response times and API costs.

This comprehensive guide covers:

  • Cache basics — How APIM caching works
  • Built-in cache — Using APIM's internal cache
  • Redis cache — Using Azure Redis for distributed caching
  • Cache policies — Configuration options and examples
  • Vary by — Controlling cache variations
  • Best practices — Optimal caching strategies

How APIM Caching Works

Cache Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                    APIM CACHING ARCHITECTURE                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌──────────┐                              ┌──────────┐            │
│   │  Client  │                              │  Client  │            │
│   │    1     │                              │    2     │            │
│   └────┬─────┘                              └────┬─────┘            │
│        │                                         │                  │
│        ▼                                         ▼                  │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                  API Management Gateway                     │   │
│   │                                                             │   │
│   │  ┌─────────────────────────────────────────────────────┐    │   │
│   │  │              Cache (Built-in or Redis)              │    │   │
│   │  │                                                     │    │   │
│   │  │  Key: /orders?status=active   Value: {...}          │    │   │
│   │  │  Key: /products              Value: {...}           │    │   │
│   │  │  TTL: 3600s                  Expires: 14:30         │    │   │
│   │  └─────────────────────────────────────────────────────┘    │   │ 
│   └──────┬───────────────────────────────────────┬──────────────┘   │
│          │                                       │                  │
│          ▼                                       ▼                  │
│   ┌──────────────┐                        ┌──────────────┐          │
│   │ Backend API  │                        │ Backend API  │          │
│   │ (first time) │                        │ (not called) │          │
│   └──────────────┘                        └──────────────┘          │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Cache Key Generation

APIM generates cache keys based on:

  • URL path — The request path
  • Query parameters — (if configured to include)
  • Vary-by headers — Custom headers that differentiate cached responses
  • Developer ID — (if vary-by-developer is enabled)

Built-in Cache Configuration

Basic Cache Policy

<outbound>
    <!-- Check cache -->
    <cache-lookup vary-by-developer="false">
        <vary-by-query-parameter>page</vary-by-query-parameter>
    </cache-lookup>
    
    <!-- Store in cache -->
    <cache-store duration="3600" />
</outbound>

Cache with Vary-by Headers

<outbound>
    <cache-lookup vary-by-developer="false">
        <!-- Cache separate versions for different Accept headers -->
        <vary-by-header>Accept</vary-by-header>
        <!-- Also vary by format -->
        <vary-by-header>Accept-Language</vary-by-header>
    </cache-lookup>
    
    <!-- Cache for 1 hour -->
    <cache-store duration="3600" />
</outbound>

Complete Cache Policy

<outbound>
    <choose>
        <!-- Only cache successful responses -->
        <when condition="@(context.Response.StatusCode == 200)">
            <cache-lookup vary-by-developer="true">
                <vary-by-header>Accept</vary-by-header>
                <vary-by-header>Accept-Encoding</vary-by-header>
                <vary-by-query-parameter>version</vary-by-query-parameter>
            </cache-lookup>
            
            <cache-store duration="300" />
        </when>
        <otherwise>
            <!-- Don't cache error responses -->
        </otherwise>
    </choose>
</outbound>

Azure Redis Cache

Configure Redis Cache

<outbound>
    <!-- Lookup from Redis -->
    <cache-lookup-from-redis 
        redis-cache-name="my-redis-cache" 
        redis-cache-connection-string="<connection-string-from-keyvault>" />
    
    <!-- Store to Redis -->
    <cache-store duration="7200" />
</outbound>

Redis Connection in App Settings

{
  "CachedRedisConnectionString": {
    "type": "securestring",
    "value": "rediss://myredis.cache.windows.net:6380,password=xxx,ssl=true"
  }
}

Redis vs Built-in Cache

FeatureBuilt-in CacheRedis Cache
ScopeSingle APIM instanceCross-instance
CapacityLimitedScalable
PersistenceIn-memoryDurable
CostIncludedAdditional
LatencyLowerSlightly higher

Cache Key Variations

Vary by Developer

<outbound>
    <!-- Separate cache per developer -->
    <cache-lookup vary-by-developer="true">
        <vary-by-header>Authorization</vary-by-header>
    </cache-lookup>
    
    <cache-store duration="1800" />
</outbound>

Vary by Query Parameters

<outbound>
    <!-- Cache different versions for different page numbers -->
    <cache-lookup>
        <vary-by-query-parameter>page</vary-by-query-parameter>
        <vary-by-query-parameter>size</vary-by-query-parameter>
        <vary-by-query-parameter>sort</vary-by-query-parameter>
    </cache-lookup>
    
    <cache-store duration="300" />
</outbound>

Vary by Headers

<outbound>
    <!-- Different cached response for different Accept types -->
    <cache-lookup>
        <vary-by-header>Accept</vary-by-header>
    </cache-lookup>
    
    <!-- JSON vs XML responses cached separately -->
    <cache-store duration="600" />
</outbound>

Conditional Caching

Only Cache Specific Responses

<outbound>
    <choose>
        <!-- Cache GET requests only -->
        <when condition="@(context.Request.Method == HttpMethod.Get)">
            
            <!-- Only cache successful responses -->
            <when condition="@(context.Response.StatusCode == 200)">
                <cache-lookup vary-by-developer="false">
                    <vary-by-header>Accept</vary-by-header>
                </cache-lookup>
                
                <cache-store duration="300" />
            </when>
        </when>
        
        <!-- Don't cache other methods -->
    </choose>
</outbound>

Cache Based on Response Content

<outbound>
    <choose>
        <when condition="@{
            var cacheControl = context.Response.Headers.GetValueOrDefault(\"Cache-Control\", \"\");
            return cacheControl.Contains(\"no-cache\") == false;
        }">
            <cache-lookup />
            <cache-store duration="600" />
        </when>
    </choose>
</outbound>

Cache Invalidation

Invalidate Cache on Update

<inbound>
    <!-- Delete cache when products are updated -->
    <choose>
        <when condition="@(context.Request.Method == HttpMethod.Delete)">
            <cache-remove-value key="products" />
        </when>
        <when condition="@(context.Request.Method == HttpMethod.Put ||
                          context.Request.Method == HttpMethod.Post)">
            <cache-remove-value key="products" />
            <cache-remove-value key="products-categories" />
        </when>
    </choose>
</outbound>

Wildcard Invalidation

<inbound>
    <!-- Invalidate all product-related caches -->
    <cache-remove>
        <on-error>
            <!-- Continue even if removal fails -->
        </on-error>
    </cache-remove>
</outbound>

Use Cases and Recommended Durations

Cache Duration Guidelines

API TypeExampleRecommended Duration
Static dataCountry codes, categories1-24 hours
Reference dataProduct catalog, pricing5-60 minutes
User-specificUser profile, preferences5-30 minutes
Dynamic dataStock levels, availability1-10 minutes
Search resultsComplex queries1-5 minutes
Real-timeLive data, timestampsDon't cache

Examples by Use Case

<!-- Static reference data - long cache -->
<outbound>
    <cache-lookup />
    <cache-store duration="86400" />  <!-- 24 hours -->
</outbound>

<!-- User profile - medium cache -->
<outbound>
    <cache-lookup vary-by-developer="true" />
    <cache-store duration="1800" />  <!-- 30 minutes -->
</outbound>

<!-- Product catalog - short cache -->
<outbound>
    <cache-lookup>
        <vary-by-query-parameter>category</vary-by-query-parameter>
    </cache-lookup>
    <cache-store duration="300" />  <!-- 5 minutes -->
</outbound>

Best Practices

PracticeDescription
Cache GET requests onlyPOST/PUT/DELETE should modify data
Use vary-by appropriatelyDon't over-segment cache
Set appropriate TTLBalance freshness vs. performance
Cache errors brieflyDon't cache errors for long
Invalidate on updatesClear cache when data changes
Monitor cache hit rateTrack cache effectiveness

Monitoring Cache

# Get cache metrics
az monitor app-insights query \
  --app my-apim-insights \
  --query "requests | where name contains 'cache'"

Complete Example: Product API

<policies>
    <inbound>
        <!-- Add cache key header -->
        <set-variable name="cacheKey" value="@(context.Request.Url.Path)" />
        
        <!-- Invalidate on write -->
        <choose>
            <when condition="@(context.Request.Method == HttpMethod.Post ||
                              context.Request.Method == HttpMethod.Put ||
                              context.Request.Method == HttpMethod.Delete)">
                <cache-remove>
                    <on-error>
                        <!-- Continue on error -->
                    </on-error>
                </cache-remove>
            </when>
        </choose>
    </inbound>
    
    <backend>
        <base />
    </backend>
    
    <outbound>
        <choose>
            <when condition="@(context.Request.Method == HttpMethod.Get &&
                              context.Response.StatusCode == 200)">
                
                <!-- Vary by Accept header (JSON vs XML) -->
                <cache-lookup vary-by-developer="false">
                    <vary-by-header>Accept</vary-by-header>
                    <vary-by-header>Accept-Language</vary-by-header>
                    <vary-by-query-parameter>category</vary-by-query-parameter>
                </cache-lookup>
                
                <!-- Cache for 5 minutes -->
                <cache-store duration="300" />
            </when>
        </choose>
        
        <!-- Add cache headers to response -->
        <set-header name="X-Cache" exists-action="override">
            <value>@(context.Variables.ContainsKey("CacheHit") ? "HIT" : "MISS")</value>
        </set-header>
    </outbound>
</policies>

Related Topics


Azure Integration Hub - Intermediate Level