Browse Source

Merge pull request #37758 from nextcloud/redis-atomic

redis: use atomic operations everywhere
pull/38372/head
Simon L 3 years ago
committed by GitHub
parent
commit
3d2d1c171e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 90
      lib/private/Memcache/Redis.php
  2. 6
      tests/lib/Memcache/RedisTest.php

90
lib/private/Memcache/Redis.php

@ -32,6 +32,22 @@ namespace OC\Memcache;
use OCP\IMemcacheTTL;
class Redis extends Cache implements IMemcacheTTL {
/** name => [script, sha1] */
public const LUA_SCRIPTS = [
'dec' => [
'if redis.call("exists", KEYS[1]) == 1 then return redis.call("decrby", KEYS[1], ARGV[1]) else return "NEX" end',
'720b40cb66cef1579f2ef16ec69b3da8c85510e9',
],
'cas' => [
'if redis.call("get", KEYS[1]) == ARGV[1] then redis.call("set", KEYS[1], ARGV[2]) return 1 else return 0 end',
'94eac401502554c02b811e3199baddde62d976d4',
],
'cad' => [
'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end',
'cf0e94b2e9ffc7e04395cf88f7583fc309985910',
],
];
/**
* @var \Redis|\RedisCluster $cache
*/
@ -54,18 +70,19 @@ class Redis extends Cache implements IMemcacheTTL {
public function get($key) {
$result = $this->getCache()->get($this->getPrefix() . $key);
if ($result === false && !$this->getCache()->exists($this->getPrefix() . $key)) {
if ($result === false) {
return null;
} else {
return json_decode($result, true);
}
return self::decodeValue($result);
}
public function set($key, $value, $ttl = 0) {
$value = self::encodeValue($value);
if ($ttl > 0) {
return $this->getCache()->setex($this->getPrefix() . $key, $ttl, json_encode($value));
return $this->getCache()->setex($this->getPrefix() . $key, $ttl, $value);
} else {
return $this->getCache()->set($this->getPrefix() . $key, json_encode($value));
return $this->getCache()->set($this->getPrefix() . $key, $value);
}
}
@ -82,6 +99,7 @@ class Redis extends Cache implements IMemcacheTTL {
}
public function clear($prefix = '') {
// TODO: this is slow and would fail with Redis cluster
$prefix = $this->getPrefix() . $prefix . '*';
$keys = $this->getCache()->keys($prefix);
$deleted = $this->getCache()->del($keys);
@ -98,17 +116,14 @@ class Redis extends Cache implements IMemcacheTTL {
* @return bool
*/
public function add($key, $value, $ttl = 0) {
// don't encode ints for inc/dec
if (!is_int($value)) {
$value = json_encode($value);
}
$value = self::encodeValue($value);
$args = ['nx'];
if ($ttl !== 0 && is_int($ttl)) {
$args['ex'] = $ttl;
}
return $this->getCache()->set($this->getPrefix() . $key, (string)$value, $args);
return $this->getCache()->set($this->getPrefix() . $key, $value, $args);
}
/**
@ -130,10 +145,8 @@ class Redis extends Cache implements IMemcacheTTL {
* @return int | bool
*/
public function dec($key, $step = 1) {
if (!$this->hasKey($key)) {
return false;
}
return $this->getCache()->decrBy($this->getPrefix() . $key, $step);
$res = $this->evalLua('dec', [$key], [$step]);
return ($res === 'NEX') ? false : $res;
}
/**
@ -145,18 +158,10 @@ class Redis extends Cache implements IMemcacheTTL {
* @return bool
*/
public function cas($key, $old, $new) {
if (!is_int($new)) {
$new = json_encode($new);
}
$this->getCache()->watch($this->getPrefix() . $key);
if ($this->get($key) === $old) {
$result = $this->getCache()->multi()
->set($this->getPrefix() . $key, $new)
->exec();
return $result !== false;
}
$this->getCache()->unwatch();
return false;
$old = self::encodeValue($old);
$new = self::encodeValue($new);
return $this->evalLua('cas', [$key], [$old, $new]) > 0;
}
/**
@ -167,15 +172,9 @@ class Redis extends Cache implements IMemcacheTTL {
* @return bool
*/
public function cad($key, $old) {
$this->getCache()->watch($this->getPrefix() . $key);
if ($this->get($key) === $old) {
$result = $this->getCache()->multi()
->unlink($this->getPrefix() . $key)
->exec();
return $result !== false;
}
$this->getCache()->unwatch();
return false;
$old = self::encodeValue($old);
return $this->evalLua('cad', [$key], [$old]) > 0;
}
public function setTTL($key, $ttl) {
@ -185,4 +184,25 @@ class Redis extends Cache implements IMemcacheTTL {
public static function isAvailable(): bool {
return \OC::$server->getGetRedisFactory()->isAvailable();
}
protected function evalLua(string $scriptName, array $keys, array $args) {
$keys = array_map(fn ($key) => $this->getPrefix() . $key, $keys);
$args = array_merge($keys, $args);
$script = self::LUA_SCRIPTS[$scriptName];
$result = $this->getCache()->evalSha($script[1], $args, count($keys));
if (false === $result) {
$result = $this->getCache()->eval($script[0], $args, count($keys));
}
return $result;
}
protected static function encodeValue(mixed $value): string {
return is_int($value) ? (string) $value : json_encode($value);
}
protected static function decodeValue(string $value): mixed {
return is_numeric($value) ? (int) $value : json_decode($value, true);
}
}

6
tests/lib/Memcache/RedisTest.php

@ -56,4 +56,10 @@ class RedisTest extends Cache {
parent::setUp();
$this->instance = new \OC\Memcache\Redis($this->getUniqueID());
}
public function testScriptHashes() {
foreach (\OC\Memcache\Redis::LUA_SCRIPTS as $script) {
$this->assertEquals(sha1($script[0]), $script[1]);
}
}
}
Loading…
Cancel
Save