diff options
author | Varun Patil <varunpatil@ucla.edu> | 2023-04-16 13:44:06 -0700 |
---|---|---|
committer | backportbot-nextcloud[bot] <backportbot-nextcloud[bot]@users.noreply.github.com> | 2023-05-31 07:13:20 +0000 |
commit | 8745f76bd0f3ca66cfe3b9c791e2df2dd5d33d1f (patch) | |
tree | f26d10beae48c754b9e1dc8474841f83ec28ff82 /lib | |
parent | 9ef37aaa6e7ae42fcacbb5a3e2e89f1063686925 (diff) | |
download | nextcloud-server-8745f76bd0f3ca66cfe3b9c791e2df2dd5d33d1f.tar.gz nextcloud-server-8745f76bd0f3ca66cfe3b9c791e2df2dd5d33d1f.zip |
redis: use atomic operations everywhere
This removes a lot of acrobatics in the code and does each operation
atomically using a lua script. This also reduces several round trips
to the server, and the scripts are compiled and cached server-side.
Notably, since all operations work only on a single key (except clear,
which is broken anyway and shouldn't be used), they will continue to
function and be atomic for Redis cluster.
Signed-off-by: Varun Patil <varunpatil@ucla.edu>
Diffstat (limited to 'lib')
-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..4c8151b9d6c 100644 --- a/lib/private/Memcache/Redis.php +++ b/lib/private/Memcache/Redis.php @@ -31,6 +31,22 @@ namespace OC\Memcache; use OCP\IMemcacheTTL; +/** name => [script, sha1] */ +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', + ], +]; + class Redis extends Cache implements IMemcacheTTL { /** * @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($scriptName, $keys, $args) { + $keys = array_map(fn ($key) => $this->getPrefix() . $key, $keys); + $args = array_merge($keys, $args); + $script = 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($value) { + return is_int($value) ? (string) $value : json_encode($value); + } + + protected static function decodeValue($value) { + return is_numeric($value) ? (int) $value : json_decode($value, true); + } } |