Skip to content
advanced Phase 102 · More Trade-offs

Cache vs Database

45m
1 problems
Topic Progress 0%

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

0 / 1 solved
Cache Invalidation System

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?

Question 1 options

2. What is the main risk of caching?

Question 2 options

3. What is cache stampede?

Question 3 options

4. How to prevent cache stampede?

Question 4 options

Flashcards

Question

Cache-aside pattern?

Answer

Read cache → miss → load DB → save cache

Question

Write-through pattern?

Answer

Write DB → write cache simultaneously

Question

Cache stampede?

Answer

Many requests hit DB when cache expires simultaneously

Question

Prevent cache stampede?

Answer

Locking + early expiration + queue refresh

Question

Price data cache strategy?

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