DEV Community

ahmet gedik
ahmet gedik

Posted on

Redis Caching Strategies for Video Content Platforms

A video platform that pulls content from seven European regions needs aggressive caching or it folds under its own weight. ViralVidVault curates viral videos from across Europe, and the caching architecture went through several iterations. Here's what stuck.

Why Not Just Use File Cache?

File-based caching works for single-server setups, and it's what ViralVidVault started with. But it has limits:

  • No atomic operations (race conditions on cache writes)
  • No built-in expiry (you manage TTLs yourself)
  • No data structure support (everything is serialize/unserialize)

Redis solves all three while adding sub-millisecond reads.

Data Structures That Match the Domain

The trick with Redis is picking the right data structure for each access pattern.

Hashes for Video Metadata

Each video is a hash with individual fields you can read or update independently:

function cacheVideoMeta(Redis $redis, array $video): void {
    $key = "vid:{$video['id']}";
    $redis->hMSet($key, [
        'title'     => $video['title'],
        'channel'   => $video['channel_title'],
        'thumb'     => $video['thumbnail_url'],
        'views'     => $video['view_count'],
        'region'    => $video['region'],
        'fetched'   => time(),
    ]);
    $redis->expire($key, 21600); // 6h TTL
}

// Update just the view count without touching other fields
function updateViewCount(Redis $redis, string $videoId, int $views): void {
    $redis->hSet("vid:{$videoId}", 'views', $views);
}
Enter fullscreen mode Exit fullscreen mode

Compare this to storing the whole video as a JSON string — updating a single field means deserializing, modifying, and reserializing. Hashes avoid that entirely.

Sorted Sets for Regional Trending

ViralVidVault serves different trending feeds per European region. Sorted sets keep videos ranked by a score you define:

function rebuildRegionalTrending(Redis $redis, PDO $db, string $region): void {
    $key = "trending:{$region}";
    $redis->del($key); // Clear stale data

    $stmt = $db->prepare(
        "SELECT video_id, view_count, published_at FROM videos
         WHERE region = ? AND published_at > datetime('now', '-48 hours')
         ORDER BY view_count DESC LIMIT 100"
    );
    $stmt->execute([$region]);

    $pipe = $redis->pipeline();
    while ($row = $stmt->fetch()) {
        // Score: views weighted by recency
        $age = time() - strtotime($row['published_at']);
        $score = $row['view_count'] / max($age / 3600, 1);
        $pipe->zAdd($key, $score, $row['video_id']);
    }
    $pipe->exec();
    $redis->expire($key, 10800); // 3h TTL
}

// Fetch top trending for display
function getRegionalTrending(Redis $redis, string $region, int $count = 20): array {
    return $redis->zRevRange("trending:{$region}", 0, $count - 1, true);
}
Enter fullscreen mode Exit fullscreen mode

The score formula (views divided by age in hours) naturally pushes fresh viral content above old high-view videos. No application-level sorting needed.

Search Cache with Short TTL

Search results change frequently as new content arrives. Use plain string keys with aggressive TTLs:

function cacheSearchResults(Redis $redis, string $query, array $results): void {
    $key = 'search:' . md5(mb_strtolower(trim($query)));
    $redis->setex($key, 600, json_encode($results)); // 10 minutes
}

function getCachedSearch(Redis $redis, string $query): ?array {
    $key = 'search:' . md5(mb_strtolower(trim($query)));
    $data = $redis->get($key);
    return $data ? json_decode($data, true) : null;
}
Enter fullscreen mode Exit fullscreen mode

Ten minutes is the sweet spot. Long enough to absorb repeated searches (people often search the same terms), short enough that new content appears promptly.

Page Cache in Redis

Full HTML page cache for category and home pages eliminates PHP execution entirely on cache hits:

function serveFromCache(Redis $redis, string $uri): bool {
    $key = 'page:' . md5($uri);
    $html = $redis->get($key);
    if ($html !== false) {
        header('X-Cache: HIT');
        echo $html;
        return true;
    }
    return false;
}

function cachePage(Redis $redis, string $uri, string $html, int $ttl = 10800): void {
    $key = 'page:' . md5($uri);
    $redis->setex($key, $ttl, $html);
}
Enter fullscreen mode Exit fullscreen mode

On ViralVidVault, page cache hits serve in under 5ms. Without cache, a category page with 30 videos takes 80-150ms.

Cache Invalidation Strategy

Two invalidation triggers:

  1. Cron-driven — When the fetcher pulls new videos for a region, bust that region's caches
  2. TTL-driven — Let short-lived caches expire naturally
function invalidateRegion(Redis $redis, string $region): void {
    $pipe = $redis->pipeline();
    // Clear trending for this region
    $pipe->del("trending:{$region}");
    // Clear home page (contains cross-region trending)
    $pipe->del('page:' . md5('/'));
    // Category pages for this region
    foreach (getActiveCategories() as $cat) {
        $pipe->del('page:' . md5("/category/{$cat}"));
    }
    $pipe->exec();
}
Enter fullscreen mode Exit fullscreen mode

Search cache is deliberately left alone — its 10-minute TTL handles staleness. Explicitly invalidating search during a fetch would cause a thundering herd as dozens of users re-execute their queries simultaneously.

Memory Budget

# redis.conf
maxmemory 192mb
maxmemory-policy allkeys-lru
Enter fullscreen mode Exit fullscreen mode

192MB handles the full cache set comfortably. Video metadata hashes are small (under 500 bytes each), page caches are compressed HTML, and sorted sets are just ID-score pairs. LRU eviction means cold data drops automatically without manual cleanup.

TTL Reference

Layer TTL Why
Video metadata hashes 6h Refreshed by cron fetch
Regional trending sets 3h Rebuilt after each fetch
Page cache (home/category) 3h Balances freshness vs load
Search results 10min Users expect near-real-time
Channel data 24h Rarely changes

The pattern is simple: the more user-facing the data, the shorter the TTL. Backend data that users don't see directly can afford to be stale longer.


This article is part of the Building ViralVidVault series.

Top comments (0)