Redis For The
Everyday Developer


                     Ross Tuck
                        Confoo
                 March 1st, 2013
Who Am I?
Ross Tuck
Team Lead at Ibuildings
Codemonkey
HTTP nut
Hat guy
@rosstuck
Boring.
NoSQL
NoNoSQL
Evaluation
•   Created by Salvatore Sanfilippo ( @antirez )
•   Sponsored by VMware
•   4 years old
•   redis.io
“Redis is an open source, advanced
          key-value store.”
“It is often referred to as
a data structure server...”
Defancypants
Redis




Server A           Server B
In-Memory
In-Memory
The Cool Stuff
Fast.
Demo.
Oh wait, it's already done.
120,000~ ops per sec
Flexible.
(optionally)
Persistent.
Replication
     Pub/Sub
   Transactions
     Scripting
      Slices
      Dices
Makes julienne fries
“Well, Ross, that sounds cool...”
+
MySQL       Redis
MySQL
  +
Redis
 4ever
Setup
apt-get install redis-server




                  Easy but old
http://redis.io/download
PHP Libraries

• 5.3+: Predis

• 5.2: Predis or Rediska

• C Extension: phpredis

• redis-cli
$client = new PredisClient();
The Surprise Ending
PHP
 MySQL
Memcache
PHP
 MySQL
Memcache
PHP
MySQL
Redis
It's about 80% cases.
Use Case #1: Caching
Redis has commands.
Commands have parameters.
Think functions.
$client->commandGoesHere($params, $go, $here);
SET
$client->set('ross:mood', 'nervous');




// Later
$client->get('ross:mood'); // returns “nervous”
$client->set('ross:mood', 'nervous');
$client->expire('ross:mood', 5);



// 4 seconds later...
$client->get('ross:mood'); // “nervous”

// 5 seconds later...
$client->get('ross:mood'); // null
$client->set('ross:mood', 'nervous');
$client->expire('ross:mood', 5);
$client->setex('ross:mood', 5, 'nervous');
Great for caching
Ideal for sessions
Use Case #2: Simple Data
Search...

      omg cheezburgers in the lunchroom
      today. Better hurry if u want 1!!! ^_^




How do I store this?
key   value
key                value
homepage_message   omg cheezburgers...
key                value
homepage_message   omg cheezburgers...
tps_reports        new cover pages on...
You already know it.
$client->set('home:message', 'cheezburgers...');
$client->get('home:message');
Equal
 Easier
More Fun
Use Case #3: Hit Counters
increment
$client->incr('page:42:views');   // 1
$client->incr('page:42:views');   // 2
$client->incr('page:42:views');   // 3
Redis is hard ;)
How is this better?
Fast?
redis-benchmark
====== INCR ======
  10000 requests completed in 0.08 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1


100.00% <= 0 milliseconds
119047.62 requests per second
Fast enough.
$client->incr('cookies:eaten');
$client->incrBy('cookies:eaten', 2);
$client->incrByFloat('cookies:eaten', 0.5);   version 2.6+
Use Case #4: Latest News
“It is often referred to as
a data structure server...”
“...since keys can contain strings, hashes,
         lists, sets and sorted sets.”
$redis = array();
Generic
Hash
List
Set
Sorted Set
Generic
Hash         // strings and numbers
List         $redis['ross:mood'] = "happy";
Set          $redis['foo'] = 9;

Sorted Set
Generic
Hash         // associative array
List         $redis['foo']['name'] = 'Bob';
             $redis['foo']['age'] = 31;
Set
Sorted Set
             Objects, forms, records
Generic
Hash         // not associative
List         $redis['foo'][] = 'zip';
Set          $redis['foo'][] = 'zap';

Sorted Set
             Lists, stacks, queues
Generic
             // No dupes, no order
Hash
             shuffle(
List
                  array_unique($redis['foo'])
Set          );
Sorted Set
             Relations, stats, matching
Generic
Hash         // Ordered by *score*
List         array_unique($redis['foo']);
Set
Sorted Set
             Curing cancer, world peace
             Sets but with order or scores
Y U NO STOP




LISTING THINGS
Generic
Hash
List
Set
Sorted Set
Generic
Hash
List
Set
Sorted Set
// Code
$client->lpush('news:latest', 'Aliens Attack!');



// Redis
['Aliens Attack!']
// 2 hours later...
$client->lpush('news:latest', 'Takei 2016');



// Redis
['Takei 2016', 'Aliens Attack!']
// That evening...
$client->lpush('news:latest', 'Eggs = Cancer!');



// Redis
['Eggs = Cancer!', 'Takei 2016', 'Aliens Attack!']
Recap
// Code
$client->lpush('news:latest', 'Aliens Attack!');
$client->lpush('news:latest', 'Takei 2016');
$client->lpush('news:latest', 'Eggs = Cancer!');


// Redis
['Eggs = Cancer!', 'Takei 2016', 'Aliens Attack!']
Getting it back out?
Start Index



$client->lrange('news:latest', 0, 1);




                                  En d Index
var_dump(
$client->lrange('news:latest', 0, 1)
);

array(2) {
     [0]=>   string(14) "Eggs = Cancer!"
     [1]=>   string(10) "Takei 2016"
}
That's it.
 Really.
