Skip to content
advanced Phase 101 · Trade-offs

Redis vs Database Caching

45m
2 problems
Topic Progress 0%

Redis vs Database Caching

Caching Layers in Magento 2

Application Layer
  ├── Full Page Cache (Redis/Varnish)
  ├── Block Cache (Redis)
  ├── Configuration Cache (Redis)
  └── Query Cache (Database)
      └── Redis (istributed cache)

Redis Caching

// Redis configuration in env.php
'redis' => [
    'host' => '127.0.0.1',
    'port' => '6379',
    'database' => '0',
    'password' => '',
    'timeout' => '2.5',
    'persistent' => true,
],

// Cache implementation
$cache = $this->cache->load('catalog_product_' . $productId);
if ($cache) {
    return unserialize($cache);
}
// Fallback to database
$product = $this->productRepository->getById($productId);
$this->cache->save(serialize($product), 'catalog_product_' . $productId, [], 3600);

Database Query Caching

-- MySQL query cache (deprecated in 8.0)
SELECT SQL_CACHE * FROM catalog_product_entity WHERE entity_id = 123;

-- Using temporary tables for complex queries
CREATE TEMPORARY TABLE tmp_product_data AS
SELECT p.*, pe.*
FROM catalog_product_entity p
JOIN catalog_product_entity_varchar pe ON p.entity_id = pe.entity_id
WHERE pe.attribute_id = 71; -- name attribute

Performance Comparison

Response Time Comparison

Operation Redis MySQL
Simple GET 0.1-0.5ms 1-10ms
Complex query N/A 10-500ms
Serialization 0.1ms N/A
Network overhead 0.2ms 0.5ms

Throughput

// Redis: ~100,000 operations/sec
// MySQL: ~1,000-10,000 queries/sec
// Redis cluster: ~500,000 operations/sec

// Benchmark example
$iterations = 10000;

$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
    $redis->get('product_' . $i);
}
$redisTime = microtime(true) - $start;

$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
    $db->query('SELECT * FROM catalog_product_entity WHERE entity_id = ' . $i);
}
$dbTime = microtime(true) - $start;

Memory Considerations

// Redis memory usage
$redis->set('product_123', serialize($productData));
$memoryUsed = $redis->info()['used_memory'];

// Database: disk-based, no memory limit for reads
// Redis: RAM-based, limited by server memory
// Recommendation: Use Redis for hot data, DB for cold storage

When to Use Each

Use Redis When:

  • Data is frequently accessed (hot data)
  • Response time is critical (< 5ms needed)
  • Data is small and fits in memory
  • Multiple reads per write
  • Session data, configuration, cart data

Use Database When:

  • Data is large and rarely accessed (cold data)
  • Complex queries with joins/aggregations
  • Data requires ACID transactions
  • Full-text search needed
  • Reporting and analytics queries

Hybrid Approach (Recommended)

// Read-through cache pattern
public function getProduct($id)
{
    $cacheKey = 'product_' . $id;
    $cached = $this->redis->get($cacheKey);
    
    if ($cached !== false) {
        return unserialize($cached);
    }
    
    $product = $this->db->fetchRow(
        'SELECT * FROM catalog_product_entity WHERE entity_id = ?',
        [$id]
    );
    
    $this->redis->setex($cacheKey, 3600, serialize($product));
    return $product;
}

// Cache-aside with TTL
public function getCategoryProducts($categoryId)
{
    $cacheKey = 'category_' . $categoryId . '_products';
    $cached = $this->redis->get($cacheKey);
    
    if ($cached !== false) {
        return unserialize($cached);
    }
    
    $products = $this->db->fetchAll(
        'SELECT * FROM catalog_category_product WHERE category_id = ?',
        [$categoryId]
    );
    
    $this->redis->setex($cacheKey, 1800, serialize($products));
    return $products;
}

Consistency Trade-offs

Cache Invalidation Strategies

// 1. Time-based expiration (TTL)
$this->redis->setex($key, 3600, $data);

// 2. Event-based invalidation
public function onProductSave($observer)
{
    $productId = $observer->getEvent()->getProduct()->getId();
    $this->redis->del('product_' . $productId);
}

// 3. Write-through cache
public function saveProduct($product)
{
    $this->db->save($product);
    $this->redis->setex('product_' . $product->getId(), 3600, serialize($product));
}

// 4. Versioned cache keys
$version = $this->config->get('catalog_version');
$cacheKey = 'product_' . $id . '_v' . $version;

Stale Data Risks

Scenario: Product price updated
├── Redis cached: $29.99 (5 min old)
├── Database: $34.99 (new price)
└── Risk: Customer sees old price

Solutions:
1. Short TTL for price data (60 seconds)
2. Price change event triggers cache invalidation
3. Checkout always reads from database
4. Price versioning with cache invalidation

Distribution Challenges

// Multi-server cache consistency
$redis1 = new Redis(); // Server 1
$redis2 = new Redis(); // Server 2

// Problem: Cache invalidation on one server
// doesn't affect other servers

// Solution: Redis Pub/Sub for invalidation
$redis1->publish('cache_invalidation', $key);
$redis2->subscribe(['cache_invalidation'], function($redis, $channel, $key) {
    $redis->del($key);
});

Practice Problems

0 / 2 solved
Design Product Caching Strategy

Design a caching strategy for a product catalog with 1M products, 10K updates/hour, and 100K reads/second.

Solution
// Strategy:
// 1. Hot products (top 10%): Redis with 5-min TTL
// 2. Warm products: Redis with 60-min TTL
// 3. Cold products: Database only
// 4. Invalidation: Event-based on save
// 5. Warm-up: Pre-load on deploy
Cache Invalidation System

Implement a cache invalidation system that works across multiple Redis instances.

Solution
// Implementation:
// 1. Version counter in database
// 2. Cache key includes version: product_123_v5
// 3. On update: increment version, publish invalidation
// 4. On receive: delete local cache
// 5. Fallback: TTL-based expiration

Quiz

1. Redis is best suited for which type of data?

Question 1 options

2. What is the typical response time difference between Redis and MySQL?

Question 2 options

3. Which cache invalidation strategy is most reliable for price changes?

Question 3 options

4. What is the main limitation of Redis caching?

Question 4 options

Flashcards

Question

Redis typical response time?

Answer

0.1-0.5ms for simple GET operations

Question

MySQL typical query time?

Answer

1-10ms for simple queries, up to 500ms for complex

Question

Best cache invalidation for prices?

Answer

Event-based invalidation on price save event

Question

Redis main limitation?

Answer

Limited by available RAM memory

Question

Recommended pattern for product cache?

Answer

Read-through cache with TTL and event-based invalidation

Revision Notes

Key Takeaways

  • 1. Redis: 0.1-0.5ms response, ~100K ops/sec, RAM-limited
  • 2. MySQL: 1-10ms response, ~1-10K queries/sec, disk-based
  • 3. Use Redis for hot data, MySQL for cold/complex queries
  • 4. Event-based invalidation is most reliable
  • 5. Hybrid approach: Redis for reads, DB as source of truth

Interview Tips

  • Compare response times and throughput metrics
  • Explain cache invalidation strategies
  • Discuss consistency vs performance trade-offs
  • Know when to use each caching layer

Cheat Sheet

Redis vs DB

  • Redis: 0.1-0.5ms, 100K ops/sec, RAM
  • MySQL: 1-10ms, 1-10K qps, Disk
  • Hot data → Redis, Cold → MySQL
  • Invalidation: Event-based > TTL
  • Checkout: Always read from DB