Redis & Advanced Caching

Redis Data Types: Hashes and Sorted Sets

20 min Lesson 5 of 30

Redis Hashes

Redis hashes are maps between string fields and string values, similar to objects or dictionaries in programming languages. Hashes are perfect for representing objects like users, products, or any structured data with multiple attributes.

Memory Efficient: Hashes are optimized to use very little memory when storing small objects. A hash with a few fields uses less memory than storing each field as a separate string key.

HSET and HGET - Basic Hash Operations

Set and get individual fields in a hash:

// HSET - Set field in hash\n127.0.0.1:6379> HSET user:1000 name "Edrees Salih"\n(integer) 1\n\n127.0.0.1:6379> HSET user:1000 email "edrees@example.com"\n(integer) 1\n\n127.0.0.1:6379> HSET user:1000 age "30"\n(integer) 1\n\n// HGET - Get field from hash\n127.0.0.1:6379> HGET user:1000 name\n"Edrees Salih"\n\n127.0.0.1:6379> HGET user:1000 email\n"edrees@example.com"\n\n// Get non-existent field returns nil\n127.0.0.1:6379> HGET user:1000 phone\n(nil)
// Laravel examples\n// Store user profile\nRedis::hset('user:1000', 'name', 'Edrees Salih');\nRedis::hset('user:1000', 'email', 'edrees@example.com');\nRedis::hset('user:1000', 'role', 'admin');\n\n// Get specific field\n$userName = Redis::hget('user:1000', 'name');\necho "User: {$userName}";

HMSET and HMGET - Multiple Fields

Set or get multiple fields at once for better performance:

// HMSET - Set multiple fields (deprecated in Redis 4.0, use HSET)\n127.0.0.1:6379> HMSET product:500 name "Laptop" price "999.99" stock "50" category "electronics"\nOK\n\n// Modern HSET accepts multiple field-value pairs\n127.0.0.1:6379> HSET product:501 name "Mouse" price "29.99" stock "200"\n(integer) 3\n\n// HMGET - Get multiple fields\n127.0.0.1:6379> HMGET product:500 name price stock\n1) "Laptop"\n2) "999.99"\n3) "50"
// Laravel - Store entire object\n$productData = [\n 'name' => 'Gaming Keyboard',\n 'price' => '149.99',\n 'stock' => '75',\n 'category' => 'electronics',\n 'brand' => 'Razer'\n];\n\nRedis::hmset('product:502', $productData);\n\n// Get multiple fields\n$fields = Redis::hmget('product:502', ['name', 'price', 'stock']);\nlist($name, $price, $stock) = $fields;

HGETALL - Get All Fields

Retrieve all fields and values from a hash:

// HGETALL - Get all fields and values\n127.0.0.1:6379> HGETALL user:1000\n1) "name"\n2) "Edrees Salih"\n3) "email"\n4) "edrees@example.com"\n5) "age"\n6) "30"\n\n// Empty or non-existent hash returns empty array\n127.0.0.1:6379> HGETALL nonexistent\n(empty array)
// Laravel - Get complete object\n$userData = Redis::hgetall('user:1000');\n\n// Returns associative array:\n// [\n// 'name' => 'Edrees Salih',\n// 'email' => 'edrees@example.com',\n// 'age' => '30'\n// ]\n\n// Use in views\nreturn view('profile', ['user' => $userData]);
Performance Warning: HGETALL is O(N) where N is the hash size. Avoid using HGETALL on very large hashes (>1000 fields). Use HSCAN for large hashes or get only needed fields with HMGET.

HDEL - Delete Fields

Remove one or more fields from a hash:

// HDEL - Delete fields\n127.0.0.1:6379> HSET user:1000 temp_token "abc123"\n(integer) 1\n\n127.0.0.1:6379> HDEL user:1000 temp_token\n(integer) 1 // 1 = field deleted\n\n127.0.0.1:6379> HDEL user:1000 temp_token\n(integer) 0 // 0 = field didn't exist\n\n// Delete multiple fields\n127.0.0.1:6379> HDEL user:1000 age phone\n(integer) 2
// Laravel - Clear sensitive data\nRedis::hdel('user:1000', 'password_reset_token');\n\n// Remove multiple temporary fields\nRedis::hdel('session:abc123', 'csrf_token', 'temp_data', 'flash_message');

