mirror of
https://github.com/nextcloud/server.git
synced 2026-04-21 14:23:17 -04:00
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>
This commit is contained in:
parent
9ef37aaa6e
commit
8745f76bd0
1 changed files with 55 additions and 35 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue