Introduction
Memcached is a high-performance, distributed memory caching system that stores data in RAM for fast access. It’s designed to speed up dynamic web applications by reducing database load and improving response times. Understanding Memcached is essential for system design interviews and building high-performance applications.
This guide covers:
- Memcached Fundamentals: Architecture, key-value storage, and operations
- Distributed Caching: Multi-server setup and consistent hashing
- Cache Strategies: Cache-aside, write-through, write-back
- Performance Optimization: Connection pooling, pipelining, and monitoring
- Best Practices: Cache invalidation, key design, and error handling
What is Memcached?
Memcached is a distributed memory caching system that:
- In-Memory Storage: Stores data in RAM for fast access
- Key-Value Store: Simple key-value data model
- Distributed: Multiple servers work together
- High Performance: Sub-millisecond latency
- Simple API: Easy to integrate
Key Concepts
Key: Unique identifier for cached data
Value: Data stored in cache
Expiration: Time-to-live (TTL) for cached items
Server: Memcached instance
Client: Application that uses Memcached
Cluster: Group of Memcached servers
Architecture
High-Level Architecture
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │────▶│ Client │────▶│ Client │
│ Application │ │ Application │ │ Application │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└────────────────────┴────────────────────┘
│
│ Memcached Protocol
│
▼
┌─────────────────────────┐
│ Memcached Client │
│ (Hash Distribution) │
└──────┬──────────────────┘
│
┌─────────────┴─────────────┐
│ │
┌──────▼──────┐ ┌───────▼──────┐
│ Memcached │ │ Memcached │
│ Server 1 │ │ Server 2 │
│ (RAM) │ │ (RAM) │
└─────────────┘ └─────────────┘
Explanation:
- Client Applications: Applications that use Memcached for caching (e.g., web applications, APIs, databases).
- Memcached Client: Client library that distributes keys across Memcached servers using consistent hashing.
- Memcached Servers: In-memory key-value stores that cache data in RAM for fast access. Multiple servers form a distributed cache cluster.
Core Architecture
┌─────────────────────────────────────────────────────────┐
│ Application │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Memcached Client │ │
│ │ (Connection Pooling, Hashing) │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────┴─────────────────┐ │
│ │ │ │
│ ┌─────▼──────┐ ┌───────▼──────┐ │
│ │ Memcached │ │ Memcached │ │
│ │ Server 1 │ │ Server 2 │ │
│ │ (RAM) │ │ (RAM) │ │
│ └────────────┘ └────────────────┘ │
└──────────────────────────────────────────────────────────┘
Data Model
Key-Value Storage
Key:
- String identifier
- Maximum 250 bytes
- Unique within cluster
Value:
- Binary data
- Maximum 1MB per item
- Any serializable data
Example:
Key: "user:123"
Value: {"id": 123, "name": "John", "email": "john@example.com"}
TTL: 3600 seconds
Operations
SET:
# Store value
memcache_client.set('user:123', user_data, time=3600)
GET:
# Retrieve value
user_data = memcache_client.get('user:123')
DELETE:
# Delete value
memcache_client.delete('user:123')
INCREMENT/DECREMENT:
# Increment counter
memcache_client.incr('counter:views', 1)
# Decrement counter
memcache_client.decr('counter:views', 1)
Distributed Caching
Consistent Hashing
How it Works:
- Keys mapped to servers using hash function
- Server addition/removal affects minimal keys
- Even distribution
Example:
# Hash key to server
def get_server(key, servers):
hash_value = hash(key)
server_index = hash_value % len(servers)
return servers[server_index]
# Key "user:123" → Server 1
# Key "user:456" → Server 2
Benefits:
- Even distribution
- Minimal rehashing on server changes
- Scalability
Multi-Server Setup
Configuration:
# Connect to multiple servers
servers = [
'memcached1.example.com:11211',
'memcached2.example.com:11211',
'memcached3.example.com:11211'
]
client = memcache.Client(servers)
Benefits:
- Horizontal scaling
- High availability
- Load distribution
Cache Strategies
1. Cache-Aside (Lazy Loading)
Pattern:
1. Check cache
2. If miss, read from database
3. Store in cache
4. Return data
Example:
def get_user(user_id):
# Check cache
cache_key = f'user:{user_id}'
user = memcache_client.get(cache_key)
if user is None:
# Cache miss - read from database
user = database.get_user(user_id)
# Store in cache
memcache_client.set(cache_key, user, time=3600)
return user
Pros:
- Simple to implement
- Cache only what’s accessed
- Flexible
Cons:
- Cache miss penalty
- Potential stale data
- Two round trips on miss
2. Write-Through
Pattern:
1. Write to cache
2. Write to database
3. Return success
Example:
def update_user(user_id, user_data):
# Write to cache
cache_key = f'user:{user_id}'
memcache_client.set(cache_key, user_data, time=3600)
# Write to database
database.update_user(user_id, user_data)
Pros:
- Cache always up-to-date
- No stale data
- Simple
Cons:
- Write latency
- Cache may not be needed
- More writes
3. Write-Back (Write-Behind)
Pattern:
1. Write to cache only
2. Return success
3. Async write to database
Example:
def update_user(user_id, user_data):
# Write to cache
cache_key = f'user:{user_id}'
memcache_client.set(cache_key, user_data, time=3600)
# Async write to database
async_queue.enqueue(database.update_user, user_id, user_data)
Pros:
- Fast writes
- Reduced database load
- Better performance
Cons:
- Data loss risk
- Complexity
- Eventual consistency
Cache Invalidation
TTL-Based Expiration
Automatic Expiration:
# Set with TTL
memcache_client.set('user:123', user_data, time=3600) # 1 hour
Benefits:
- Automatic cleanup
- Simple
- No manual invalidation
Manual Invalidation
Delete on Update:
def update_user(user_id, user_data):
# Update database
database.update_user(user_id, user_data)
# Invalidate cache
cache_key = f'user:{user_id}'
memcache_client.delete(cache_key)
Update on Write:
def update_user(user_id, user_data):
# Update database
database.update_user(user_id, user_data)
# Update cache
cache_key = f'user:{user_id}'
memcache_client.set(cache_key, user_data, time=3600)
Cache Warming
Pre-populate Cache:
def warm_cache():
# Load frequently accessed data
popular_users = database.get_popular_users()
for user in popular_users:
cache_key = f'user:{user.id}'
memcache_client.set(cache_key, user, time=3600)
Performance Optimization
Connection Pooling
Reuse Connections:
# Create connection pool
pool = memcache.Client(
['memcached1:11211', 'memcached2:11211'],
server_max_value_length=1024*1024
)
Benefits:
- Reduced overhead
- Better performance
- Resource efficiency
Pipelining
Batch Operations:
# Multiple operations in one request
results = memcache_client.get_multi(['key1', 'key2', 'key3'])
Benefits:
- Reduced network overhead
- Better throughput
- Efficiency
Key Design
Good Keys:
# Descriptive and unique
'user:123'
'product:456:details'
'session:abc123'
Bad Keys:
# Too generic
'data'
'item'
'info'
Memory Management
LRU Eviction:
- Least recently used items evicted
- Automatic when memory full
- No manual cleanup needed
Memory Allocation:
# Start with memory limit
memcached -m 1024 # 1GB memory
Monitoring
Statistics
Get Stats:
stats = memcache_client.get_stats()
print(stats)
Key Metrics:
- hits: Cache hits
- misses: Cache misses
- evictions: Items evicted
- bytes: Memory used
- curr_items: Current items
Hit Rate
Calculate:
hit_rate = hits / (hits + misses) * 100
Target:
- 80%+ hit rate
- Monitor and optimize
- Adjust TTL if needed
Performance Characteristics
Maximum Read & Write Throughput
Single Node:
- Max Read Throughput: 200K-500K ops/sec (GET operations)
- Max Write Throughput: 200K-500K ops/sec (SET operations)
- Typical Performance: 100K-300K ops/sec for mixed workloads
Distributed Cluster:
- Max Read Throughput: 200K-500K ops/sec per node (linear scaling)
- Max Write Throughput: 200K-500K ops/sec per node (linear scaling)
- Example: 10-node cluster can handle 2M-5M ops/sec total
Factors Affecting Throughput:
- Network latency
- Item size (larger items = lower throughput)
- Memory bandwidth
- CPU cores (multi-threaded)
- Connection pooling
- Key distribution across nodes
Performance Comparison:
- Memcached: Typically faster than Redis for simple GET/SET operations
- Redis: More features but slightly slower for basic operations
- Memcached: Optimized for maximum throughput on simple operations
Latency
Typical Latency:
- Local Network: < 0.5ms
- Same Data Center: 0.5-2ms
- Cross-Region: 5-50ms
Factors Affecting Latency:
- Network latency
- Item size
- Server load
- Memory access speed
Best Practices
1. Key Design
- Use descriptive prefixes
- Include identifiers
- Keep keys short
- Use consistent naming
2. TTL Strategy
- Set appropriate TTL
- Balance freshness vs performance
- Use longer TTL for stable data
- Shorter TTL for dynamic data
3. Error Handling
- Handle cache failures gracefully
- Fallback to database
- Don’t let cache errors break app
- Log cache errors
4. Memory Management
- Monitor memory usage
- Set appropriate limits
- Use LRU eviction
- Plan for growth
Comparison with Redis
Memcached vs Redis
Memcached:
- Simple key-value
- No persistence
- No data structures
- Faster for simple operations
Redis:
- Rich data structures
- Persistence options
- More features
- Slightly slower
When to Use Memcached:
- Simple caching needs
- Maximum performance
- No persistence needed
- Simple key-value only
When to Use Redis:
- Need data structures
- Persistence required
- More features needed
- Complex operations
What Interviewers Look For
Caching Understanding
- Cache Concepts
- Understanding of caching benefits
- Cache strategies
- Cache invalidation
- Red Flags: No caching understanding, wrong strategies, no invalidation
- Performance Optimization
- Cache hit rate optimization
- TTL strategy
- Key design
- Red Flags: No optimization, poor hit rate, bad keys
- Distributed Caching
- Consistent hashing
- Multi-server setup
- Load distribution
- Red Flags: Single server, no hashing, poor distribution
Problem-Solving Approach
- Cache Strategy Selection
- Choose appropriate strategy
- Cache-aside vs write-through
- Trade-off analysis
- Red Flags: Wrong strategy, no trade-offs, poor selection
- Cache Invalidation
- TTL vs manual
- Invalidation on updates
- Cache warming
- Red Flags: No invalidation, stale data, no warming
System Design Skills
- Performance Optimization
- Cache layer design
- Hit rate optimization
- Memory management
- Red Flags: No cache, poor performance, no optimization
- Scalability
- Distributed caching
- Horizontal scaling
- Load distribution
- Red Flags: Single server, no scaling, poor distribution
Communication Skills
- Clear Explanation
- Explains caching concepts
- Discusses trade-offs
- Justifies design decisions
- Red Flags: Unclear explanations, no justification, confusing
Meta-Specific Focus
- Performance Expertise
- Understanding of caching
- Memcached mastery
- Performance optimization
- Key: Demonstrate performance expertise
- System Design Skills
- Can design cache layer
- Understands performance impact
- Makes informed trade-offs
- Key: Show practical caching design skills
Summary
Memcached Key Points:
- In-Memory Caching: Fast key-value storage in RAM
- Distributed: Multiple servers with consistent hashing
- Simple API: Easy to integrate and use
- High Performance: Sub-millisecond latency
- Cache Strategies: Cache-aside, write-through, write-back
- LRU Eviction: Automatic memory management
Common Use Cases:
- Database query caching
- Session storage
- API response caching
- Frequently accessed data
- Performance optimization
Best Practices:
- Use descriptive cache keys
- Set appropriate TTL
- Implement cache-aside pattern
- Monitor cache hit rate
- Handle cache failures gracefully
- Use connection pooling
- Design for cache invalidation
Memcached is a powerful caching solution that significantly improves application performance by reducing database load and response times.