diff options
author | Andy Scherzinger <info@andy-scherzinger.de> | 2023-06-01 17:12:05 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-01 17:12:05 +0200 |
commit | f5d5636272c2a0c0d0c4bfdac8e5af459973d154 (patch) | |
tree | 9fca8cbf628e683fe966a4f3cfe84767b64c60e3 /lib/private | |
parent | ca1d9a167ebde00fa18f34823a5a993351538905 (diff) | |
parent | 24875c02dbad1b413067e001f5f1a5945d4310ac (diff) | |
download | nextcloud-server-f5d5636272c2a0c0d0c4bfdac8e5af459973d154.tar.gz nextcloud-server-f5d5636272c2a0c0d0c4bfdac8e5af459973d154.zip |
Merge pull request #38538 from nextcloud/backport/37758/stable27
[stable27] redis: use atomic operations everywhere
Diffstat (limited to 'lib/private')
-rw-r--r-- | lib/private/Memcache/Redis.php | 90 |
1 files changed, 55 insertions, 35 deletions
diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php index b6f96fffba4..bde25a3385a 100644 --- a/lib/private/Memcache/Redis.php +++ b/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); + } } |