
Setting Up Redis as a Cache for MySQL with PHP on Ubuntu 24
Caching is one of those things that can make or break your web application’s performance, especially when you’re dealing with database-heavy operations. Redis, being an in-memory data structure store, paired with MySQL and PHP creates a powerful combo that can dramatically reduce your database load and response times. In this guide, we’ll walk through setting up Redis as a cache layer for MySQL on Ubuntu 24, covering everything from installation to real-world implementation strategies that actually work in production environments.
How Redis Caching Works with MySQL
Redis operates as a key-value store that sits between your PHP application and MySQL database. When your application requests data, it first checks Redis. If the data exists (cache hit), it returns immediately. If not (cache miss), the application queries MySQL, stores the result in Redis for future requests, then returns the data.
The magic happens because Redis stores everything in RAM, making read operations incredibly fast – we’re talking microseconds instead of milliseconds. This is particularly effective for:
- Frequently accessed user data
- Complex query results that don’t change often
- Session data and user preferences
- Product catalogs and pricing information
- API response caching
Installation and Initial Setup
Let’s get Redis and the necessary PHP extensions installed on Ubuntu 24. First, update your system and install Redis:
sudo apt update
sudo apt install redis-server php-redis php-mysql
Start and enable Redis to run on boot:
sudo systemctl start redis-server
sudo systemctl enable redis-server
Verify Redis is running:
redis-cli ping
You should get a “PONG” response. Now let’s configure Redis for optimal performance. Edit the Redis configuration:
sudo nano /etc/redis/redis.conf
Key settings to modify:
# Set maximum memory (adjust based on your server)
maxmemory 256mb
# Use allkeys-lru eviction policy
maxmemory-policy allkeys-lru
# Enable persistence (optional, but recommended)
save 900 1
save 300 10
save 60 10000
Restart Redis to apply changes:
sudo systemctl restart redis-server
PHP Integration and Cache Implementation
Now let’s create a PHP cache class that handles Redis operations with MySQL fallback. This approach gives you flexibility and error handling:
redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
// Initialize MySQL connection
$this->mysql = new PDO(
"mysql:host={$mysqlConfig['host']};dbname={$mysqlConfig['db']}",
$mysqlConfig['user'],
$mysqlConfig['pass']
);
$this->mysql->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public function get($key, $query = null, $params = [], $ttl = null) {
$ttl = $ttl ?: $this->defaultTTL;
try {
// Try Redis first
$cached = $this->redis->get($key);
if ($cached !== false) {
return json_decode($cached, true);
}
} catch (Exception $e) {
error_log("Redis error: " . $e->getMessage());
}
// Cache miss or Redis unavailable - query MySQL
if ($query) {
$stmt = $this->mysql->prepare($query);
$stmt->execute($params);
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Store in Redis for next time
try {
$this->redis->setex($key, $ttl, json_encode($result));
} catch (Exception $e) {
error_log("Redis set error: " . $e->getMessage());
}
return $result;
}
return null;
}
public function set($key, $data, $ttl = null) {
$ttl = $ttl ?: $this->defaultTTL;
try {
return $this->redis->setex($key, $ttl, json_encode($data));
} catch (Exception $e) {
error_log("Redis set error: " . $e->getMessage());
return false;
}
}
public function delete($key) {
try {
return $this->redis->del($key);
} catch (Exception $e) {
error_log("Redis delete error: " . $e->getMessage());
return false;
}
}
public function flush() {
try {
return $this->redis->flushAll();
} catch (Exception $e) {
error_log("Redis flush error: " . $e->getMessage());
return false;
}
}
}
Real-World Implementation Examples
Let’s look at practical examples that you’ll actually use in production applications.
User Profile Caching
'localhost',
'db' => 'myapp',
'user' => 'dbuser',
'pass' => 'dbpass'
]);
function getUserProfile($userId) {
global $cache;
$key = "user_profile:{$userId}";
$query = "SELECT id, username, email, avatar, last_login, preferences
FROM users WHERE id = ?";
return $cache->get($key, $query, [$userId], 1800); // 30 min cache
}
// Usage
$user = getUserProfile(123);
echo "Welcome back, " . $user[0]['username'];
Product Catalog with Categories
get($key, $query, [$categoryId, $limit, $offset], 600); // 10 min cache
}
// Invalidate cache when products are updated
function updateProduct($productId, $data) {
global $cache;
// Update database
// ... database update logic ...
// Clear related caches
$product = getProductById($productId);
$categoryId = $product['category_id'];
// Clear category-based caches (simplified - in production, use more sophisticated cache tagging)
$cache->delete("products:category:{$categoryId}:*");
$cache->delete("product:{$productId}");
}
API Response Caching
= DATE_SUB(NOW(), INTERVAL 30 DAY)
AND o.status = 'completed'
GROUP BY p.id
ORDER BY total_sold DESC
LIMIT 50";
return $cache->get($key, $query, [], 3600); // 1 hour cache for analytics
}
Performance Comparison and Benchmarks
Here’s what you can expect in terms of performance improvements with Redis caching:
Operation Type | MySQL Only (ms) | Redis Cache Hit (ms) | Improvement |
---|---|---|---|
Simple user lookup | 15-25 | 0.5-2 | 10-50x faster |
Complex analytics query | 200-500 | 1-3 | 100-500x faster |
Product catalog (50 items) | 50-100 | 2-5 | 20-50x faster |
User session data | 10-20 | 0.3-1 | 20-60x faster |
Cache Strategies and Best Practices
Different caching strategies work better for different use cases. Here’s a breakdown:
Strategy | Best For | Cache TTL | Invalidation Method |
---|---|---|---|
Cache-Aside | User profiles, product data | 30 min – 2 hours | Manual on updates |
Write-Through | Critical data consistency | Long (6-24 hours) | Automatic |
Write-Behind | High-write scenarios | Variable | Batch processing |
Refresh-Ahead | Predictable access patterns | Just before expiry | Proactive refresh |
Key Naming Conventions
Consistent key naming makes cache management much easier:
# User-related data
user:profile:{user_id}
user:settings:{user_id}
user:sessions:{user_id}
# Product catalog
product:{product_id}
products:category:{category_id}:page:{page_num}
products:search:{query_hash}
# Analytics and reports
analytics:daily:{date}
reports:sales:{period}:{filters_hash}
Monitoring and Troubleshooting
Create a simple monitoring script to keep track of cache performance:
connect('127.0.0.1', 6379);
$info = $redis->info();
return [
'connected_clients' => $info['connected_clients'],
'used_memory_human' => $info['used_memory_human'],
'used_memory_peak_human' => $info['used_memory_peak_human'],
'keyspace_hits' => $info['keyspace_hits'],
'keyspace_misses' => $info['keyspace_misses'],
'hit_rate' => round(($info['keyspace_hits'] / ($info['keyspace_hits'] + $info['keyspace_misses'])) * 100, 2)
];
}
// Monitor cache hit rate
$stats = getCacheStats();
if ($stats['hit_rate'] < 80) {
error_log("Cache hit rate is low: {$stats['hit_rate']}%");
}
Common Issues and Solutions
- Redis connection timeouts: Increase timeout values in php.ini or use connection pooling
- Memory eviction: Monitor memory usage and adjust maxmemory settings
- Cache stampede: Implement cache locking for expensive operations
- Stale data: Use appropriate TTL values and implement proper cache invalidation
Advanced Configuration and Security
For production environments, secure your Redis installation:
# /etc/redis/redis.conf
# Disable dangerous commands
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG ""
# Enable authentication
requirepass your_strong_password_here
# Bind to specific interface
bind 127.0.0.1
# Enable logging
logfile /var/log/redis/redis-server.log
loglevel notice
Update your PHP code to use authentication:
connect('127.0.0.1', 6379);
$redis->auth('your_strong_password_here');
Alternative Caching Solutions
While Redis is excellent, here's how it compares to other caching solutions:
Solution | Memory Usage | Persistence | Data Types | Best Use Case |
---|---|---|---|---|
Redis | Moderate | Optional | Rich (strings, lists, sets, etc.) | Complex caching, sessions, real-time |
Memcached | Low | None | Key-value only | Simple caching, high concurrency |
APCu | Very low | None | PHP variables | Single-server PHP opcode cache |
File-based | None (disk) | Built-in | Any serializable | Low-traffic sites, shared hosting |
For most web applications dealing with MySQL, Redis offers the best balance of performance, features, and reliability. The rich data types and pub/sub capabilities make it particularly valuable for modern web applications that need more than simple key-value caching.
Check out the official Redis documentation at https://redis.io/documentation for more advanced configuration options and the PHP Redis extension docs at https://github.com/phpredis/phpredis for additional methods and features.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.