react/cache in use
Recently we, ReactPHP, released 0.5 of our cache package with
TTL and other PSR-16
similarities. In this post we'll go over which packages
I recently updated and how I am using them in my sites.
JSON and msgpack
The JSON
and msgpack
packages are decorators around other CacheInterface
implementors to
encode/decode data when writing/reading from the decorated cache. Where the JSON
package is useful for writing arrays
to for example Redis
, it chokes in non-UTF-8 data. This is where msgpack
comes in handy,
it encodes pretty much anything (in my experience) and writes to the decorated cache.
// Stores everything in `ArrayCache` JSON encoded
$cache = new Json(new ArrayCache());
Redis
While react/cache
ships with the in memory ArrayCache
it only lasts as long as the PHP process is alive. As a more permanent cache I use Redis
. With
it's native support for TTL
it works well for my use cases. It is build
on clue/redis-react
and needs a connected redis client to work.
$factory->createClient()->then(function (Client $client) {
$cache = new Redis($client, 'cache:key:prefix:', /** Optional TTL value here overwriting set passed value */);
});
Fallback
The fallback
package has been specifically designed to be
a building block for keeping the most recent items in memory while offloading the rest to Redis
or any other longer
term storage. But it call be used with any cache pair.
There are a few ways to use it, one of them is to start with an empty cache and slowly populate it from your fallback cache (redis in this case) and let is slowly cache everything fetched. Bear in mind that this only works well when the cache holds a small finite number of items as otherwise your memory usage will explode.
$cache = new Fallback(
new ArrayCache(),
new Redis($redis, 'cache:key:')
);
react/http session middleware
Due to ReactPHP's nature you can't use $_SESSION
so I had to implement my own session handling middleware
to have sessions in my sites. To store sessions it accepts a CacheInterface
implementation leaving the storage details
up to the user.
The following bit of code is the session configuration for WyriMaps
which ultimately
stores sessions in Redis
but covers a few packages we discussed above. First of all it uses the fallback cache to
deal with the array cache which is configured to only hold the 5 latest items (LRU
)
in memory. When a get
returns a null
it falls back to the JSON
cache which wraps the Redis
cache to see if it
can be found in the more permanent cache storage. The main reason to wrap only the Redis
cache with the JSON
cache
and not wrap JSON
around the fallback cache is that we only JSON
encode/decode interactions with Redis
.
new SessionMiddleware(
'VolJinForWarchief',
new Fallback(
new ArrayCache(5),
new Json(
new RedisCache($redis, 'cache:sessions:', 604800)
)
),
[/** Cookie settings */]
);
react/http webroot preload middleware
WyriMaps
, like most sites, also serves CSS
, JavaScript
, fonts, images, and more.
To do that in a simple way I've crafted wyrihaximus/react-http-middleware-webroot-preload
which reads all file contents from the given webroot and loads that in memory to super fast access (below 1ms internally).
But the site is under active development and to keep everything working for users already on the site I need to keep a
few version of the JavaScript
files available for a while. As a result the amount of files in memory keep growing and
growing, so to mitigate that only the latest requested files are kept in memory. The rest of the files are encoded with
msgpack
stored into redis. The reason for using msgpack
and not JSON
to store the array holding the contents and mime
type is that PHP
's JSON
encoder chokes on anything not UTF-8, which binary files easily can be, msgpack
doesn't
has issues with that.
new WebrootPreloadMiddleware(
'/var/wwww/public/',
$logger,
new Fallback(
new ArrayCache(33),
new Msgpack(
new RedisCache($redis, 'cache:' . $version . ':webrootpreload:', 60 * 60 * 24 * 31 * 6)
)
)
);
Conclusion
My react/cache
packages allow me to keep the memory usage of my ReactPHP applications low while still have resources
previously cached in memory with in reach. For some apps the memory dropped nearly 10MB implementing the fallback to
redis setup by sacrificing a few ms of response time for those static resources in redis and keeping it at below 1ms
for those in memory. 🍦🍦🍦