aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorVarun Patil <varunpatil@ucla.edu>2023-04-16 13:44:06 -0700
committerbackportbot-nextcloud[bot] <backportbot-nextcloud[bot]@users.noreply.github.com>2023-05-31 07:13:20 +0000
commit8745f76bd0f3ca66cfe3b9c791e2df2dd5d33d1f (patch)
treef26d10beae48c754b9e1dc8474841f83ec28ff82 /lib
parent9ef37aaa6e7ae42fcacbb5a3e2e89f1063686925 (diff)
downloadnextcloud-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.php90
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);
+ }
}