What about size?
$client->lpush('news:latest', 'Free Jetpacks!');
$client->lpush('news:latest', 'Elvis Found!');
$client->lpush('news:latest', 'Takei Wins!');
//...and so on...
ltrim
$client->ltrim('news:latest', 0, 2);
// Only the three latest stories remain!
Cron
or simpler...
$client->lpush('news:latest', 'Cats Leave Euro');
$client->ltrim('news:latest', 0, 2);
Great option for notifications
Use Case #5: Tricksy Caching
SELECT * FROM Articles
INNER JOIN Authors ON (complicated joins)
-- More joins
WHERE (complicated logic)
LIMIT 0, 20
SELECT Articles.id FROM Articles
INNER JOIN Authors ON (complicated joins)
-- More joins
WHERE (complicated logic)
$client->lpush('search:a17f3', $ids);
$client->lrange('search:a17f3', $limit, $offset);
SELECT * FROM Articles
INNER JOIN Authors ON (complicated joins)
-- More joins
WHERE Articles.id IN (1, 2, 3)
Use Case #6: Recently Viewed
Generic
Hash
List
Set
Sorted Set
Generic
Hash
List         No duplicates
Set
Sorted Set
Generic
Hash
List
Set          Needs to be ordered
Sorted Set
Generic
Hash
List
Set
Sorted Set   Just Right
zadd
$client->zadd('mykey', 1, 'mydata');




                             Any integer
                                     t
                             or floa
$client->zadd('recent', 1, '/p/first');
$client->zadd('recent', time(), '/p/first');
$client->zadd('recent', 1338020901, '/p/first');
$client->zadd('recent', 1338020902, '/p/second');
$client->zadd('recent', 1338020903, '/p/third');
$client->zadd('recent', 1338020904, '/p/fourth');
Reading it back out?
$client->zrange('recent', 0, 2);

array(3) {
    [0]=> string(8) "/p/first"
    [1]=> string(9) "/p/second"
    [2]=> string(8) "/p/third"
}
Reverse


$client->zrevrange('recent', 0, 2);

array(3) {
    [0]=> string(9) "/p/fourth"
    [1]=> string(8) "/p/third"
    [2]=> string(9) "/p/second"
}
Duplicates?
$client->zadd('recent', 1338020901, '/p/first');


// later...
$client->zadd('recent', 1338020928, '/p/first');
$client->zrevrange('recent', 0, 2);

array(3) {
    [0]=> string(8) "/p/first"
    [1]=> string(9) "/p/fourth"
    [2]=> string(8) "/p/third"
}
Cool.
Other things we can do?
$client->zrangebyscore('recent', $low, $high);
$yesterday = time()-(60*60*24);
$client->zrangebyscore('recent', $yesterday, '+inf');
Intersections
zinterstore
$client->zinterstore('omg', 2, 'recent', 'favorite');
$client->zrange('omg', 0, 4);
Deletion
zrem
zremrangebyscore
$yesterday = time()-(60*60*24);
$client->zremrangebyscore(
     'recent', '-inf', $yesterday
);
We can do a lot.
Scores can be anything.
Use Case #7: Sharing Data
PHP




          Redis

Node.js           Python
•   ActionScript   •   Java
•   C              •   Lua
•   C#             •   Node.js
•   C++            •   Objective-C
•   Clojure        •   Perl
•   Common Lisp    •   PHP
•   Erlang         •   Pure Data
•   Fancy          •   Python
•   Go             •   Ruby
•   Haskell        •   Scala
•   haXe           •   Smalltalk
•   Io             •   Tcl
$client = new PredisClient();
$client->set('foo', 'bar');
var redis = require("redis");
var client = redis.createClient();


client.get("foo", redis.print);
Step Further...
Pub/Sub
$client->publish('bids:42', '$13.01');
client.on("message", function (channel, message) {
      console.log(channel + "= " + message);
});
client.subscribe("bids:42");

// prints “bids:42= $13.01”
yet
Not everyday
Use Case #8: Worker Queues
$client->lpush('jobs:pending', 'clear_cache');
$client->lpush('jobs:pending', '{"do":"email", …}');
$client->lpush('jobs:pending', 'job:45');
// worker
$client = new PredisClient(array(
       'read_write_timeout' => -1
));


do {
       $job = $client->brpop('jobs:pending', 0);
       doJob($job);
} while(true);
This will work.
However...
Things break.
Things break.
Clients break.
Clients break.
Clients crash.
do {
       $job = $client->brpop('jobs:pending', 0);
       doJob($job);
} while(true);
Multiple keys is the redis way.
'jobs:pending'
'jobs:working'
What we need is:   blocking
                   rpop
                   lpush
brpoplpush
                .
      No, really
do {
       $job = $client->brpoplpush(
            'jobs:pending', 'jobs:working', 0
       );


       doJob($job);
} while(true);
do {
       $job = $client->brpoplpush(
            'jobs:pending', 'jobs:working', 0
       );


       if(doJob($job)) {
            $client->lrem('jobs:working', 0, $job);
       }
} while(true);
Use Case #9: Scripted Commands
Use Case #9: Impressing People
$client->set('jesse', 'dude');
$client->set('chester', 'sweet');
Lua
var                          arguments


local first = redis.call('get', KEYS[1]);
local second = redis.call('get', KEYS[2]);


redis.call('set', KEYS[1], second);
redis.call('set', KEYS[2], first);


return {first, second};
// jesse: dude
// chester: sweet


EVAL 'local first...' 2 jesse chester


// jesse: sweet
// chester: dude
Eval != Evil

   *Offer  only applies
   in Redis-Land.
                       .
    Void where pedantic
Don't over do it.
Reusing scripts?
SCRIPT LOAD 'local first...'
// 591d1b681192f606d8cb658e1e173e771a90e60e
EVAL 'local first...' 2 jesse chester
EVALSHA 591d1... 2 jesse chester
Sweet.
Epilogue
Simple = Powerful
Fun.
Keep it in RAM.
Extensible + Ecosystem
Great docs.
Use them.
Bad for the DB?
Might be good for Redis.
Bad for the DB?
Might be good for Redis.
IAEA     Kotaku



@svdgraaf




               Alltheragefaces


                   QuickMeme
http://joind.in/7971

Redis for the Everyday Developer