AWS ElastiCache: Redis and Memcached Caching Strategies (2026)

ElastiCache is AWS's managed in-memory caching service — eliminating the operational burden of managing Redis or Memcached clusters. A well-placed cache can reduce database load by 80%+ and cut API response times from hundreds of milliseconds to single-digit milliseconds. This guide covers the Redis vs Memcached decision, cluster configuration, caching patterns, and Spring Boot integration.

1. Redis vs Memcached

FeatureRedisMemcached
Data structuresString, Hash, List, Set, ZSet, Stream, BitmapString only
PersistenceRDB snapshots + AOF logsNone
ReplicationYes (primary + replicas)No
Multi-AZYes (automatic failover)No
Pub/SubYesNo
Lua scriptingYesNo
Multi-threadingLimited (6.0+ for I/O)Yes (built-in)
Use Memcached whenSimple caching, multi-threaded scaling needed, no persistence required, simplest possible setup
Pro Tip: Choose Redis for almost everything. The extra features (data structures, persistence, replication, Pub/Sub) are worth the marginal complexity. Only use Memcached if you specifically need multi-threaded horizontal scaling for pure simple key-value caching.

2. Creating an ElastiCache Redis Cluster

# Create subnet group (must be in same VPC as your app)
aws elasticache create-cache-subnet-group \
  --cache-subnet-group-name myapp-cache-subnets \
  --cache-subnet-group-description "ElastiCache subnets" \
  --subnet-ids subnet-private-1a subnet-private-1b

# Create Redis replication group (primary + 1 replica, Multi-AZ)
aws elasticache create-replication-group \
  --replication-group-id myapp-redis \
  --replication-group-description "MyApp production cache" \
  --cache-node-type cache.t3.medium \
  --engine redis \
  --engine-version 7.2 \
  --num-cache-clusters 2 \
  --cache-subnet-group-name myapp-cache-subnets \
  --security-group-ids sg-redis-cache \
  --automatic-failover-enabled \
  --multi-az-enabled \
  --at-rest-encryption-enabled \
  --transit-encryption-enabled \
  --auth-token "$(aws secretsmanager get-secret-value --secret-id myapp/redis/token --query SecretString --output text)"

3. Cluster Mode

Cluster mode disabled (default): one primary with up to 5 replicas in one shard. Simple setup, good for most workloads.

Cluster mode enabled: data sharded across up to 500 shards, each with replicas. Use when your dataset exceeds a single node's memory or you need write scaling:

# Cluster mode enabled — 3 shards, 1 replica each
aws elasticache create-replication-group \
  --replication-group-id myapp-redis-cluster \
  --replication-group-description "Clustered Redis" \
  --cache-node-type cache.r6g.large \
  --engine redis \
  --engine-version 7.2 \
  --num-node-groups 3 \
  --replicas-per-node-group 1 \
  --automatic-failover-enabled \
  --cache-subnet-group-name myapp-cache-subnets \
  --security-group-ids sg-redis-cache
Note: With cluster mode enabled, all keys in a multi-key operation (MGET, pipelines, Lua scripts) must hash to the same slot. Use hash tags {user}.sessions and {user}.profile to force related keys to the same shard.

4. Caching Patterns

Cache-Aside (Lazy Loading)

@Service
public class ProductService {

    private final RedisTemplate<String, Product> redisTemplate;
    private final ProductRepository productRepository;

    public Product getProduct(Long id) {
        String cacheKey = "product:" + id;
        // 1. Check cache
        Product cached = redisTemplate.opsForValue().get(cacheKey);
        if (cached != null) return cached;

        // 2. Cache miss — load from DB
        Product product = productRepository.findById(id)
            .orElseThrow(() -> new NotFoundException("Product not found: " + id));

        // 3. Store in cache with TTL
        redisTemplate.opsForValue().set(cacheKey, product, Duration.ofMinutes(30));
        return product;
    }

    public void updateProduct(Long id, Product updated) {
        productRepository.save(updated);
        // Invalidate cache on update
        redisTemplate.delete("product:" + id);
    }
}

Write-Through

public Product updateProduct(Long id, Product updated) {
    // Write to DB AND cache simultaneously
    Product saved = productRepository.save(updated);
    redisTemplate.opsForValue().set("product:" + id, saved, Duration.ofHours(1));
    return saved;
}

5. Spring Boot Integration

# application.yaml
spring:
  data:
    redis:
      host: myapp-redis.abc123.use1.cache.amazonaws.com
      port: 6379
      ssl:
        enabled: true
      password: ${REDIS_AUTH_TOKEN}
      lettuce:
        pool:
          max-active: 20
          max-idle: 10
          min-idle: 5
@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            .serializeValuesWith(
                RedisSerializationContext.SerializationPair
                    .fromSerializer(new GenericJackson2JsonRedisSerializer()));

        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .withCacheConfiguration("products", config.entryTtl(Duration.ofHours(1)))
            .withCacheConfiguration("users", config.entryTtl(Duration.ofMinutes(5)))
            .build();
    }
}

// Use Spring Cache annotations
@Service
public class ProductService {

    @Cacheable(value = "products", key = "#id")
    public Product getProduct(Long id) { ... }

    @CachePut(value = "products", key = "#product.id")
    public Product updateProduct(Product product) { ... }

    @CacheEvict(value = "products", key = "#id")
    public void deleteProduct(Long id) { ... }
}

6. Session Storage

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
spring:
  session:
    store-type: redis
    timeout: 30m
    redis:
      namespace: myapp:session

This stores all HTTP sessions in Redis — enabling stateless application instances and sticky-session-free load balancing.

Frequently Asked Questions

What cache.t3.medium vs cache.r6g — how do I choose?

t3.medium (burstable): good for dev/staging with unpredictable loads. r6g (memory-optimized Graviton): best price/performance for production — r6g.large gives 13GB memory at about the same cost as r5.large with 12GB. Avoid t-series in production for latency-sensitive workloads.

How do I handle cache stampede (thundering herd)?

When a hot cache key expires, hundreds of requests simultaneously miss and hammer the database. Solutions: use probabilistic early expiration (XFetch algorithm), cache locking (SET NX with short TTL to let one request rebuild), or background refresh (refresh before expiry using a background thread).

How do I monitor ElastiCache Redis?

Key CloudWatch metrics: CacheHitRate (should be >90%), CurrConnections (watch for connection leaks), DatabaseMemoryUsagePercentage (alert at 75%), EngineCPUUtilization (alert at 70%), SwapUsage (should be 0). Enable slow log in Redis config to identify slow commands.