Matthew Turland
I'm Matt.
It's nice to meet you.
Thank you for attending.
Now hiring a Senior Software Engineer for my team
"Can you cache the books listed on the home page?
Redis is an open source (BSD licensed), in-memory data structure store, used as database, cache and message broker.redis.io
redis-server
package via apt et al.redis
package via Homebrewbuild-essential
apt package or the equivalentredis-cli
redis-cli -h 127.0.0.1 -p 6379
Predis is a flexible and feature-complete Redis client library for PHP >= 5.3.github.com/nrk/predis
Just use Composer.
composer install predis/predis
Just instantiate the client.
$redis = new \Predis\Client;
$redis = new \Predis\Client;
$cached_books = $redis->get('home_page_books');
if (empty($cached_books)) {
$pdo = new \PDO('...');
$books = $pdo->query('...')->fetchAll();
$redis->set('home_page_books', serialize($books));
} else {
$books = unserialize($cached_books);
}
$ redis-cli
127.0.0.1:6379> KEYS *
1) "foo"
[snip]
127.0.0.1:6379> KEYS book_*
1) "book_12345"
[snip]
127.0.0.1:6379> TYPE book_12345
hash
Boss: What happens if we update data for any of these books? You: We'd be presenting stale cached data to the end user. Boss:
$cached_book = $redis->get('book_12345');
if (empty($cached_book)) {
$book = $pdo->query('...')->fetch(\PDO::FETCH_ASSOC);
$redis->set('book_12345', serialize($book));
} else {
$book = unserialize($cached_book);
}
// Prepend the book to the list
$redis->lpush('home_page_books', '12345');
// Append the book to the list
$redis->rpush('home_page_books', '12345');
// Replace the first book in the list with this book
$redis->lset('home_page_books', 0, '12345');
// Add the book to the list before or after another book
$redis->linsert('home_page_books', 'BEFORE', '67890', '12345');
// Remove all instances of the book from the list
$redis->lrem('home_page_books', 0, '12345');
$book_keys = array_map(
function ($id) { return 'book_' . $id; },
$redis->lrange('home_page_books', 0, -1)
);
$cached_books = $redis->mget($book_keys);
$books = array_map('unserialize', $cached_books);
Boss: Our network seems really saturated with traffic. You: We're caching all available data per book and retrieving it every time we need any of it. Boss:
$book = $redis->hgetall('book_12345');
// or
$book = $redis->hmget('book_12345', ['title', 'author', 'price']);
if (empty($book)) {
$book = $pdo->query('...')->fetch(\PDO::FETCH_ASSOC);
$redis->hmset('book_12345', $book);
}
Boss: We want the individual book page to show a section with other books ordered by customers who ordered that book. Can we do that?
// Someone who ordered book 12345 also ordered book 67890
$redis->sadd('ordered_12345', '67890');
// Get identifiers of all books ordered with book 12345
$ordered_books = $redis->smembers('ordered_12345');
// Get identifier for a random book ordered with book 12345
$ordered_book = $redis->srandmember('ordered_12345');
Boss: We want the shopping cart page to show a section with other books ordered by customers who ordered all the books in the cart. Can we do that?
// Assuming these values:
$ordered_12345 = $redis->smembers('ordered_12345'); // ['23456', '34567']
$ordered_67890 = $redis->smembers('ordered_67890'); // ['34567', '45678']
// Get other books ordered with books 12345 and 67890
$ordered_books = $redis->sinter('ordered_12345', 'ordered_67890');
// $ordered_books is now assigned ['34567']
// Assuming these values:
$ordered_12345 = $redis->smembers('ordered_12345'); // ['23456', '34567']
$ordered_67890 = $redis->smembers('ordered_67890'); // ['34567', '45678']
// Get books ordered with book 12345 but not with book 67890
$ordered_12345 = $redis->sdiff('ordered_12345', 'ordered_67890');
// ['23456']
// Get books ordered with either of books 12345 and 67890
$ordered_12345 = $redis->sunion('ordered_12345', 'ordered_67890');
// ['23456', '34567', '45678']
Boss: Oh, wait, I forgot, we also want the ordered books sorted by how many times they've been ordered. Can we do that?
// Add a book ordered with book 12345 for the first time
$rank = $redis->zscore('ordered_12345', '67890');
if ($rank === null) {
$redis->zadd('ordered_12345', 1, '67890');
} else {
$redis->zincrby('ordered_12345', 1, '67890');
}
// Or, with Redis >= 3
$redis->zadd('ordered_12345', 'INCR', 1, '67890');
// Get books ordered with book 12345...
// ... sorted with MOST orders first
$ordered_books = $redis->zrevrange('ordered_12345');
// ... sorted with LEAST orders first
$ordered_books = $redis->zrange('ordered_12345');
// ... at least twice, sorted with MOST orders first
$ordered_books = $redis->zrevrangebyscore('ordered_12345', 2, '+inf');
// ... at least twice, sorted with LEAST orders first
$ordered_books = $redis->zrangebyscore('ordered_12345', 2, '+inf');
Boss: We're storing a lot of book data in Redis and running out of memory. You: We're storing a lot of binary flags in the book hashes.
// Enable the 8th flag for book 12345
$redis->setbit('book_12345_flags', 7, 1);
// Disable the 4th flag for book 12345
$redis->setbit('book_12345_flags', 3, 0);
// Get the value of the 8th flag for book 12345
$flag = $redis->getbit('book_12345_flags', 7);
BIT
commands in Redis commands for strings
Boss: Book database queries are spiking and ordered books are no longer being updated in Redis. You: FUUUUUUUU...
$ redis-cli
127.0.0.1:6379> INFO
...
# Memory
used_memory:104857600
used_memory_human:100M
...
127.0.0.1:6379> CONFIG GET maxmemory
100mb
// Delete an element by key
$redis->del('ordered_12345');
// Expire an element one minute from now
$redis->expire('ordered_12345', 60);
// Same thing, but with a specific time
$redis->expireat('ordered_12345', time() + 60);
// Remove any set expiration time
$redis->persist('ordered_12345');
// Get remaining seconds before expiration
$expires_in = $redis->ttl('ordered_12345');
Boss: When we use multiple Redis commands in one place, it seems to slow our page load times. You: We're making network round trips for each command we send.
$responses = $redis->pipeline(function ($pipe) {
$pipe->sadd('ordered_12345', '67890');
$pipe->smembers('ordered_12345');
});
// or
$responses = $redis->pipeline()
->sadd('ordered_12345', '67890')
->smembers('ordered_12345')
->execute();
Boss: Redis went down last night. There are now instances where some keys related to other keys are missing. You: We're not using transactions.
$responses = $redis->transaction(function ($tx) {
$tx->del('ordered_12345');
$tx->sadd('ordered_12345', '67890');
// ...
});
// or
$responses = $redis->transaction()
->del('ordered_12345')
->sadd('ordered_12345', '67890')
->execute();
Boss: We want to audit what we're storing in Redis. Create a report.
You can use SCAN
/
SSCAN
/
HSCAN
/
ZSCAN
, or...
// Equivalent to using SCAN
use Predis\Collection\Iterator\Keyspace;
foreach (new Keyspace($redis, 'book_*') as $key) {
$book = $redis->hgetall($key);
// ...
}
Please rate my talk!
Also, check out the joind.in mobile apps!