Hash Helper Commands

// HEXISTS - Check if field exists\n127.0.0.1:6379> HEXISTS user:1000 email\n(integer) 1 // 1 = exists\n\n127.0.0.1:6379> HEXISTS user:1000 phone\n(integer) 0 // 0 = doesn't exist\n\n// HKEYS - Get all field names\n127.0.0.1:6379> HKEYS user:1000\n1) "name"\n2) "email"\n3) "age"\n\n// HVALS - Get all values\n127.0.0.1:6379> HVALS user:1000\n1) "Edrees Salih"\n2) "edrees@example.com"\n3) "30"\n\n// HLEN - Get number of fields\n127.0.0.1:6379> HLEN user:1000\n(integer) 3
// Laravel examples\n// Check if field exists\nif (Redis::hexists('user:1000', 'premium_until')) {\n // User has premium subscription\n}\n\n// Get all field names\n$fields = Redis::hkeys('product:500');\n// Returns: ['name', 'price', 'stock', 'category']\n\n// Count fields\n$fieldCount = Redis::hlen('user:1000');

HINCRBY - Increment Hash Fields

Atomically increment numeric fields in a hash:

// HINCRBY - Increment by integer\n127.0.0.1:6379> HSET product:500 views 0\n(integer) 1\n\n127.0.0.1:6379> HINCRBY product:500 views 1\n(integer) 1\n\n127.0.0.1:6379> HINCRBY product:500 views 5\n(integer) 6\n\n// Decrement with negative value\n127.0.0.1:6379> HINCRBY product:500 stock -1\n(integer) 49\n\n// HINCRBYFLOAT - Increment by float\n127.0.0.1:6379> HSET product:500 rating 4.5\n(integer) 1\n\n127.0.0.1:6379> HINCRBYFLOAT product:500 rating 0.3\n"4.8"
// Laravel - Track statistics\n// Product views counter\nRedis::hincrby('product:500', 'views', 1);\n\n// Decrease stock on purchase\nRedis::hincrby('product:500', 'stock', -$quantity);\n\n// Update average rating\n$newRating = 4.8;\nRedis::hset('product:500', 'rating', $newRating);\nRedis::hincrby('product:500', 'rating_count', 1);

Practical Hash Use Cases

// 1. User session storage\nclass RedisSessionHandler {\n public function create($sessionId, $userId, $data) {\n Redis::hmset("session:{$sessionId}", [\n 'user_id' => $userId,\n 'ip_address' => request()->ip(),\n 'user_agent' => request()->userAgent(),\n 'created_at' => time(),\n 'data' => serialize($data)\n ]);\n Redis::expire("session:{$sessionId}", 7200); // 2 hours\n }\n \n public function getData($sessionId) {\n return Redis::hgetall("session:{$sessionId}");\n }\n}\n\n// 2. Product catalog caching\nclass ProductCache {\n public function cacheProduct($product) {\n Redis::hmset("product:{$product->id}", [\n 'name' => $product->name,\n 'price' => $product->price,\n 'stock' => $product->stock,\n 'description' => $product->description,\n 'category' => $product->category,\n 'updated_at' => $product->updated_at\n ]);\n Redis::expire("product:{$product->id}", 3600);\n }\n \n public function getProduct($productId) {\n $cached = Redis::hgetall("product:{$productId}");\n return $cached ?: Product::find($productId);\n }\n}

Redis Sorted Sets

Sorted sets are collections of unique strings (members) where each member is associated with a score. Members are ordered by score, making sorted sets perfect for leaderboards, rankings, and priority queues.

Unique Feature: Sorted sets combine the uniqueness of sets with ordered access by score. They're implemented using both a hash table and a skip list, providing O(log N) operations.

ZADD - Add Members with Scores

