Skip to content

Commit

Permalink
MDL-63127 cachestore_redis: Implemented compression
Browse files Browse the repository at this point in the history
  • Loading branch information
roperto authored and mdjnelson committed Oct 7, 2019
1 parent 6ec1078 commit cda3e43
Show file tree
Hide file tree
Showing 4 changed files with 565 additions and 5 deletions.
6 changes: 6 additions & 0 deletions cache/stores/redis/addinstanceform.php
Expand Up @@ -58,5 +58,11 @@ protected function configuration_definition() {
$form->addHelpButton('serializer', 'useserializer', 'cachestore_redis');
$form->setDefault('serializer', Redis::SERIALIZER_PHP);
$form->setType('serializer', PARAM_INT);

$compressoroptions = cachestore_redis::config_get_compressor_options();
$form->addElement('select', 'compressor', get_string('usecompressor', 'cachestore_redis'), $compressoroptions);
$form->addHelpButton('compressor', 'usecompressor', 'cachestore_redis');
$form->setDefault('compressor', cachestore_redis::COMPRESSOR_NONE);
$form->setType('compressor', PARAM_INT);
}
}
4 changes: 4 additions & 0 deletions cache/stores/redis/lang/en/cachestore_redis.php
Expand Up @@ -24,6 +24,8 @@

defined('MOODLE_INTERNAL') || die();

