Cache vs Database Overview
Data Access Patterns
Database:
├── Source of truth
├── Persistent storage
├── ACID compliance
├── Complex queries
└── Slow (1-10ms)
Cache:
├── Temporary storage
├── Fast access (0.1-0.5ms)
├── Limited data
├── Simple key-value
└── May be stale
Cache Strategies
// 1. Cache-aside (Lazy loading)
public function getProduct($id)
{
$cached = $this->cache->load('product_' . $id);
if ($cached) return unserialize($cached);
$product = $this->db->fetchRow('SELECT * FROM ... WHERE entity_id = ?', [$id]);
$this->cache->save(serialize($product), 'product_' . $id, [], 3600);
return $product;
}
// 2. Write-through
public function saveProduct($product)
{
$this->db->save($product);
$this->cache->save(serialize($product), 'product_' . $product->getId());
}
// 3. Write-behind (Write-back)
public function saveProduct($product)
{
$this->cache->save(serialize($product), 'product_' . $product->getId());
$this->queue->sendMessage('db.save', $product->toArray());
}
// 4. Refresh-ahead
public function getProduct($id)
{
$cached = $this->cache->load('product_' . $id);
if ($cached && $this->needsRefresh('product_' . $id)) {
$this->queue->sendMessage('product.refresh', ['id' => $id]);
}
return $cached ? unserialize($cached) : $this->loadFromDb($id);
}
Data Freshness vs Performance
Freshness Requirements
| Data Type | Freshness Need | Cache TTL | Strategy |
|---|---|---|---|
| Product price | High | 60 seconds | Short TTL + event invalidation |
| Product stock | Critical | No cache | Direct DB query |
| Product name | Medium | 1 hour | Medium TTL |
| Category tree | Low | 24 hours | Long TTL |
| CMS content | Medium | 15 minutes | Event invalidation |
Performance Impact
// Without cache: 100% database load
// 1000 requests × 5ms = 5 seconds total
// Database: 1000 queries
// With cache (90% hit rate):
// 900 requests × 0.5ms = 0.45 seconds
// 100 requests × 5ms = 0.5 seconds
// Total: 0.95 seconds
// Database: 100 queries
// Improvement: 84% faster, 90% less DB load
Stale Data Scenarios
Scenario: Product price updated in DB
├── Cache has old price (TTL 60s)
├── Customer sees old price
├── Checkout uses DB price (correct)
└── Risk: Cart shows different price
Solutions:
1. Price change event invalidates cache
2. Short TTL for price data
3. Checkout always reads from DB
4. Display "price may have changed" warning
Cache Invalidation Strategies
Time-Based (TTL)
// Simple TTL
$this->cache->save($data, $key, [], 3600); // 1 hour
// Different TTL per data type
$ttl = [
'product_price' => 60,
'product_name' => 3600,
'category_tree' => 86400,
'cms_page' => 900,
];
$this->cache->save($data, $key, [], $ttl[$type]);
Event-Based
// Invalidate on data change
public function onProductSave($observer)
{
$product = $observer->getEvent()->getProduct();
$this->cache->remove('product_' . $product->getId());
$this->cache->remove('product_list_category_' . $product->getCategoryId());
}
// events.xml
<event name="catalog_product_save_after">
<observer name="cache_invalidation" instance="Vendor\Observer\CacheInvalidation"/>
</event>
Version-Based
// Versioned cache keys
$version = $this->config->get('catalog_version');
$cacheKey = 'product_' . $id . '_v' . $version;
// On data change, increment version
public function onProductSave($product)
{
$version = $this->config->get('catalog_version');
$this->config->save('catalog_version', $version + 1);
}
// All old cache keys become invalid
// New requests use new version
Tag-Based
// Cache with tags
$this->cache->save($data, $key, ['product_' . $id, 'category_' . $catId]);
// Invalidate by tag
$this->cache->clean('tags' => ['product_123']);
// All cache entries tagged with product_123 are invalidated
Comparison
| Strategy | Pros | Cons | Best For |
|---|---|---|---|
| TTL | Simple, automatic | May be stale | Static content |
| Event | Immediate invalidation | Complex setup | Critical data |
| Version | Bulk invalidation | Memory overhead | Frequent changes |
| Tag | Granular control | Implementation complexity | Related data |
Consistency Challenges
Race Conditions
// Problem: Read-modify-write race
$product = $this->cache->load('product_123');
$product->setQty($product->getQty() - 1);
$this->cache->save(serialize($product), 'product_123');
// Another request reads old qty
// Both subtract 1, only one subtracted
// Solution: Database as source of truth
$this->db->update('inventory', [
'qty' => new \Zend_Db_Expr('qty - 1')
], 'entity_id = 123 AND qty > 0');
$this->cache->remove('product_123');
Distributed Cache
Problem: Cache invalidation across servers
├── Server 1: Cache invalidated
├── Server 2: Still has old cache
└── Server 3: Still has old cache
Solutions:
1. Redis Pub/Sub for invalidation
2. Shared cache (Redis cluster)
3. TTL-based expiration
4. Version-based invalidation
Cache Stampede
// Problem: Many requests for same uncached data
// Cache expired, 1000 requests hit database simultaneously
// Solution 1: Locking
$lock = $this->lockManager->lock('product_123_lock');
if ($lock) {
$data = $this->db->fetchRow('SELECT * FROM ...');
$this->cache->save(serialize($data), 'product_123');
$this->lockManager->unlock('product_123_lock');
} else {
// Wait and retry
sleep(0.1);
return $this->getProduct($id);
}
// Solution 2: Early expiration
// Refresh cache before it expires
if ($this->cache->getTtl('product_123') < 300) {
$this->queue->sendMessage('product.refresh', ['id' => 123]);
}
Practice Problems
Design cache invalidation for product data with price, stock, and name updates.
Solution
// System:
// 1. Price: 60s TTL + event invalidation
// 2. Stock: No cache (always DB)
// 3. Name: 1h TTL + event invalidation
// 4. Category: 24h TTL + version-based
// 5. Distributed: Redis Pub/Sub for invalidation
// 6. Stampede: Lock + early expiration Quiz
1. What is the cache-aside pattern?
2. What is the main risk of caching?
3. What is cache stampede?
4. How to prevent cache stampede?
Flashcards
Question
Cache-aside pattern?
Click to reveal answer
Answer
Read cache → miss → load DB → save cache
Question
Write-through pattern?
Click to reveal answer
Answer
Write DB → write cache simultaneously
Question
Cache stampede?
Click to reveal answer
Answer
Many requests hit DB when cache expires simultaneously
Question
Prevent cache stampede?
Click to reveal answer
Answer
Locking + early expiration + queue refresh
Question
Price data cache strategy?
Click to reveal answer
Answer
Short TTL (60s) + event invalidation on change
Revision Notes
Key Takeaways
- 1. Cache-aside: Read cache first, load from DB on miss
- 2. Write-through: Write DB and cache together
- 3. TTL-based: Simple but may serve stale data
- 4. Event-based: Immediate invalidation on data change
- 5. Cache stampede: Use locking and early expiration
Interview Tips
- • Explain cache-aside vs write-through patterns
- • Discuss stale data risks and solutions
- • Know cache invalidation strategies
- • Understand cache stampede prevention
Cheat Sheet
Cache vs DB
- Cache-aside: Cache→miss→DB→cache
- Write-through: DB+cache together
- TTL: Simple, may be stale
- Event: Immediate invalidation
- Version: Bulk invalidation
- Stampede: Lock + early expiration
- Price: 60s TTL + event invalidation