Redis Cluster Caching
Cluster Architecture
┌─────────────┠┌─────────────┠┌─────────────â”
│ Node 1 │ │ Node 2 │ │ Node 3 │
│ Slots 0- │ │ Slots 5461-│ │ Slots 10923│
│ 5460 │ │ 10922 │ │ 16383 │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
┌──────┴──────┠┌──────┴──────┠┌──────┴──────â”
│ Replica 1 │ │ Replica 2 │ │ Replica 3 │
└─────────────┘ └─────────────┘ └─────────────┘
Cache Distribution
Key Hash: CRC16(key) MOD 16384 = slot number
Each node responsible for subset of slots
Replicas provide redundancy per node
Magento Cache Config
// app/etc/env.php
'cache' => [
'frontend' => [
'default' => [
'backend' => 'Magento\Framework\Cache\Backend\Redis',
'backend_options' => [
'server' => 'redis-cluster.example.com',
'port' => '6379',
'database' => '0',
'compress_data' => '1'
]
],
'page_cache' => [
'backend' => 'Magento\Framework\Cache\Backend\Redis',
'backend_options' => [
'server' => 'redis-cluster.example.com',
'port' => '6379',
'database' => '1',
'compress_data' => '0'
]
]
]
],
Cache Consistency
Consistency Challenges
Problem: Cache-DB inconsistency
1. Write to DB
2. Invalidate cache
3. Read request gets stale data from cache
4. Cache miss → read from DB → inconsistent
Consistency Strategies
Strategy | Approach | Trade-off
───────────────────|─────────────────────────|─────────────
Write-through | Update cache + DB | Write latency
Write-behind | Update cache, async DB | Possible loss
Cache-aside | Read from DB, cache | Stale reads
Event-driven | Invalidate on events | Eventual consistency
Event-Driven Invalidation
// Invalidate cache on data change
$eventManager->dispatch('catalog_product_save_after', [
'product' => $product
]);
// Observer invalidates cache
public function execute(EventObserver $observer) {
$product = $observer->getEvent()->getProduct();
$cacheKey = 'product_' . $product->getId();
$this->cache->remove($cacheKey);
// Also invalidate related caches
$this->cache->clean([
'catalog_product_' . $product->getId(),
'category_products_' . $product->getCategoryId()
]);
}
Cache Warming
Warming Strategies
Strategy | When | Use Case
──────────────────|───────────────────────|─────────────
Pre-deployment | Before traffic | Code deploy
Scheduled | Off-peak hours | Daily warm
On-demand | On cache miss spike | Flash sales
Predictive | Based on patterns | Smart warming
Cache Warming Script
// Warm critical caches
class CacheWarmer {
public function warm() {
// Product pages
$products = $this->productCollection->create()
->addFieldToFilter('status', 1)
->setPageSize(1000)
->load();
foreach ($products as $product) {
$this->warmProductPage($product->getId());
}
// Category pages
$categories = $this->categoryCollection->create()
->addFieldToFilter('is_active', 1)
->load();
foreach ($categories as $category) {
$this->warmCategoryPage($category->getId());
}
}
private function warmProductPage($productId) {
$url = $this->urlBuilder->getUrl('catalog/product/view', [
'id' => $productId
]);
$this->httpClient->get($url);
}
}
Scheduled Warming
# cron: Warm cache daily at 3 AM
0 3 * * * /usr/local/bin/magento-cache-warm.sh
# Warm before deployment
/bin/magento cache:warm
Cache Failure Handling
Cache Failure Scenarios
Scenario | Impact | Handling
───────────────────|──────────────|──────────────────
Cache miss | DB load | Normal operation
Cache unavailable | All DB | Graceful degradation
Cache stampede | DB overload | Prevent with locks
Cache inconsistency| Stale data | Event-driven invalidation
Cache-Aside with Fallback
function getFromCache($key, $ttl = 3600) {
try {
$value = $this->redis->get($key);
if ($value !== false) {
return unserialize($value);
}
} catch (Exception $e) {
$this->logger->warning('Cache read failed', ['key' => $key]);
}
// Cache miss or failure - get from DB
$value = $this->database->fetch($key);
try {
$this->redis->setex($key, $ttl, serialize($value));
} catch (Exception $e) {
$this->logger->warning('Cache write failed', ['key' => $key]);
}
return $value;
}
Cache Stampede Prevention
// Use locks to prevent stampede
$lockKey = 'lock_' . $cacheKey;
if ($this->redis->set($lockKey, '1', ['NX', 'EX' => 10])) {
// Got lock - rebuild cache
$value = $this->rebuildCache($cacheKey);
$this->redis->del($lockKey);
} else {
// Wait for other process
usleep(100000); // 100ms
return $this->getFromCache($cacheKey);
}
Quiz
1. How does Redis Cluster distribute keys?
2. What is cache stampede?
3. What is cache warming?
Flashcards
Question
Redis Cluster key distribution?
Click to reveal answer
Answer
CRC16(key) MOD 16384 assigns to slot
Question
Cache stampede?
Click to reveal answer
Answer
Multiple processes rebuilding same cache simultaneously
Question
Cache warming purpose?
Click to reveal answer
Answer
Pre-populate cache before traffic to avoid cold starts
Question
Cache consistency strategy?
Click to reveal answer
Answer
Event-driven invalidation on data changes
Revision Notes
Key Takeaways
- 1. Redis Cluster distributes keys across 16384 hash slots
- 2. Event-driven invalidation ensures cache consistency
- 3. Cache warming pre-populates before traffic hits
- 4. Use locks to prevent cache stampede
- 5. Graceful degradation when cache fails
Interview Tips
- • Explain Redis Cluster key distribution
- • Discuss cache consistency strategies and trade-offs
- • Describe cache warming and stampede prevention
Cheat Sheet
Distributed Caching:
Redis Cluster: 16384 hash slots
Key → Slot → Node assignment
Consistency:
Write-through: Sync cache + DB
Event-driven: Invalidate on changes
Cache-aside: Read-through pattern
Cache Warming:
Pre-populate before traffic
Scheduled: Daily at off-peak
On-demand: Before deployments
Failures:
Cache-aside with DB fallback
Locks prevent stampede
Graceful degradation