// ZADD - Add members with scores\n127.0.0.1:6379> ZADD leaderboard 100 "player1"\n(integer) 1\n\n127.0.0.1:6379> ZADD leaderboard 250 "player2" 180 "player3"\n(integer) 2\n\n// Update score (member already exists)\n127.0.0.1:6379> ZADD leaderboard 300 "player1"\n(integer) 0 // 0 = member updated, not added\n\n// NX flag - only add if not exists\n127.0.0.1:6379> ZADD leaderboard NX 400 "player1"\n(integer) 0 // Failed - member exists\n\n// XX flag - only update if exists\n127.0.0.1:6379> ZADD leaderboard XX 350 "player1"\n(integer) 0 // Updated existing member
// Laravel - Gaming leaderboard\nRedis::zadd('game:leaderboard', 1500, 'user:1000');\nRedis::zadd('game:leaderboard', 2300, 'user:1001');\nRedis::zadd('game:leaderboard', 1800, 'user:1002');\n\n// Update player score\n$userId = 1000;\n$newScore = 2500;\nRedis::zadd('game:leaderboard', $newScore, "user:{$userId}");

ZRANGE and ZREVRANGE - Get Ranges

Retrieve members in order by score (ascending or descending):

// ZRANGE - Get members by rank (ascending)\n127.0.0.1:6379> ZADD scores 10 "Alice" 20 "Bob" 15 "Charlie" 25 "David"\n(integer) 4\n\n127.0.0.1:6379> ZRANGE scores 0 -1\n1) "Alice" // score: 10\n2) "Charlie" // score: 15\n3) "Bob" // score: 20\n4) "David" // score: 25\n\n// ZRANGE with scores\n127.0.0.1:6379> ZRANGE scores 0 -1 WITHSCORES\n1) "Alice"\n2) "10"\n3) "Charlie"\n4) "15"\n5) "Bob"\n6) "20"\n7) "David"\n8) "25"\n\n// ZREVRANGE - Get members in reverse order (descending)\n127.0.0.1:6379> ZREVRANGE scores 0 2\n1) "David" // Highest score first\n2) "Bob"\n3) "Charlie"\n\n// Get top 3\n127.0.0.1:6379> ZREVRANGE scores 0 2 WITHSCORES\n1) "David"\n2) "25"\n3) "Bob"\n4) "20"\n5) "Charlie"\n6) "15"
// Laravel - Get top 10 players\n$topPlayers = Redis::zrevrange('game:leaderboard', 0, 9, 'WITHSCORES');\n\n// Format for display\n$leaderboard = [];\nfor ($i = 0; $i < count($topPlayers); $i += 2) {\n $leaderboard[] = [\n 'player' => $topPlayers[$i],\n 'score' => $topPlayers[$i + 1]\n ];\n}\n\n// Get bottom performers\n$bottomPlayers = Redis::zrange('game:leaderboard', 0, 9, 'WITHSCORES');

ZSCORE and ZRANK - Get Score and Rank

// ZSCORE - Get member's score\n127.0.0.1:6379> ZSCORE scores "Bob"\n"20"\n\n127.0.0.1:6379> ZSCORE scores "NonExistent"\n(nil)\n\n// ZRANK - Get member's rank (0-based, ascending)\n127.0.0.1:6379> ZRANK scores "Bob"\n(integer) 2 // 3rd position (Alice, Charlie, Bob)\n\n// ZREVRANK - Get reverse rank (descending)\n127.0.0.1:6379> ZREVRANK scores "Bob"\n(integer) 1 // 2nd from top
// Laravel - Show player stats\n$userId = 1000;\n$score = Redis::zscore('game:leaderboard', "user:{$userId}");\n$rank = Redis::zrevrank('game:leaderboard', "user:{$userId}");\n\nif ($score !== null) {\n echo "Your score: {$score}<br>";\n echo "Your rank: " . ($rank + 1) . " of " . Redis::zcard('game:leaderboard');\n}

ZRANGEBYSCORE - Range by Score

Get members within a score range:

