SlideShare a Scribd company logo
1 of 53
Download to read offline
Writing modular &
encapsulated Redis code
Jim Nelson
jnelson@archive.org
Internet Archive
Internet Archive
Universal Access to All Knowledge
● Founded 1996, based in San Francisco
● Archive of digital and physical media
● Includes Web, books, music, film, software & more
● Digital holdings: over 35 petabytes & counting
● Over 1 million users per day
● Key collections & services:
○ Wayback Machine
○ Grateful Dead live concert collection
○ The Emularity: in-browser emulation of 8-bit software, IBM
PC, Mac, & now handhelds like Speak-and-Spell
Internet Archive ♡ Redis
● Caching & directory lookup backed by 10-node sharded Redis cluster
● Sharding performed client-side via consistent hashing (PHP, Predis)
● Each node supported by two replicated mirrors (fail-over)
● Specialized Redis instances also used throughout IA’s services, including
○ Wayback Machine
○ Search (item metadata and full-text)
○ Administrative & usage tracking
○ …and more
Before we get started
● I’m assuming you’ve written Redis client code already
● You don’t need to know the exact details of the Redis protocol…
● …but you need some understanding of how Redis “works”
○ single-threaded
○ different data types (strings, hash, sorted sets, etc.)
○ transactions (MULTI/EXEC)
This presentation uses PHP sample code and Predis (a PHP Redis client)
but most concepts presented should work in other languages & clients
Redis rocks…
“Baby, where have you been my whole life?”
● Quick, quick, quick
○ Quick to code
○ Quick to debug
○ Quick response times
○ Quick to model data
● Logical, well-defined command structure
● Atomicity
● Flexibility over rigor
Learning only a handful of straightforward concepts,
I felt I had a solid working understanding of Redis
…but there
are issues.
“It’s complicated.” (As in, Facebook-it’s-complicated.)
● Application code mixed with data model code
○ Little-to-no separation between data logic and business logic
● Relational data often sprayed across separate Redis buckets
○ No clean way to normalize or deserialize complex objects if they’re
sharded across keys
● Performance considerations trump clean coding practices
○ Modularity ignored in order to minimize round-trips to server
The temptation of Redis is
that it looks like a
collections framework.
What’s missing?
All the things I know and love about designing clean code.
Modularity
Encapsulation
Readability
What is
encapsulation?
Encapsulation “says that a program
should be divided into modules that
hide data structures behind an
interface of procedure calls. The
purpose of this is to allow you to
change the underlying data structure
without causing a large ripple effect
across the system.”
–Martin Fowler,
martinfowler.com
Redis programming today
Sending & receiving data
Redis (and most Redis clients) offer different modes of
operation:
● Immediate mode
● Pipeline mode
● Transaction mode
● Atomic pipeline mode
Immediate mode
Send one command, receive one response.
That’s it. No, really.
$redis = new PredisClient();
$name = $redis->get(“user:$id:name”); // sends GET, recvs name
$email = $redis->get(“user:$id:email”); // sends GET, recvs email
$login_count = $redis->incr(“user:$id:logins”) // sends INCR, recvs incremented number
Simple enough!
Pipeline mode
● Collect (or schedule) several commands on the client side
● Send all commands in one burst
● Receive all responses in one burst
$redis = new PredisClient();
$pipeline = $redis->pipeline(); // no network I/O
$pipeline->get(“user:$id:name”); // no network I/O
$pipeline->get(“user:$id:email”); // no network I/O
$pipeline->incr(“user:$id:logins”);// no network I/O
$result = $pipeline->execute(); // sends GET/GET/INCR, recvs name, email, login
Single round-trip to the server but operations are not atomic
Transactions
● MULTI command opens transaction
● Send one or more commands
● EXEC closes transaction & receives all results
● Operations execute atomically
$redis = new PredisClient();
$transaction = $redis->transaction(); // sends MULTI
$transaction->get(“user:$id:name”); // sends GET, recvs QUEUED
$transaction->get(“user:$id:email”); // sends GET, recvs QUEUED
$transaction->incr(“user:$id:logins”);// sends INCR, recvs QUEUED
$result = $transaction->execute(); // sends EXEC, recvs name, email, login (post-INCR)
Each operation is a separate round-trip to the server, including the MULTI and EXEC
Atomic pipelines
● Schedule several commands on the client side
● All commands are “wrapped” in a MULTI/EXEC
● Send all commands in one burst, receive responses on one burst
● Operations execute atomically
$redis = new PredisClient();
$atomic = $redis->pipeline([‘atomic’ => true]);
$atomic->get(“user:$id:name”); // no network I/O
$atomic->get(“user:$id:email”); // no network I/O
$atomic->incr(“user:$id:logins”);// no network I/O
$result = $atomic->execute(); // sends MULTI/GET/GET/INCR/EXEC, recvs name, email, login
Atomic pipelines generally are more efficient than raw transactions
Warning:
Ugly code ahead.
Worse:
It’s written in PHP.
In the name of minimizing round-trips
/**
* A do-everything function so opening a user session is a single
* round-trip to the Redis server.
*/
function open_user_session($redis, $userid, $emoji_id, $notifications) {
$pipe = $redis->pipeline();
$pipe->get(“user:$userid:name”);
$pipe->get(“user:$userid:member_of”);
$pipe->hmget(“emoji:$emoji_id”, “tag”, “name”, “image”);
$today = date(“Y-m-d”);
$pipe->incr(“logins:$today”);
$pipe->set(“session:$userid”, time(), “EX”, 600, “NX”);
$pipe->sadd(“members:logged_in”, $userid);
$results = $pipe->execute();
Do-everything functions break encapsulation
● Details of data layout in Redis is exposed
● Changing data structures means changing lots of code—
the “ripple effect”
● Encourages code duplication—tempted to Ctrl+C / Ctrl+V
● Difficult to refactor
● Difficult to maintain
Passing pipeline object to other functions has fundamental problem:
How does the scheduling code receive the results of its read operation(s)?
Transforming & normalizing results
/**
* Do-everything function continued.
*
* In Predis (and other clients) transaction and pipeline
* results are returned in a numeric-indexed array.
*/
function open_user_session($redis, $id, $emoji_id, $notifications) {
// … pipeline is executed as in previous slide …
$user = new User($results[0]);
$user->setMembershipObject(Membership::fromString($results[1]));
$emoji = new Emoji($results[2][1], $results[2][0]);
$emoji->setImage(base64_decode($results[2][2]));
$user->setEmoji($emoji);
StatsD::gauge(‘login_count’, (int) $results[3]);
if ($results[4] != null) $notifications->sessionExtended($id);
if ($results[5] > 0) Syslog::notice(“$id already logged in”);
return $user;
}
Commands are detached from result processing
● Results array is ordered in command-order
● Adding a new command changes index of all results which follow
● Difficult to associate commands with their results
● $results[3][0] is not friendly
● Redis data types don’t necessarily correspond to language data types
● string(“0”) is not FALSE, NULL, nil, undefined, or even an integer in all languages
Even if the code is all contained in a single function,
it’s difficult to match the Redis command with its result(s)
The full function
function open_user_session($redis, $id, $emoji_id, $notifications) {
$pipe = $redis->pipeline();
$pipe->get(“user:$id:name”);
$pipe->get(“user:$id:member_of”);
$pipe->hmget(“emoji:$emoji_id”, “tag”, “name”, “image”);
$today = date(“Y-m-d”);
$pipe->incr(“logins:$today”);
$pipe->set(“session:$id”, “EX”, 600, “NX”);
$pipe->sadd(“members:logged_in”, $id);
$results = $pipe->execute();
$user = new User($results[0]);
$user->setMembershipObject(Membership::fromString($results[1]));
$emoji = new Emoji($results[2][1], $results[2][0]);
$emoji->setImage(base64_decode($results[2][2]));
$user->setEmoji($emoji);
StatsD::gauge(‘login_count’, (int) $results[3]);
if ($results[4] != null) $notifications->sessionExtended($id);
if ($results[5] > 0) Syslog::notice(“$id already logged in”);
return $user;
}
In a clean world, the
do-everything function would be
broken up into functions, each
bite-sized Redis operation(s).
The hard reality:
Production-scale requirements
often require packing
operations in pipelines.
The hard reality:
Production-scale requirements
often create balls of
code mud.
Promises & futures
with Redis
Promises & futures
First developed in the 1970s
“Rediscovered” in the 2000s for Web
Sometimes called “deferred programming”
The Big Concept:
Bind a costly scheduled operation
with its resulting value(s) upon completion
Promises & futures
Here’s how I’m using these terms:
A Promise object schedules the operation.
The Promise returns a placeholder object for each operation.
The object “receives” the result when the operation completes.
That object is a Future.
Promises & futures with Redis
Going back to a key concept:
Redis pipelines and transactions “schedule” work to be performed later.
$pipeline = $redis->pipeline(); // no network I/O
$pipeline->get(“user:$id:name”); // no network I/O
$pipeline->get(“user:$id:email”); // no network I/O
$pipeline->incr(“user:$id:logins”);// no network I/O
$result = $pipeline->execute(); // sends GET/GET/INCR, recvs name, email, login
This matches perfectly with the promises/futures approach
Promises & futures with Redis
Your Redis client might already have support!
A little searching turned up libraries for NodeJS & Scala
I’m sure there’s more—this is not an exotic concept.
Deferred:
Promises & futures for PHP/Predis
Deferred: Promises & futures for PHP/Predis
Programming with Deferred almost looks like coding with Predis:
$redis = new PredisClient();
$promises = new DeferredPromisesPipeline($redis);
$get_name = $promises->get(“user:$id:name”); // returns DeferredFuture
$hgetall_emoji = $promises->hgetall(“emoji:$emoji_id”); // returns DeferredFuture
$promises->execute(); // network I/O happens here
$name = $get_name->value(); // string
$emoji = $hgetall_emoji->value(); // HGETALL array
Scheduling commands with Deferred
Redis commands return placeholder Future objects:
$get_name = $promises->get(“user:$id:name”); // DeferredFuture
$hgetall_emoji = $promises->hgetall(“emoji:$emoji_id”); // DeferredFuture
Futures hold no value until the Promises object is executed.
In promises/futures parlance, the Futures are unfulfilled.
Fulfilling operations with Deferred
Executing a Promises object fulfills the Future objects:
$promises->execute(); // network I/O happens here (“fulfilled”)
$name = $get_name->value(); // string
$emoji = $hgetall_emoji->value(); // HGETALL array
Once fulfilled, the resulting value may be retrieved with the
value() method.
Transforming results
Often we want to normalize or deserialize Redis results. Futures allows for
transformations:
// DeferredFuture
$username = $promises->get(“user:$id:name”);
// reverse the user’s name upon fulfillment
$username->transform(“strrev”); // strrev() is a PHP function
// network I/O -- strrev() is executed here
$promises->execute();
// user’s name backwards
echo $username->value();
Transformations: A more interesting example
Use transformations to fetch objects instead of arrays of strings:
// $emoji is the DeferredFuture
$emoji = $promises->hmget(“emoji:$id”, “name”, “tag”, “image”);
// $hmget is an array of values
$emoji->transform(function ($hmget) {
$e = new Emoji($hmget[0], $hmget[1]);
$e->load_image(base64_decode($hmget[2]));
return $e; // returns an initialized Emoji object
});
$promises->execute(); // network I/O -- transformation happens here
return $emoji->value(); // returns the initialized Emoji object
Stacking transformations
Multiple transformations may be registered. They are processed in order:
function emoji_image_binary($promises, $id) {
$image_future = $promises->hget(“emoji:$id”, “image”);
$image_future->transform(function ($hget) {
return base64_decode($hget); // this transformation is first
});
return $image_future;
}
$image_md5_future = emoji_image_binary($promises, ‘happy-face’);
$image_md5_future->transform(function ($bin) {
return md5sum($bin); // this transformation is second
});
Make HMGET look like HGETALL
In Predis, HGETALL returns a keyed array (or a map):
$redis->hgetall(“profile:jim”) // [ “name” => “Jim”, “emoji” => “:elephant:” ]
while HMGET returns an indexed array:
$redis->hmget(“profile:jim”, “name”, “emoji”) // [ “Jim”, “:elephant:” ]
If you have a function which uses HMGET in one code path and HGETALL in
another, transformations can smooth out the difference.
Make HMGET look like HGETALL
function getProfile($promises, $username, array $fields = null) {
if (!$fields) {
$future = $promises->hgetall(“profile:$username”);
} else {
$future = $promises->hmget(“profile:$username”, $fields);
$future->transform(function ($hmget) use ($fields) {
return array_combine($fields, $hmget);
});
}
return $future;
}
Binding
Binding allows for transferring the final result to a PHP variable:
$username = null;
$future = $promises->get(“user:$id:name”);
$future->bind($username);
// more conveniently (identical to prior 2 lines)
$promises->get(“user:$id:name”)->bind($username);
$promises->execute(); // $username is now set to a string
return $username;
Transformations + binding
$profile = null;
// get raw user profile data with HGETALL
$future = $promises->hgetall(“user:$id:profile”);
$future->transform(function ($hgetall) {
// initialize UserProfile with HGETALL results
return new UserProfile($hgetall[“name”], $hgetall[“location”], $hgetall[“email”]);
});
// bind UserProfile object to $profile variable
$future->bind($profile);
// fulfill
$promises->execute();
Transformations are always done first, then binding
Reducing
Multiple Futures may be reduced to a single Future:
$registered = $promises->sismember(“users:registered”, $userid);
$validated = $promises->sismember(“users:validated”, $userid);
$confirmed = $promises->sismember(“users:confirmed”, $userid);
// reduce all three SISMEMBERs to a single Future
$is_ready = $promises->reduce($registered, $validated, $confirmed); // returns a DeferredFuture
$is_ready->transform(function (array $results) {
return $results[0] && $results[1] && $results[2];
});
$promises->execute();
return $is_ready->value(); // returns boolean
Putting it all together
The full function
function open_user_session($redis, $id, $emoji_id, $notifications) {
$pipe = $redis->pipeline();
$pipe->get(“user:$id:name”);
$pipe->get(“user:$id:member_of”);
$pipe->hmget(“emoji:$emoji_id”, “tag”, “name”, “image”);
$today = date(“Y-m-d”);
$pipe->incr(“logins:$today”);
$pipe->set(“session:$id”, “EX”, 600, “NX”);
$pipe->sadd(“members:logged_in”, $id);
$results = $pipe->execute();
$user = new User($results[0]);
$user->set_membership_object(Membership::from_string($results[1]));
$emoji = new Emoji($results[2][1], $results[2][0]);
$emoji->set_image(base64_decode($results[2][2]));
$user->set_emoji($emoji);
StatsD::gauge(‘login_count’, (int) $results[3]);
if ($results[4] != null) $notifications->session_extended($id);
if ($results[5] > 0) Syslog::notice(“$id already logged in”);
return $user;
}
class User
// Returns DeferredFuture for User instance
static function getFuture($promises, $id)
{
$name = $promises->get(“user:$id:name”)
$member_of = $promises->get(“user:$id:member_of”);
$user_future = $promises->reduce($name, $member_of)->transform($results) {
$user = new User($results[0]);
$user->set_membership_object(Membership::from_string($results[1]);
return $user;
});
return $user_future;
}
class Emoji
// Returns DeferredFuture for Emoji instance
static function getFuture($promises, $emoji_id)
{
$emoji_future = $promises->hmget(“emoji:$emoji_id”,
“name”, “tag”, “image”);
$emoji_future->transform(function ($hmget) {
$emoji = new Emoji($hmget[0], $hmget[1]);
$emoji->set_image(base64_decode($hmget[2]);
return $emoji;
});
return $emoji_future;
}
class Session
// Starts the session on the Redis cache and notifies if extended
function startSession($promises, $userid, $notifications)
{
$set_future = $promises->set(“session:$userid”, “EX”, 600, “NX”);
$set_future->onFulfilled(function ($set) use ($userid, $notifications) {
if ($set !== null)
$notifications->notify_session_extended($userid);
});
return $set_future;
}
The full function (revised)
function open_user_session($redis, $userid, $emoji_id, $notifications) {
$promises = new DeferredPromisesPipeline($redis);
$user = null;
User::getFuture($promises, $userid)->bind($user);
Emoji::getFuture($promises, $emoji_id)->onFulfilled(function ($emoji) use (&$user) {
$user->set_emoji($emoji);
});
Session::create()->startSession($promises, $userid, $notifications);
LoginTracker::increment($promises);
Members::setLoggedIn($promises, $userid);
$promises->execute();
return $user;
“Show me the code.”
https://github.com/internetarchive/deferred
Questions?
Thank You

More Related Content

What's hot

RedisConf18 - Serving Automated Home Valuation with Redis & Kafka
RedisConf18 - Serving Automated Home Valuation with Redis & KafkaRedisConf18 - Serving Automated Home Valuation with Redis & Kafka
RedisConf18 - Serving Automated Home Valuation with Redis & Kafka
Redis Labs
 
RedisConf18 - Remote Monitoring & Controlling Scienific Instruments
RedisConf18 - Remote Monitoring & Controlling Scienific InstrumentsRedisConf18 - Remote Monitoring & Controlling Scienific Instruments
RedisConf18 - Remote Monitoring & Controlling Scienific Instruments
Redis Labs
 
HDFS Selective Wire Encryption
HDFS Selective Wire EncryptionHDFS Selective Wire Encryption
HDFS Selective Wire Encryption
Konstantin V. Shvachko
 
RedisConf18 - Redis Enterprise on Cloud Native Platforms
RedisConf18 - Redis Enterprise on Cloud  Native  Platforms RedisConf18 - Redis Enterprise on Cloud  Native  Platforms
RedisConf18 - Redis Enterprise on Cloud Native Platforms
Redis Labs
 
RedisConf18 - Implementing a New Data Structure for Redis
RedisConf18 - Implementing a New Data Structure for Redis  RedisConf18 - Implementing a New Data Structure for Redis
RedisConf18 - Implementing a New Data Structure for Redis
Redis Labs
 

What's hot (20)

RedisConf18 - Serving Automated Home Valuation with Redis & Kafka
RedisConf18 - Serving Automated Home Valuation with Redis & KafkaRedisConf18 - Serving Automated Home Valuation with Redis & Kafka
RedisConf18 - Serving Automated Home Valuation with Redis & Kafka
 
RedisConf18 - Redis as a time-series DB
RedisConf18 - Redis as a time-series DBRedisConf18 - Redis as a time-series DB
RedisConf18 - Redis as a time-series DB
 
RedisConf18 - Ultra Scaling with Redis Enterprise
RedisConf18 - Ultra Scaling with Redis EnterpriseRedisConf18 - Ultra Scaling with Redis Enterprise
RedisConf18 - Ultra Scaling with Redis Enterprise
 
High-Volume Data Collection and Real Time Analytics Using Redis
High-Volume Data Collection and Real Time Analytics Using RedisHigh-Volume Data Collection and Real Time Analytics Using Redis
High-Volume Data Collection and Real Time Analytics Using Redis
 
RedisConf18 - Remote Monitoring & Controlling Scienific Instruments
RedisConf18 - Remote Monitoring & Controlling Scienific InstrumentsRedisConf18 - Remote Monitoring & Controlling Scienific Instruments
RedisConf18 - Remote Monitoring & Controlling Scienific Instruments
 
HDFS Selective Wire Encryption
HDFS Selective Wire EncryptionHDFS Selective Wire Encryption
HDFS Selective Wire Encryption
 
iland Internet Solutions: Leveraging Cassandra for real-time multi-datacenter...
iland Internet Solutions: Leveraging Cassandra for real-time multi-datacenter...iland Internet Solutions: Leveraging Cassandra for real-time multi-datacenter...
iland Internet Solutions: Leveraging Cassandra for real-time multi-datacenter...
 
RedisConf18 - Redis Enterprise on Cloud Native Platforms
RedisConf18 - Redis Enterprise on Cloud  Native  Platforms RedisConf18 - Redis Enterprise on Cloud  Native  Platforms
RedisConf18 - Redis Enterprise on Cloud Native Platforms
 
RedisConf17 - Operationalizing Redis at Scale
RedisConf17 - Operationalizing Redis at ScaleRedisConf17 - Operationalizing Redis at Scale
RedisConf17 - Operationalizing Redis at Scale
 
RedisConf18 - Implementing a New Data Structure for Redis
RedisConf18 - Implementing a New Data Structure for Redis  RedisConf18 - Implementing a New Data Structure for Redis
RedisConf18 - Implementing a New Data Structure for Redis
 
Redis Day TLV 2018 - 10 Reasons why Redis should be your Primary Database
Redis Day TLV 2018 - 10 Reasons why Redis should be your Primary DatabaseRedis Day TLV 2018 - 10 Reasons why Redis should be your Primary Database
Redis Day TLV 2018 - 10 Reasons why Redis should be your Primary Database
 
PlayStation and Searchable Cassandra Without Solr (Dustin Pham & Alexander Fi...
PlayStation and Searchable Cassandra Without Solr (Dustin Pham & Alexander Fi...PlayStation and Searchable Cassandra Without Solr (Dustin Pham & Alexander Fi...
PlayStation and Searchable Cassandra Without Solr (Dustin Pham & Alexander Fi...
 
Message Queuing on a Large Scale: IMVUs stateful real-time message queue for ...
Message Queuing on a Large Scale: IMVUs stateful real-time message queue for ...Message Queuing on a Large Scale: IMVUs stateful real-time message queue for ...
Message Queuing on a Large Scale: IMVUs stateful real-time message queue for ...
 
HadoopCon- Trend Micro SPN Hadoop Overview
HadoopCon- Trend Micro SPN Hadoop OverviewHadoopCon- Trend Micro SPN Hadoop Overview
HadoopCon- Trend Micro SPN Hadoop Overview
 
Managing 50K+ Redis Databases Over 4 Public Clouds ... with a Tiny Devops Team
Managing 50K+ Redis Databases Over 4 Public Clouds ... with a Tiny Devops TeamManaging 50K+ Redis Databases Over 4 Public Clouds ... with a Tiny Devops Team
Managing 50K+ Redis Databases Over 4 Public Clouds ... with a Tiny Devops Team
 
Webinar: Diagnosing Apache Cassandra Problems in Production
Webinar: Diagnosing Apache Cassandra Problems in ProductionWebinar: Diagnosing Apache Cassandra Problems in Production
Webinar: Diagnosing Apache Cassandra Problems in Production
 
RedisConf17 - Lyft - Geospatial at Scale - Daniel Hochman
RedisConf17 - Lyft - Geospatial at Scale - Daniel HochmanRedisConf17 - Lyft - Geospatial at Scale - Daniel Hochman
RedisConf17 - Lyft - Geospatial at Scale - Daniel Hochman
 
Troubleshooting Cassandra (J.B. Langston, DataStax) | C* Summit 2016
Troubleshooting Cassandra (J.B. Langston, DataStax) | C* Summit 2016Troubleshooting Cassandra (J.B. Langston, DataStax) | C* Summit 2016
Troubleshooting Cassandra (J.B. Langston, DataStax) | C* Summit 2016
 
RedisConf17 - Redis in High Traffic Adtech Stack
RedisConf17 - Redis in High Traffic Adtech StackRedisConf17 - Redis in High Traffic Adtech Stack
RedisConf17 - Redis in High Traffic Adtech Stack
 
What's new with enterprise Redis - Leena Joshi, Redis Labs
What's new with enterprise Redis - Leena Joshi, Redis LabsWhat's new with enterprise Redis - Leena Joshi, Redis Labs
What's new with enterprise Redis - Leena Joshi, Redis Labs
 

Similar to RedisConf18 - Writing modular & encapsulated Redis code

Codebits 2012 - Fast relational web site construction.
Codebits 2012 - Fast relational web site construction.Codebits 2012 - Fast relational web site construction.
Codebits 2012 - Fast relational web site construction.
Nelson Gomes
 
Day in a life of a node.js developer
Day in a life of a node.js developerDay in a life of a node.js developer
Day in a life of a node.js developer
Edureka!
 

Similar to RedisConf18 - Writing modular & encapsulated Redis code (20)

Docker tlv
Docker tlvDocker tlv
Docker tlv
 
Perl Tools for Productivity
Perl Tools for ProductivityPerl Tools for Productivity
Perl Tools for Productivity
 
Metarhia: Node.js Macht Frei
Metarhia: Node.js Macht FreiMetarhia: Node.js Macht Frei
Metarhia: Node.js Macht Frei
 
Node azure
Node azureNode azure
Node azure
 
Learned lessons in a real world project by Luis Rovirosa at PHPMad
Learned lessons in a real world project by Luis Rovirosa at PHPMadLearned lessons in a real world project by Luis Rovirosa at PHPMad
Learned lessons in a real world project by Luis Rovirosa at PHPMad
 
Tech Talk: DevOps at LeanIX @ Startup Camp Berlin
Tech Talk: DevOps at LeanIX @ Startup Camp BerlinTech Talk: DevOps at LeanIX @ Startup Camp Berlin
Tech Talk: DevOps at LeanIX @ Startup Camp Berlin
 
Redis
RedisRedis
Redis
 
Expanding your impact with programmability in the data center
Expanding your impact with programmability in the data centerExpanding your impact with programmability in the data center
Expanding your impact with programmability in the data center
 
Php on the Web and Desktop
Php on the Web and DesktopPhp on the Web and Desktop
Php on the Web and Desktop
 
SQL in the Hybrid World
SQL in the Hybrid WorldSQL in the Hybrid World
SQL in the Hybrid World
 
Catalyst - refactor large apps with it and have fun!
Catalyst - refactor large apps with it and have fun!Catalyst - refactor large apps with it and have fun!
Catalyst - refactor large apps with it and have fun!
 
Unit 02: Web Technologies (2/2)
Unit 02: Web Technologies (2/2)Unit 02: Web Technologies (2/2)
Unit 02: Web Technologies (2/2)
 
DCEU 18: App-in-a-Box with Docker Application Packages
DCEU 18: App-in-a-Box with Docker Application PackagesDCEU 18: App-in-a-Box with Docker Application Packages
DCEU 18: App-in-a-Box with Docker Application Packages
 
Introduction to interactive data visualisation using R Shiny
Introduction to interactive data visualisation using R ShinyIntroduction to interactive data visualisation using R Shiny
Introduction to interactive data visualisation using R Shiny
 
Aad Versteden | State-of-the-art web applications fuelled by Linked Data awar...
Aad Versteden | State-of-the-art web applications fuelled by Linked Data awar...Aad Versteden | State-of-the-art web applications fuelled by Linked Data awar...
Aad Versteden | State-of-the-art web applications fuelled by Linked Data awar...
 
Software Engineering
Software EngineeringSoftware Engineering
Software Engineering
 
Codebits 2012 - Fast relational web site construction.
Codebits 2012 - Fast relational web site construction.Codebits 2012 - Fast relational web site construction.
Codebits 2012 - Fast relational web site construction.
 
Day in a life of a node.js developer
Day in a life of a node.js developerDay in a life of a node.js developer
Day in a life of a node.js developer
 
Day In A Life Of A Node.js Developer
Day In A Life Of A Node.js DeveloperDay In A Life Of A Node.js Developer
Day In A Life Of A Node.js Developer
 
Nagios Conference 2012 - Eric Loyd - Nagios Implementation Case Eastman Kodak...
Nagios Conference 2012 - Eric Loyd - Nagios Implementation Case Eastman Kodak...Nagios Conference 2012 - Eric Loyd - Nagios Implementation Case Eastman Kodak...
Nagios Conference 2012 - Eric Loyd - Nagios Implementation Case Eastman Kodak...
 

More from Redis Labs

SQL, Redis and Kubernetes by Paul Stanton of Windocks - Redis Day Seattle 2020
SQL, Redis and Kubernetes by Paul Stanton of Windocks - Redis Day Seattle 2020SQL, Redis and Kubernetes by Paul Stanton of Windocks - Redis Day Seattle 2020
SQL, Redis and Kubernetes by Paul Stanton of Windocks - Redis Day Seattle 2020
Redis Labs
 
Anatomy of a Redis Command by Madelyn Olson of Amazon Web Services - Redis Da...
Anatomy of a Redis Command by Madelyn Olson of Amazon Web Services - Redis Da...Anatomy of a Redis Command by Madelyn Olson of Amazon Web Services - Redis Da...
Anatomy of a Redis Command by Madelyn Olson of Amazon Web Services - Redis Da...
Redis Labs
 
RediSearch 1.6 by Pieter Cailliau - Redis Day Bangalore 2020
RediSearch 1.6 by Pieter Cailliau - Redis Day Bangalore 2020RediSearch 1.6 by Pieter Cailliau - Redis Day Bangalore 2020
RediSearch 1.6 by Pieter Cailliau - Redis Day Bangalore 2020
Redis Labs
 
RedisGraph 2.0 by Pieter Cailliau - Redis Day Bangalore 2020
RedisGraph 2.0 by Pieter Cailliau - Redis Day Bangalore 2020RedisGraph 2.0 by Pieter Cailliau - Redis Day Bangalore 2020
RedisGraph 2.0 by Pieter Cailliau - Redis Day Bangalore 2020
Redis Labs
 

More from Redis Labs (20)

Redis Day Bangalore 2020 - Session state caching with redis
Redis Day Bangalore 2020 - Session state caching with redisRedis Day Bangalore 2020 - Session state caching with redis
Redis Day Bangalore 2020 - Session state caching with redis
 
Protecting Your API with Redis by Jane Paek - Redis Day Seattle 2020
Protecting Your API with Redis by Jane Paek - Redis Day Seattle 2020Protecting Your API with Redis by Jane Paek - Redis Day Seattle 2020
Protecting Your API with Redis by Jane Paek - Redis Day Seattle 2020
 
The Happy Marriage of Redis and Protobuf by Scott Haines of Twilio - Redis Da...
The Happy Marriage of Redis and Protobuf by Scott Haines of Twilio - Redis Da...The Happy Marriage of Redis and Protobuf by Scott Haines of Twilio - Redis Da...
The Happy Marriage of Redis and Protobuf by Scott Haines of Twilio - Redis Da...
 
SQL, Redis and Kubernetes by Paul Stanton of Windocks - Redis Day Seattle 2020
SQL, Redis and Kubernetes by Paul Stanton of Windocks - Redis Day Seattle 2020SQL, Redis and Kubernetes by Paul Stanton of Windocks - Redis Day Seattle 2020
SQL, Redis and Kubernetes by Paul Stanton of Windocks - Redis Day Seattle 2020
 
Rust and Redis - Solving Problems for Kubernetes by Ravi Jagannathan of VMwar...
Rust and Redis - Solving Problems for Kubernetes by Ravi Jagannathan of VMwar...Rust and Redis - Solving Problems for Kubernetes by Ravi Jagannathan of VMwar...
Rust and Redis - Solving Problems for Kubernetes by Ravi Jagannathan of VMwar...
 
Redis for Data Science and Engineering by Dmitry Polyakovsky of Oracle
Redis for Data Science and Engineering by Dmitry Polyakovsky of OracleRedis for Data Science and Engineering by Dmitry Polyakovsky of Oracle
Redis for Data Science and Engineering by Dmitry Polyakovsky of Oracle
 
Practical Use Cases for ACLs in Redis 6 by Jamie Scott - Redis Day Seattle 2020
Practical Use Cases for ACLs in Redis 6 by Jamie Scott - Redis Day Seattle 2020Practical Use Cases for ACLs in Redis 6 by Jamie Scott - Redis Day Seattle 2020
Practical Use Cases for ACLs in Redis 6 by Jamie Scott - Redis Day Seattle 2020
 
Moving Beyond Cache by Yiftach Shoolman Redis Labs - Redis Day Seattle 2020
Moving Beyond Cache by Yiftach Shoolman Redis Labs - Redis Day Seattle 2020Moving Beyond Cache by Yiftach Shoolman Redis Labs - Redis Day Seattle 2020
Moving Beyond Cache by Yiftach Shoolman Redis Labs - Redis Day Seattle 2020
 
Leveraging Redis for System Monitoring by Adam McCormick of SBG - Redis Day S...
Leveraging Redis for System Monitoring by Adam McCormick of SBG - Redis Day S...Leveraging Redis for System Monitoring by Adam McCormick of SBG - Redis Day S...
Leveraging Redis for System Monitoring by Adam McCormick of SBG - Redis Day S...
 
JSON in Redis - When to use RedisJSON by Jay Won of Coupang - Redis Day Seatt...
JSON in Redis - When to use RedisJSON by Jay Won of Coupang - Redis Day Seatt...JSON in Redis - When to use RedisJSON by Jay Won of Coupang - Redis Day Seatt...
JSON in Redis - When to use RedisJSON by Jay Won of Coupang - Redis Day Seatt...
 
Highly Available Persistent Session Management Service by Mohamed Elmergawi o...
Highly Available Persistent Session Management Service by Mohamed Elmergawi o...Highly Available Persistent Session Management Service by Mohamed Elmergawi o...
Highly Available Persistent Session Management Service by Mohamed Elmergawi o...
 
Anatomy of a Redis Command by Madelyn Olson of Amazon Web Services - Redis Da...
Anatomy of a Redis Command by Madelyn Olson of Amazon Web Services - Redis Da...Anatomy of a Redis Command by Madelyn Olson of Amazon Web Services - Redis Da...
Anatomy of a Redis Command by Madelyn Olson of Amazon Web Services - Redis Da...
 
Building a Multi-dimensional Analytics Engine with RedisGraph by Matthew Goos...
Building a Multi-dimensional Analytics Engine with RedisGraph by Matthew Goos...Building a Multi-dimensional Analytics Engine with RedisGraph by Matthew Goos...
Building a Multi-dimensional Analytics Engine with RedisGraph by Matthew Goos...
 
RediSearch 1.6 by Pieter Cailliau - Redis Day Bangalore 2020
RediSearch 1.6 by Pieter Cailliau - Redis Day Bangalore 2020RediSearch 1.6 by Pieter Cailliau - Redis Day Bangalore 2020
RediSearch 1.6 by Pieter Cailliau - Redis Day Bangalore 2020
 
RedisGraph 2.0 by Pieter Cailliau - Redis Day Bangalore 2020
RedisGraph 2.0 by Pieter Cailliau - Redis Day Bangalore 2020RedisGraph 2.0 by Pieter Cailliau - Redis Day Bangalore 2020
RedisGraph 2.0 by Pieter Cailliau - Redis Day Bangalore 2020
 
RedisTimeSeries 1.2 by Pieter Cailliau - Redis Day Bangalore 2020
RedisTimeSeries 1.2 by Pieter Cailliau - Redis Day Bangalore 2020RedisTimeSeries 1.2 by Pieter Cailliau - Redis Day Bangalore 2020
RedisTimeSeries 1.2 by Pieter Cailliau - Redis Day Bangalore 2020
 
RedisAI 0.9 by Sherin Thomas of Tensorwerk - Redis Day Bangalore 2020
RedisAI 0.9 by Sherin Thomas of Tensorwerk - Redis Day Bangalore 2020RedisAI 0.9 by Sherin Thomas of Tensorwerk - Redis Day Bangalore 2020
RedisAI 0.9 by Sherin Thomas of Tensorwerk - Redis Day Bangalore 2020
 
Rate-Limiting 30 Million requests by Vijay Lakshminarayanan and Girish Koundi...
Rate-Limiting 30 Million requests by Vijay Lakshminarayanan and Girish Koundi...Rate-Limiting 30 Million requests by Vijay Lakshminarayanan and Girish Koundi...
Rate-Limiting 30 Million requests by Vijay Lakshminarayanan and Girish Koundi...
 
Three Pillars of Observability by Rajalakshmi Raji Srinivasan of Site24x7 Zoh...
Three Pillars of Observability by Rajalakshmi Raji Srinivasan of Site24x7 Zoh...Three Pillars of Observability by Rajalakshmi Raji Srinivasan of Site24x7 Zoh...
Three Pillars of Observability by Rajalakshmi Raji Srinivasan of Site24x7 Zoh...
 
Solving Complex Scaling Problems by Prashant Kumar and Abhishek Jain of Myntr...
Solving Complex Scaling Problems by Prashant Kumar and Abhishek Jain of Myntr...Solving Complex Scaling Problems by Prashant Kumar and Abhishek Jain of Myntr...
Solving Complex Scaling Problems by Prashant Kumar and Abhishek Jain of Myntr...
 

Recently uploaded

EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
Earley Information Science
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
vu2urc
 

Recently uploaded (20)

Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your Business
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 

RedisConf18 - Writing modular & encapsulated Redis code

  • 1. Writing modular & encapsulated Redis code Jim Nelson jnelson@archive.org Internet Archive
  • 2. Internet Archive Universal Access to All Knowledge ● Founded 1996, based in San Francisco ● Archive of digital and physical media ● Includes Web, books, music, film, software & more ● Digital holdings: over 35 petabytes & counting ● Over 1 million users per day ● Key collections & services: ○ Wayback Machine ○ Grateful Dead live concert collection ○ The Emularity: in-browser emulation of 8-bit software, IBM PC, Mac, & now handhelds like Speak-and-Spell
  • 3. Internet Archive ♡ Redis ● Caching & directory lookup backed by 10-node sharded Redis cluster ● Sharding performed client-side via consistent hashing (PHP, Predis) ● Each node supported by two replicated mirrors (fail-over) ● Specialized Redis instances also used throughout IA’s services, including ○ Wayback Machine ○ Search (item metadata and full-text) ○ Administrative & usage tracking ○ …and more
  • 4. Before we get started ● I’m assuming you’ve written Redis client code already ● You don’t need to know the exact details of the Redis protocol… ● …but you need some understanding of how Redis “works” ○ single-threaded ○ different data types (strings, hash, sorted sets, etc.) ○ transactions (MULTI/EXEC) This presentation uses PHP sample code and Predis (a PHP Redis client) but most concepts presented should work in other languages & clients
  • 6. “Baby, where have you been my whole life?” ● Quick, quick, quick ○ Quick to code ○ Quick to debug ○ Quick response times ○ Quick to model data ● Logical, well-defined command structure ● Atomicity ● Flexibility over rigor Learning only a handful of straightforward concepts, I felt I had a solid working understanding of Redis
  • 8. “It’s complicated.” (As in, Facebook-it’s-complicated.) ● Application code mixed with data model code ○ Little-to-no separation between data logic and business logic ● Relational data often sprayed across separate Redis buckets ○ No clean way to normalize or deserialize complex objects if they’re sharded across keys ● Performance considerations trump clean coding practices ○ Modularity ignored in order to minimize round-trips to server
  • 9. The temptation of Redis is that it looks like a collections framework.
  • 10. What’s missing? All the things I know and love about designing clean code. Modularity Encapsulation Readability
  • 11. What is encapsulation? Encapsulation “says that a program should be divided into modules that hide data structures behind an interface of procedure calls. The purpose of this is to allow you to change the underlying data structure without causing a large ripple effect across the system.” –Martin Fowler, martinfowler.com
  • 13. Sending & receiving data Redis (and most Redis clients) offer different modes of operation: ● Immediate mode ● Pipeline mode ● Transaction mode ● Atomic pipeline mode
  • 14. Immediate mode Send one command, receive one response. That’s it. No, really. $redis = new PredisClient(); $name = $redis->get(“user:$id:name”); // sends GET, recvs name $email = $redis->get(“user:$id:email”); // sends GET, recvs email $login_count = $redis->incr(“user:$id:logins”) // sends INCR, recvs incremented number Simple enough!
  • 15. Pipeline mode ● Collect (or schedule) several commands on the client side ● Send all commands in one burst ● Receive all responses in one burst $redis = new PredisClient(); $pipeline = $redis->pipeline(); // no network I/O $pipeline->get(“user:$id:name”); // no network I/O $pipeline->get(“user:$id:email”); // no network I/O $pipeline->incr(“user:$id:logins”);// no network I/O $result = $pipeline->execute(); // sends GET/GET/INCR, recvs name, email, login Single round-trip to the server but operations are not atomic
  • 16. Transactions ● MULTI command opens transaction ● Send one or more commands ● EXEC closes transaction & receives all results ● Operations execute atomically $redis = new PredisClient(); $transaction = $redis->transaction(); // sends MULTI $transaction->get(“user:$id:name”); // sends GET, recvs QUEUED $transaction->get(“user:$id:email”); // sends GET, recvs QUEUED $transaction->incr(“user:$id:logins”);// sends INCR, recvs QUEUED $result = $transaction->execute(); // sends EXEC, recvs name, email, login (post-INCR) Each operation is a separate round-trip to the server, including the MULTI and EXEC
  • 17. Atomic pipelines ● Schedule several commands on the client side ● All commands are “wrapped” in a MULTI/EXEC ● Send all commands in one burst, receive responses on one burst ● Operations execute atomically $redis = new PredisClient(); $atomic = $redis->pipeline([‘atomic’ => true]); $atomic->get(“user:$id:name”); // no network I/O $atomic->get(“user:$id:email”); // no network I/O $atomic->incr(“user:$id:logins”);// no network I/O $result = $atomic->execute(); // sends MULTI/GET/GET/INCR/EXEC, recvs name, email, login Atomic pipelines generally are more efficient than raw transactions
  • 20. In the name of minimizing round-trips /** * A do-everything function so opening a user session is a single * round-trip to the Redis server. */ function open_user_session($redis, $userid, $emoji_id, $notifications) { $pipe = $redis->pipeline(); $pipe->get(“user:$userid:name”); $pipe->get(“user:$userid:member_of”); $pipe->hmget(“emoji:$emoji_id”, “tag”, “name”, “image”); $today = date(“Y-m-d”); $pipe->incr(“logins:$today”); $pipe->set(“session:$userid”, time(), “EX”, 600, “NX”); $pipe->sadd(“members:logged_in”, $userid); $results = $pipe->execute();
  • 21. Do-everything functions break encapsulation ● Details of data layout in Redis is exposed ● Changing data structures means changing lots of code— the “ripple effect” ● Encourages code duplication—tempted to Ctrl+C / Ctrl+V ● Difficult to refactor ● Difficult to maintain Passing pipeline object to other functions has fundamental problem: How does the scheduling code receive the results of its read operation(s)?
  • 22. Transforming & normalizing results /** * Do-everything function continued. * * In Predis (and other clients) transaction and pipeline * results are returned in a numeric-indexed array. */ function open_user_session($redis, $id, $emoji_id, $notifications) { // … pipeline is executed as in previous slide … $user = new User($results[0]); $user->setMembershipObject(Membership::fromString($results[1])); $emoji = new Emoji($results[2][1], $results[2][0]); $emoji->setImage(base64_decode($results[2][2])); $user->setEmoji($emoji); StatsD::gauge(‘login_count’, (int) $results[3]); if ($results[4] != null) $notifications->sessionExtended($id); if ($results[5] > 0) Syslog::notice(“$id already logged in”); return $user; }
  • 23. Commands are detached from result processing ● Results array is ordered in command-order ● Adding a new command changes index of all results which follow ● Difficult to associate commands with their results ● $results[3][0] is not friendly ● Redis data types don’t necessarily correspond to language data types ● string(“0”) is not FALSE, NULL, nil, undefined, or even an integer in all languages Even if the code is all contained in a single function, it’s difficult to match the Redis command with its result(s)
  • 24. The full function function open_user_session($redis, $id, $emoji_id, $notifications) { $pipe = $redis->pipeline(); $pipe->get(“user:$id:name”); $pipe->get(“user:$id:member_of”); $pipe->hmget(“emoji:$emoji_id”, “tag”, “name”, “image”); $today = date(“Y-m-d”); $pipe->incr(“logins:$today”); $pipe->set(“session:$id”, “EX”, 600, “NX”); $pipe->sadd(“members:logged_in”, $id); $results = $pipe->execute(); $user = new User($results[0]); $user->setMembershipObject(Membership::fromString($results[1])); $emoji = new Emoji($results[2][1], $results[2][0]); $emoji->setImage(base64_decode($results[2][2])); $user->setEmoji($emoji); StatsD::gauge(‘login_count’, (int) $results[3]); if ($results[4] != null) $notifications->sessionExtended($id); if ($results[5] > 0) Syslog::notice(“$id already logged in”); return $user; }
  • 25. In a clean world, the do-everything function would be broken up into functions, each bite-sized Redis operation(s).
  • 26. The hard reality: Production-scale requirements often require packing operations in pipelines.
  • 27. The hard reality: Production-scale requirements often create balls of code mud.
  • 29. Promises & futures First developed in the 1970s “Rediscovered” in the 2000s for Web Sometimes called “deferred programming” The Big Concept: Bind a costly scheduled operation with its resulting value(s) upon completion
  • 30. Promises & futures Here’s how I’m using these terms: A Promise object schedules the operation. The Promise returns a placeholder object for each operation. The object “receives” the result when the operation completes. That object is a Future.
  • 31. Promises & futures with Redis Going back to a key concept: Redis pipelines and transactions “schedule” work to be performed later. $pipeline = $redis->pipeline(); // no network I/O $pipeline->get(“user:$id:name”); // no network I/O $pipeline->get(“user:$id:email”); // no network I/O $pipeline->incr(“user:$id:logins”);// no network I/O $result = $pipeline->execute(); // sends GET/GET/INCR, recvs name, email, login This matches perfectly with the promises/futures approach
  • 32. Promises & futures with Redis Your Redis client might already have support! A little searching turned up libraries for NodeJS & Scala I’m sure there’s more—this is not an exotic concept.
  • 34. Deferred: Promises & futures for PHP/Predis Programming with Deferred almost looks like coding with Predis: $redis = new PredisClient(); $promises = new DeferredPromisesPipeline($redis); $get_name = $promises->get(“user:$id:name”); // returns DeferredFuture $hgetall_emoji = $promises->hgetall(“emoji:$emoji_id”); // returns DeferredFuture $promises->execute(); // network I/O happens here $name = $get_name->value(); // string $emoji = $hgetall_emoji->value(); // HGETALL array
  • 35. Scheduling commands with Deferred Redis commands return placeholder Future objects: $get_name = $promises->get(“user:$id:name”); // DeferredFuture $hgetall_emoji = $promises->hgetall(“emoji:$emoji_id”); // DeferredFuture Futures hold no value until the Promises object is executed. In promises/futures parlance, the Futures are unfulfilled.
  • 36. Fulfilling operations with Deferred Executing a Promises object fulfills the Future objects: $promises->execute(); // network I/O happens here (“fulfilled”) $name = $get_name->value(); // string $emoji = $hgetall_emoji->value(); // HGETALL array Once fulfilled, the resulting value may be retrieved with the value() method.
  • 37. Transforming results Often we want to normalize or deserialize Redis results. Futures allows for transformations: // DeferredFuture $username = $promises->get(“user:$id:name”); // reverse the user’s name upon fulfillment $username->transform(“strrev”); // strrev() is a PHP function // network I/O -- strrev() is executed here $promises->execute(); // user’s name backwards echo $username->value();
  • 38. Transformations: A more interesting example Use transformations to fetch objects instead of arrays of strings: // $emoji is the DeferredFuture $emoji = $promises->hmget(“emoji:$id”, “name”, “tag”, “image”); // $hmget is an array of values $emoji->transform(function ($hmget) { $e = new Emoji($hmget[0], $hmget[1]); $e->load_image(base64_decode($hmget[2])); return $e; // returns an initialized Emoji object }); $promises->execute(); // network I/O -- transformation happens here return $emoji->value(); // returns the initialized Emoji object
  • 39. Stacking transformations Multiple transformations may be registered. They are processed in order: function emoji_image_binary($promises, $id) { $image_future = $promises->hget(“emoji:$id”, “image”); $image_future->transform(function ($hget) { return base64_decode($hget); // this transformation is first }); return $image_future; } $image_md5_future = emoji_image_binary($promises, ‘happy-face’); $image_md5_future->transform(function ($bin) { return md5sum($bin); // this transformation is second });
  • 40. Make HMGET look like HGETALL In Predis, HGETALL returns a keyed array (or a map): $redis->hgetall(“profile:jim”) // [ “name” => “Jim”, “emoji” => “:elephant:” ] while HMGET returns an indexed array: $redis->hmget(“profile:jim”, “name”, “emoji”) // [ “Jim”, “:elephant:” ] If you have a function which uses HMGET in one code path and HGETALL in another, transformations can smooth out the difference.
  • 41. Make HMGET look like HGETALL function getProfile($promises, $username, array $fields = null) { if (!$fields) { $future = $promises->hgetall(“profile:$username”); } else { $future = $promises->hmget(“profile:$username”, $fields); $future->transform(function ($hmget) use ($fields) { return array_combine($fields, $hmget); }); } return $future; }
  • 42. Binding Binding allows for transferring the final result to a PHP variable: $username = null; $future = $promises->get(“user:$id:name”); $future->bind($username); // more conveniently (identical to prior 2 lines) $promises->get(“user:$id:name”)->bind($username); $promises->execute(); // $username is now set to a string return $username;
  • 43. Transformations + binding $profile = null; // get raw user profile data with HGETALL $future = $promises->hgetall(“user:$id:profile”); $future->transform(function ($hgetall) { // initialize UserProfile with HGETALL results return new UserProfile($hgetall[“name”], $hgetall[“location”], $hgetall[“email”]); }); // bind UserProfile object to $profile variable $future->bind($profile); // fulfill $promises->execute(); Transformations are always done first, then binding
  • 44. Reducing Multiple Futures may be reduced to a single Future: $registered = $promises->sismember(“users:registered”, $userid); $validated = $promises->sismember(“users:validated”, $userid); $confirmed = $promises->sismember(“users:confirmed”, $userid); // reduce all three SISMEMBERs to a single Future $is_ready = $promises->reduce($registered, $validated, $confirmed); // returns a DeferredFuture $is_ready->transform(function (array $results) { return $results[0] && $results[1] && $results[2]; }); $promises->execute(); return $is_ready->value(); // returns boolean
  • 45. Putting it all together
  • 46. The full function function open_user_session($redis, $id, $emoji_id, $notifications) { $pipe = $redis->pipeline(); $pipe->get(“user:$id:name”); $pipe->get(“user:$id:member_of”); $pipe->hmget(“emoji:$emoji_id”, “tag”, “name”, “image”); $today = date(“Y-m-d”); $pipe->incr(“logins:$today”); $pipe->set(“session:$id”, “EX”, 600, “NX”); $pipe->sadd(“members:logged_in”, $id); $results = $pipe->execute(); $user = new User($results[0]); $user->set_membership_object(Membership::from_string($results[1])); $emoji = new Emoji($results[2][1], $results[2][0]); $emoji->set_image(base64_decode($results[2][2])); $user->set_emoji($emoji); StatsD::gauge(‘login_count’, (int) $results[3]); if ($results[4] != null) $notifications->session_extended($id); if ($results[5] > 0) Syslog::notice(“$id already logged in”); return $user; }
  • 47. class User // Returns DeferredFuture for User instance static function getFuture($promises, $id) { $name = $promises->get(“user:$id:name”) $member_of = $promises->get(“user:$id:member_of”); $user_future = $promises->reduce($name, $member_of)->transform($results) { $user = new User($results[0]); $user->set_membership_object(Membership::from_string($results[1]); return $user; }); return $user_future; }
  • 48. class Emoji // Returns DeferredFuture for Emoji instance static function getFuture($promises, $emoji_id) { $emoji_future = $promises->hmget(“emoji:$emoji_id”, “name”, “tag”, “image”); $emoji_future->transform(function ($hmget) { $emoji = new Emoji($hmget[0], $hmget[1]); $emoji->set_image(base64_decode($hmget[2]); return $emoji; }); return $emoji_future; }
  • 49. class Session // Starts the session on the Redis cache and notifies if extended function startSession($promises, $userid, $notifications) { $set_future = $promises->set(“session:$userid”, “EX”, 600, “NX”); $set_future->onFulfilled(function ($set) use ($userid, $notifications) { if ($set !== null) $notifications->notify_session_extended($userid); }); return $set_future; }
  • 50. The full function (revised) function open_user_session($redis, $userid, $emoji_id, $notifications) { $promises = new DeferredPromisesPipeline($redis); $user = null; User::getFuture($promises, $userid)->bind($user); Emoji::getFuture($promises, $emoji_id)->onFulfilled(function ($emoji) use (&$user) { $user->set_emoji($emoji); }); Session::create()->startSession($promises, $userid, $notifications); LoginTracker::increment($promises); Members::setLoggedIn($promises, $userid); $promises->execute(); return $user;
  • 51. “Show me the code.” https://github.com/internetarchive/deferred