Skip to content

Commit b7332ae

Browse files
committed
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 cost of compiling the scripts can be ignored, since these are very small. 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 <[email protected]>
1 parent 857961c commit b7332ae

File tree

1 file changed

+52
-35
lines changed

1 file changed

+52
-35
lines changed

lib/private/Memcache/Redis.php

Lines changed: 52 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,19 @@ public function getCache() {
5454

5555
public function get($key) {
5656
$result = $this->getCache()->get($this->getPrefix() . $key);
57-
if ($result === false && !$this->getCache()->exists($this->getPrefix() . $key)) {
57+
if ($result === false) {
5858
return null;
59-
} else {
60-
return json_decode($result, true);
6159
}
60+
61+
return self::decodeValue($result);
6262
}
6363

6464
public function set($key, $value, $ttl = 0) {
65+
$value = self::encodeValue($value);
6566
if ($ttl > 0) {
66-
return $this->getCache()->setex($this->getPrefix() . $key, $ttl, json_encode($value));
67+
return $this->getCache()->setex($this->getPrefix() . $key, $ttl, $value);
6768
} else {
68-
return $this->getCache()->set($this->getPrefix() . $key, json_encode($value));
69+
return $this->getCache()->set($this->getPrefix() . $key, $value);
6970
}
7071
}
7172

@@ -82,6 +83,7 @@ public function remove($key) {
8283
}
8384

8485
public function clear($prefix = '') {
86+
// TODO: this is slow and would fail with Redis cluster
8587
$prefix = $this->getPrefix() . $prefix . '*';
8688
$keys = $this->getCache()->keys($prefix);
8789
$deleted = $this->getCache()->del($keys);
@@ -98,17 +100,14 @@ public function clear($prefix = '') {
98100
* @return bool
99101
*/
100102
public function add($key, $value, $ttl = 0) {
101-
// don't encode ints for inc/dec
102-
if (!is_int($value)) {
103-
$value = json_encode($value);
104-
}
103+
$value = self::encodeValue($value);
105104

106105
$args = ['nx'];
107106
if ($ttl !== 0 && is_int($ttl)) {
108107
$args['ex'] = $ttl;
109108
}
110109

111-
return $this->getCache()->set($this->getPrefix() . $key, (string)$value, $args);
110+
return $this->getCache()->set($this->getPrefix() . $key, $value, $args);
112111
}
113112

114113
/**
@@ -130,10 +129,17 @@ public function inc($key, $step = 1) {
130129
* @return int | bool
131130
*/
132131
public function dec($key, $step = 1) {
133-
if (!$this->hasKey($key)) {
134-
return false;
135-
}
136-
return $this->getCache()->decrBy($this->getPrefix() . $key, $step);
132+
$res = $this->getCache()->eval(
133+
'if redis.call("exists", KEYS[1]) == 1 then
134+
return redis.call("decrby", KEYS[1], ARGV[1])
135+
else
136+
return "NEX"
137+
end',
138+
[$this->getPrefix() . $key, $step],
139+
1
140+
);
141+
142+
return ($res === 'NEX') ? false : $res;
137143
}
138144

139145
/**
@@ -145,18 +151,19 @@ public function dec($key, $step = 1) {
145151
* @return bool
146152
*/
147153
public function cas($key, $old, $new) {
148-
if (!is_int($new)) {
149-
$new = json_encode($new);
150-
}
151-
$this->getCache()->watch($this->getPrefix() . $key);
152-
if ($this->get($key) === $old) {
153-
$result = $this->getCache()->multi()
154-
->set($this->getPrefix() . $key, $new)
155-
->exec();
156-
return $result !== false;
157-
}
158-
$this->getCache()->unwatch();
159-
return false;
154+
$old = self::encodeValue($old);
155+
$new = self::encodeValue($new);
156+
157+
return $this->getCache()->eval(
158+
'if redis.call("get", KEYS[1]) == ARGV[1] then
159+
redis.call("set", KEYS[1], ARGV[2])
160+
return 1
161+
else
162+
return 0
163+
end',
164+
[$this->getPrefix() . $key, $old, $new],
165+
1
166+
) > 0;
160167
}
161168

162169
/**
@@ -167,15 +174,17 @@ public function cas($key, $old, $new) {
167174
* @return bool
168175
*/
169176
public function cad($key, $old) {
170-
$this->getCache()->watch($this->getPrefix() . $key);
171-
if ($this->get($key) === $old) {
172-
$result = $this->getCache()->multi()
173-
->unlink($this->getPrefix() . $key)
174-
->exec();
175-
return $result !== false;
176-
}
177-
$this->getCache()->unwatch();
178-
return false;
177+
$old = self::encodeValue($old);
178+
179+
return $this->getCache()->eval(
180+
'if redis.call("get", KEYS[1]) == ARGV[1] then
181+
return redis.call("del", KEYS[1])
182+
else
183+
return 0
184+
end',
185+
[$this->getPrefix() . $key, $old],
186+
1
187+
) > 0;
179188
}
180189

181190
public function setTTL($key, $ttl) {
@@ -185,4 +194,12 @@ public function setTTL($key, $ttl) {
185194
public static function isAvailable(): bool {
186195
return \OC::$server->getGetRedisFactory()->isAvailable();
187196
}
197+
198+
protected static function encodeValue($value) {
199+
return is_int($value) ? (string) $value : json_encode($value);
200+
}
201+
202+
protected static function decodeValue($value) {
203+
return is_numeric($value) ? (int) $value : json_decode($value, true);
204+
}
188205
}

0 commit comments

Comments
 (0)