From 871e7609b54869b6fdcb48c070d7f480e3134c2e Mon Sep 17 00:00:00 2001 From: Marc Zych Date: Tue, 13 Jan 2015 10:20:18 -0800 Subject: [PATCH 01/10] Post: Matryoshka I figured it's about time I wrote a blog post about Matryoshka explaining why we wrote it and how we use it. Any feedback is appreciated! --- ...ka-configurable-caching-library-for-php.md | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 _posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md diff --git a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md new file mode 100644 index 0000000..25cd521 --- /dev/null +++ b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md @@ -0,0 +1,147 @@ +--- +layout: with-comments +title: "Matryoshka: A Configurable Caching Library for PHP" +author: Marc Zych +author_url: https://github.com/marczych +summary: We recently open-sourced Matryoshka, a configurable caching library + for PHP which makes common operations easier and allows for on-the-fly + configuration. +--- + +Like most websites, we make heavy use of caching to reduce load on our servers and decrease page response times. +Our caching daemon of choice is [memcached]. +The PHP extensions are certainly usable and provide all of the core functionality that you could need. +However, we use a lot of patterns to make our day to day caching much easier that aren't provided by the extensions. + +In comes [Matryoshka], an open source caching library for PHP which makes common operations easier and allows for on-the-fly configuration. + +# Configurable Behavior + +Matryoshka is designed to be very configurable. +You can add functionality on the fly simply by wrapping an existing `Backend` with a new one. +We use this extensively to prefix keys, modify expiration times, disable gets, gather metrics, etc. + +Start off with a `Memcache` instance: + +{% highlight php startinline=true %} +// From the native extension. +$memcache = new Memcache(); +$memcache->pconnect('localhost', 11211); +$cache = Matryoshka\Memcache::create($memcache); +{% endhighlight %} + +Then prefix all the keys: + +{% highlight php startinline=true %} +$prefixedCache = new Matryoshka\Prefix($cache, 'prefix-'); +// The key ends up being "prefix-key". +$prefixedCache->set('key', 'value'); +$value = $prefixedCache->get('key'); +{% endhighlight %} + +Finally double all expiration times for the prefixed backend: + +{% highlight php startinline=true %} +$doubleExpiration = function($expiration) { + return $expiration * 2; +}; +$cache = new Matryoshka\ExpirationChange($prefixedCache, + $doubleExpiration); +// Results in an expiration time of 20 for "prefix-key". +$cache->set('key', 'value', 10); +{% endhighlight %} + +By using composition, caching configurations can be assembled on the fly quite easily. +The core API is identical for all backends so the caller doesn't need to be aware of the exact configuration. +Common configurations and base backends (like memcached connections) can be made into singletons or provided using dependency injection in your application. +Additionally, this architecture results in very maintainable and testable code because each class has exactly one job. + +# Scopes + +Cache invalidation is hard. +To make it easier, Matryoshka provides "cache scopes" to invalidate a group of keys at once. +[This works][Scope.php] by prefixing all keys with a unique value that is stored in the backend using the scope name. + +{% highlight php startinline=true %} +$cache = new Matryoshka\Scope($memcachedBackend, 'name'); + +// This results in a get request to memcached for 'scope-name' +// which results in something like '0fb4ae36'. This `set` call +// then results in a key of '0fb4ae36-key'. +$cache->set('key', 'value'); +$cache->set('key2', 'value2'); +$value = $cache->get('key'); // '0fb4ae36-key2' => 'value' + +// Deleting the scope results in a new scope value e.g. 'e093f71e'. +$cache->deleteScope(); + +// Both of these result in a miss because the scope has a new +// value so the keys are now prefixed with 'e093f71e-'. +$value = $cache->get('key'); // 'e093f71e-key' => false +$value2 = $cache->get('key2'); // 'e093f71e-key2' => false +{% endhighlight %} + +We have found this to be particularly useful for scoping keys to code deploys. +We simply put any caches that should be invalidated under the `'deploy'` scope which is deleted anytime we deploy code. +You can also have dynamic scopes such as `"post-{$postid}"` which can be cleared anytime a specific post is modified. +This is an implementation of [generational caching] which we make [heavy use of][caching thesis] throughout our application. + +Cache scopes help with cache invalidation but unfortunately don't make [naming things] any easier. + +# Helper Functions + +Matryoshka adds a few helper functions to make common operations easier. +`getAndSet` makes populating the cache dead simple: + +{% highlight php startinline=true %} +$cache = new Matryoshka\Ephemeral(); +// Calls the provided callback if the key is not found and sets +// it in the cache before returning the value to the caller. +$value = $cache->getAndSet('key', function() { + return 'value'; +}); +{% endhighlight %} + +Similarly, `getAndSetMultiple` makes doing multi-gets significantly easier: + +{% highlight php startinline=true %} +$cache = new Matryoshka\Ephemeral(); +// Array of key => id. The id's can be anything used to identify +// the resource that the key represents. +$keys = [ + 'key1' => ['id1a', 'id1b'], + 'key2' => ['id2a', 'id2b'] +]; +// Calls the provided callback for any missed keys so the missing +// values can be generated and set before returning them to the +// caller. The values are returned in the same order as the +// provided keys. +$values = $cache->getAndSetMultiple($keys, function($missing) { + // Use the id's to fill in the missing values. + foreach ($missing as $key => $primaryKey) { + $missing[$key] = getValueFromDb($primaryKey); + } + + // Return the new values to be cached and merged with the hits. + return $missing; +}); +{% endhighlight %} + +# Try it out! + +You can install Matryoshka with composer from [Packagist] or by cloning the [repo][Matryoshka] into your project. +A complete list of backends as well as more examples are available in the [readme]. +[memcached], specifically the [Memcache extension], is the only supported caching daemon right now but adding others is very easy. +We encourage you to try it out and contribute any caching techniques that you find useful in your own applications. + +Happy caching! + +[memcached]: http://memcached.org/ +[Matryoshka]: https://github.com/iFixit/Matryoshka +[readme]: https://github.com/iFixit/Matryoshka#readme +[Scope.php]: https://github.com/iFixit/Matryoshka/blob/master/library/iFixit/Matryoshka/Scope.php +[naming things]: http://martinfowler.com/bliki/TwoHardThings.html +[Packagist]: https://packagist.org/packages/ifixit/matryoshka +[Memcache extension]: http://php.net/manual/en/book.memcache.php +[generational caching]: https://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works +[caching thesis]: http://digitalcommons.calpoly.edu/theses/1002/ From 945907d78e752b4b6cdced4dcf1cc5a39349cc51 Mon Sep 17 00:00:00 2001 From: Marc Zych Date: Tue, 13 Jan 2015 11:21:30 -0800 Subject: [PATCH 02/10] Matryoshka post: Fix scope key comment --- ...015-01-13-matryoshka-configurable-caching-library-for-php.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md index 25cd521..1d002c9 100644 --- a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md +++ b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md @@ -70,7 +70,7 @@ $cache = new Matryoshka\Scope($memcachedBackend, 'name'); // then results in a key of '0fb4ae36-key'. $cache->set('key', 'value'); $cache->set('key2', 'value2'); -$value = $cache->get('key'); // '0fb4ae36-key2' => 'value' +$value = $cache->get('key'); // '0fb4ae36-key' => 'value' // Deleting the scope results in a new scope value e.g. 'e093f71e'. $cache->deleteScope(); From 36e3e33b3051a5d9e53363ea15140c1893073527 Mon Sep 17 00:00:00 2001 From: Marc Zych Date: Tue, 13 Jan 2015 13:29:02 -0800 Subject: [PATCH 03/10] Matryoshka: Remove Ephemeral reference It's not necessary and might lead to confusion. --- ...2015-01-13-matryoshka-configurable-caching-library-for-php.md | 1 - 1 file changed, 1 deletion(-) diff --git a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md index 1d002c9..cce9c87 100644 --- a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md +++ b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md @@ -105,7 +105,6 @@ $value = $cache->getAndSet('key', function() { Similarly, `getAndSetMultiple` makes doing multi-gets significantly easier: {% highlight php startinline=true %} -$cache = new Matryoshka\Ephemeral(); // Array of key => id. The id's can be anything used to identify // the resource that the key represents. $keys = [ From 37965d32c33ab3c06929b54d5baf025b7f442b25 Mon Sep 17 00:00:00 2001 From: Marc Zych Date: Tue, 13 Jan 2015 13:33:50 -0800 Subject: [PATCH 04/10] Matryoshka: Update keys for multi-get example --- ...5-01-13-matryoshka-configurable-caching-library-for-php.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md index cce9c87..26db291 100644 --- a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md +++ b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md @@ -108,8 +108,8 @@ Similarly, `getAndSetMultiple` makes doing multi-gets significantly easier: // Array of key => id. The id's can be anything used to identify // the resource that the key represents. $keys = [ - 'key1' => ['id1a', 'id1b'], - 'key2' => ['id2a', 'id2b'] + 'key-1-a' => [1, 'a'], + 'key-2-b' => [2, 'b'] ]; // Calls the provided callback for any missed keys so the missing // values can be generated and set before returning them to the From c4023f06aa9a4a8c271c5f483768b619fa2ebe79 Mon Sep 17 00:00:00 2001 From: Marc Zych Date: Tue, 13 Jan 2015 13:35:26 -0800 Subject: [PATCH 05/10] Style: Increase inline code font size It was noticeably smaller than the rest of the body text which was distracting. --- stylesheets/styles.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stylesheets/styles.css b/stylesheets/styles.css index 5cd1c10..b52fc86 100644 --- a/stylesheets/styles.css +++ b/stylesheets/styles.css @@ -77,6 +77,10 @@ code, pre, .gist{ line-height:1.4em; } +p code { + font-size: 14px; +} + pre { padding:8px 15px; background: #f8f8f8; From 253b9df7138aafded4460ec163a4926ba9e54910 Mon Sep 17 00:00:00 2001 From: Marc Zych Date: Wed, 14 Jan 2015 14:49:37 -0800 Subject: [PATCH 06/10] Matryoshka: id's => ids --- ...5-01-13-matryoshka-configurable-caching-library-for-php.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md index 26db291..8a6d995 100644 --- a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md +++ b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md @@ -105,7 +105,7 @@ $value = $cache->getAndSet('key', function() { Similarly, `getAndSetMultiple` makes doing multi-gets significantly easier: {% highlight php startinline=true %} -// Array of key => id. The id's can be anything used to identify +// Array of key => id. The ids can be anything used to identify // the resource that the key represents. $keys = [ 'key-1-a' => [1, 'a'], @@ -116,7 +116,7 @@ $keys = [ // caller. The values are returned in the same order as the // provided keys. $values = $cache->getAndSetMultiple($keys, function($missing) { - // Use the id's to fill in the missing values. + // Use the ids to fill in the missing values. foreach ($missing as $key => $primaryKey) { $missing[$key] = getValueFromDb($primaryKey); } From a7212109b2efb60cd206571629bc0ea8549ac546 Mon Sep 17 00:00:00 2001 From: Marc Zych Date: Mon, 19 Jan 2015 18:46:43 -0800 Subject: [PATCH 07/10] Matryoshka: Remove straggling Ephemeral reference --- ...2015-01-13-matryoshka-configurable-caching-library-for-php.md | 1 - 1 file changed, 1 deletion(-) diff --git a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md index 8a6d995..f767899 100644 --- a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md +++ b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md @@ -94,7 +94,6 @@ Matryoshka adds a few helper functions to make common operations easier. `getAndSet` makes populating the cache dead simple: {% highlight php startinline=true %} -$cache = new Matryoshka\Ephemeral(); // Calls the provided callback if the key is not found and sets // it in the cache before returning the value to the caller. $value = $cache->getAndSet('key', function() { From 30c4e257dc9ae22863bf7e7eee34a7c522dbb9e8 Mon Sep 17 00:00:00 2001 From: Taylor Arnicar Date: Tue, 20 Jan 2015 13:42:06 -0800 Subject: [PATCH 08/10] Minor grammar and phrasing fixes Marc says this is plenty of changes for one commit. --- ...oshka-configurable-caching-library-for-php.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md index f767899..7772609 100644 --- a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md +++ b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md @@ -3,7 +3,7 @@ layout: with-comments title: "Matryoshka: A Configurable Caching Library for PHP" author: Marc Zych author_url: https://github.com/marczych -summary: We recently open-sourced Matryoshka, a configurable caching library +summary: We recently open-sourced Matryoshka: a configurable caching library for PHP which makes common operations easier and allows for on-the-fly configuration. --- @@ -13,13 +13,13 @@ Our caching daemon of choice is [memcached]. The PHP extensions are certainly usable and provide all of the core functionality that you could need. However, we use a lot of patterns to make our day to day caching much easier that aren't provided by the extensions. -In comes [Matryoshka], an open source caching library for PHP which makes common operations easier and allows for on-the-fly configuration. +One tool that we wrote is [Matryoshka]: an open source caching library for PHP which makes common operations easier and allows for on-the-fly configuration. # Configurable Behavior Matryoshka is designed to be very configurable. You can add functionality on the fly simply by wrapping an existing `Backend` with a new one. -We use this extensively to prefix keys, modify expiration times, disable gets, gather metrics, etc. +We use this extensively to prefix keys, modify expiration times, disable cache gets, gather metrics, etc. Start off with a `Memcache` instance: @@ -83,10 +83,14 @@ $value2 = $cache->get('key2'); // 'e093f71e-key2' => false We have found this to be particularly useful for scoping keys to code deploys. We simply put any caches that should be invalidated under the `'deploy'` scope which is deleted anytime we deploy code. -You can also have dynamic scopes such as `"post-{$postid}"` which can be cleared anytime a specific post is modified. -This is an implementation of [generational caching] which we make [heavy use of][caching thesis] throughout our application. -Cache scopes help with cache invalidation but unfortunately don't make [naming things] any easier. +Cache keys can also have dynamic scopes. +In a generic example of a blog with posts, your scope name could be `"post-{$postid}"`. +Then, all keys using a particular `$postid` prefix can be cleared anytime that specific post is modified. + +Cache scopes are an implementation of [generational caching], which we make [heavy use of][caching thesis] throughout our application. + +Note: Cache scopes help with cache invalidation but they unfortunately don't make [naming things] any easier. # Helper Functions From 726a5019f93dc05f8dd7fe9687668a5fb319f1ef Mon Sep 17 00:00:00 2001 From: Taylor Arnicar Date: Tue, 20 Jan 2015 13:44:45 -0800 Subject: [PATCH 09/10] Just some day-to-day grammar fixes Because grammar. --- ...015-01-13-matryoshka-configurable-caching-library-for-php.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md index 7772609..397c5c3 100644 --- a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md +++ b/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md @@ -11,7 +11,7 @@ summary: We recently open-sourced Matryoshka: a configurable caching library Like most websites, we make heavy use of caching to reduce load on our servers and decrease page response times. Our caching daemon of choice is [memcached]. The PHP extensions are certainly usable and provide all of the core functionality that you could need. -However, we use a lot of patterns to make our day to day caching much easier that aren't provided by the extensions. +However, we use a lot of patterns to make our day-to-day caching much easier that aren't provided by the extensions. One tool that we wrote is [Matryoshka]: an open source caching library for PHP which makes common operations easier and allows for on-the-fly configuration. From 78a826e22efa0026f38a224a799e8b864d6bc089 Mon Sep 17 00:00:00 2001 From: Marc Zych Date: Tue, 20 Jan 2015 14:00:06 -0800 Subject: [PATCH 10/10] Matryoshka: Update publish date --- ...2015-01-20-matryoshka-configurable-caching-library-for-php.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename _posts/{2015-01-13-matryoshka-configurable-caching-library-for-php.md => 2015-01-20-matryoshka-configurable-caching-library-for-php.md} (100%) diff --git a/_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md b/_posts/2015-01-20-matryoshka-configurable-caching-library-for-php.md similarity index 100% rename from _posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md rename to _posts/2015-01-20-matryoshka-configurable-caching-library-for-php.md