$string['compressor_none'] = 'No compression.';
$string['compressor_php_gzip'] = 'Use gzip compression.';
$string['pluginname'] = 'Redis';
$string['prefix'] = 'Key prefix';
$string['prefix_help'] = 'This prefix is used for all key names on the Redis server.
Expand All @@ -48,3 +50,5 @@
$string['useserializer_help'] = 'Specifies the serializer to use for serializing.
The valid serializers are Redis::SERIALIZER_PHP or Redis::SERIALIZER_IGBINARY.
The latter is supported only when phpredis is configured with --enable-redis-igbinary option and the igbinary extension is loaded.';
$string['usecompressor'] = 'Use compressor';
$string['usecompressor_help'] = 'Specifies the compressor to use after serializing. It is done at Moodle Cache API level, not at php-redis level.';
156 changes: 151 additions & 5 deletions cache/stores/redis/lib.php
Expand Up @@ -38,6 +38,16 @@
*/
class cachestore_redis extends cache_store implements cache_is_key_aware, cache_is_lockable,
cache_is_configurable, cache_is_searchable {
/**
* Compressor: none.
*/
const COMPRESSOR_NONE = 0;

/**
* Compressor: PHP GZip.
*/
const COMPRESSOR_PHP_GZIP = 1;

/**
* Name of this store.
*
Expand Down Expand Up @@ -80,6 +90,13 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
*/
protected $serializer = Redis::SERIALIZER_PHP;

/**
* Compressor for this store.
*
* @var int
*/
protected $compressor = self::COMPRESSOR_NONE;

/**
* Determines if the requirements for this type of store are met.
*
Expand Down Expand Up @@ -134,6 +151,9 @@ public function __construct($name, array $configuration = array()) {
if (array_key_exists('serializer', $configuration)) {
$this->serializer = (int)$configuration['serializer'];
}
if (array_key_exists('compressor', $configuration)) {
$this->compressor = (int)$configuration['compressor'];
}
$password = !empty($configuration['password']) ? $configuration['password'] : '';
$prefix = !empty($configuration['prefix']) ? $configuration['prefix'] : '';
$this->redis = $this->new_redis($configuration['server'], $prefix, $password);
Expand Down Expand Up @@ -161,7 +181,10 @@ protected function new_redis($server, $prefix = '', $password = '') {
if (!empty($password)) {
$redis->auth($password);
}
$redis->setOption(Redis::OPT_SERIALIZER, $this->serializer);
// If using compressor, serialisation will be done at cachestore level, not php-redis.
if ($this->compressor == self::COMPRESSOR_NONE) {
$redis->setOption(Redis::OPT_SERIALIZER, $this->serializer);
}
if (!empty($prefix)) {
$redis->setOption(Redis::OPT_PREFIX, $prefix);
}
Expand Down Expand Up @@ -236,7 +259,13 @@ public function is_ready() {
* @return mixed The value of the key, or false if there is no value associated with the key.
*/
public function get($key) {
return $this->redis->hGet($this->hash, $key);
$value = $this->redis->hGet($this->hash, $key);

if ($this->compressor == self::COMPRESSOR_NONE) {
return $value;
}

return $this->uncompress($value);
}

/**
Expand All @@ -246,7 +275,17 @@ public function get($key) {
* @return array An array of the values of the given keys.
*/
public function get_many($keys) {
return $this->redis->hMGet($this->hash, $keys);
$values = $this->redis->hMGet($this->hash, $keys);

if ($this->compressor == self::COMPRESSOR_NONE) {
return $values;
}

foreach ($values as &$value) {
$value = $this->uncompress($value);
}

return $values;
}

/**
Expand All @@ -257,6 +296,8 @@ public function get_many($keys) {
* @return bool True if the operation succeeded, false otherwise.
*/
public function set($key, $value) {
$value = $this->compress($value);

return ($this->redis->hSet($this->hash, $key, $value) !== false);
}

Expand All @@ -270,7 +311,8 @@ public function set($key, $value) {
public function set_many(array $keyvaluearray) {
$pairs = [];
foreach ($keyvaluearray as $pair) {
$pairs[$pair['key']] = $pair['value'];
$key = $pair['key'];
$pairs[$key] = $this->compress($pairs[$key]);
}
if ($this->redis->hMSet($this->hash, $pairs)) {
return count($pairs);
Expand Down Expand Up @@ -446,7 +488,8 @@ public static function config_get_configuration_array($data) {
'server' => $data->server,
'prefix' => $data->prefix,
'password' => $data->password,
'serializer' => $data->serializer
'serializer' => $data->serializer,
'compressor' => $data->compressor,
);
}

Expand All @@ -465,6 +508,9 @@ public static function config_set_edit_form_data(moodleform $editform, array $co
if (!empty($config['serializer'])) {
$data['serializer'] = $config['serializer'];
}
if (!empty($config['compressor'])) {
$data['compressor'] = $config['compressor'];
}
$editform->set_data($data);
}

Expand Down Expand Up @@ -538,4 +584,104 @@ public static function config_get_serializer_options() {
}
return $options;
}

/**
* Gets an array of options to use as the compressor.
*
* @return array
*/
public static function config_get_compressor_options() {
return [
self::COMPRESSOR_NONE => get_string('compressor_none', 'cachestore_redis'),
self::COMPRESSOR_PHP_GZIP => get_string('compressor_php_gzip', 'cachestore_redis'),
];
}

/**
* Compress the given value, serializing it first.
*
* @param mixed $value
* @return string
*/
private function compress($value) {
$value = $this->serialize($value);

switch ($this->compressor) {
case self::COMPRESSOR_NONE:
return $value;

case self::COMPRESSOR_PHP_GZIP:
return gzencode($value);

default:
debugging("Invalid compressor: {$this->compressor}");
return $value;
}
}

/**
* Uncompresses (deflates) the data, unserialising it afterwards.
*
* @param string $value
* @return mixed
*/
private function uncompress($value) {
if ($value === false) {
return false;
}

if ($this->compressor == self::COMPRESSOR_NONE) {
return $value;
}

switch ($this->compressor) {
case self::COMPRESSOR_PHP_GZIP:
$value = gzdecode($value);
break;
default:
debugging("Invalid compressor: {$this->compressor}");
}

return $this->unserialize($value);
}

/**
* Serializes the data according to the configured serializer.
*
* @param mixed $value
* @return string
*/
private function serialize($value) {
switch ($this->serializer) {
case Redis::SERIALIZER_NONE:
return $value;
case Redis::SERIALIZER_PHP:
return serialize($value);
case Redis::SERIALIZER_IGBINARY:
return igbinary_serialize($value);
default:
debugging("Invalid serializer: {$this->serializer}");
return $value;
}
}

/**
* Unserializes the data according to the configured serializer
*
* @param string $value
* @return mixed
*/
private function unserialize($value) {
switch ($this->serializer) {
case Redis::SERIALIZER_NONE:
return $value;
case Redis::SERIALIZER_PHP:
return unserialize($value);
case Redis::SERIALIZER_IGBINARY:
return igbinary_unserialize($value);
default:
debugging("Invalid serializer: {$this->serializer}");
return $value;
}
}
}

0 comments on commit cda3e43

Please sign in to comment.