// ZRANGEBYSCORE - Get members with scores between min and max\n127.0.0.1:6379> ZRANGEBYSCORE scores 15 25\n1) "Charlie" // score: 15\n2) "Bob" // score: 20\n3) "David" // score: 25\n\n// Exclusive range with ( prefix\n127.0.0.1:6379> ZRANGEBYSCORE scores (15 25\n1) "Bob" // 15 is excluded\n2) "David"\n\n// Infinite ranges\n127.0.0.1:6379> ZRANGEBYSCORE scores -inf 20\n1) "Alice"\n2) "Charlie"\n3) "Bob"\n\n// With limit (offset and count)\n127.0.0.1:6379> ZRANGEBYSCORE scores 0 100 LIMIT 0 2\n1) "Alice"\n2) "Charlie"
// Laravel - Get players in score range\n// Players with 1000-2000 points\n$midTierPlayers = Redis::zrangebyscore(\n 'game:leaderboard',\n 1000,\n 2000,\n ['withscores' => true]\n);\n\n// Get today's posts by timestamp\n$todayStart = strtotime('today');\n$todayEnd = strtotime('tomorrow');\n$todayPosts = Redis::zrangebyscore(\n 'posts:timeline',\n $todayStart,\n $todayEnd\n);

ZINCRBY and ZREM - Modify Sorted Sets

// ZINCRBY - Increment member score\n127.0.0.1:6379> ZADD counters 10 "page_views"\n(integer) 1\n\n127.0.0.1:6379> ZINCRBY counters 5 "page_views"\n"15"\n\n127.0.0.1:6379> ZINCRBY counters -2 "page_views"\n"13"\n\n// ZREM - Remove members\n127.0.0.1:6379> ZREM scores "Alice"\n(integer) 1 // 1 = member removed\n\n127.0.0.1:6379> ZREM scores "Alice"\n(integer) 0 // 0 = member didn't exist\n\n// ZCARD - Get number of members\n127.0.0.1:6379> ZCARD scores\n(integer) 3
// Laravel - Increment player score\nRedis::zincrby('game:leaderboard', 100, "user:{$userId}"); // Add 100 points\n\n// Remove banned player\nRedis::zrem('game:leaderboard', "user:{$bannedUserId}");\n\n// Get total players\n$totalPlayers = Redis::zcard('game:leaderboard');

Advanced Sorted Set Use Cases

// 1. Trending posts with decay\nclass TrendingPosts {\n public function addView($postId) {\n $score = time(); // Use timestamp as score\n Redis::zadd('posts:trending', $score, $postId);\n }\n \n public function getTrending($hours = 24) {\n $cutoff = time() - ($hours * 3600);\n // Get posts from last 24 hours\n return Redis::zrevrangebyscore(\n 'posts:trending',\n '+inf',\n $cutoff,\n ['limit' => [0, 10]]\n );\n }\n \n public function cleanup() {\n $weekAgo = time() - (7 * 24 * 3600);\n // Remove posts older than 1 week\n Redis::zremrangebyscore('posts:trending', '-inf', $weekAgo);\n }\n}\n\n// 2. Priority queue\nclass PriorityQueue {\n public function enqueue($jobId, $priority) {\n // Higher priority = higher score\n Redis::zadd('jobs:queue', $priority, $jobId);\n }\n \n public function dequeue() {\n // Get and remove highest priority job\n $jobs = Redis::zrevrange('jobs:queue', 0, 0);\n if (!empty($jobs)) {\n $jobId = $jobs[0];\n Redis::zrem('jobs:queue', $jobId);\n return $jobId;\n }\n return null;\n }\n}
When to Use Each Data Type:
  • Strings: Simple key-value, counters, caching
  • Lists: Queues, activity feeds, recent items
  • Sets: Tags, unique tracking, set operations
  • Hashes: Objects with fields, structured data
  • Sorted Sets: Leaderboards, rankings, time-series
Exercise: Build a complete blog post ranking system:
  1. Use sorted sets to track posts by: view count, like count, and recency
  2. Use hashes to store post metadata (title, author, category)
  3. Implement "trending" algorithm combining views and recency
  4. Create leaderboard showing top 10 posts by each metric
  5. Add function to get user's post rank and score
  6. Implement automatic cleanup of old posts from trending

Congratulations! You now understand all major Redis data types and their use cases. In future lessons, we'll explore Redis clustering, persistence, Lua scripting, and integration with Laravel queues and caching.