Shard::increment()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 2
b 0
f 0
nc 1
nop 4
dl 0
loc 3
rs 10
1
<?php
2
3
namespace MatthiasMullie\Scrapbook\Scale;
4
5
use MatthiasMullie\Scrapbook\KeyValueStore;
6
use SplObjectStorage;
7
8
/**
9
 * This class lets you scale your cache cluster by sharding the data across
10
 * multiple cache servers.
11
 *
12
 * Pass the individual KeyValueStore objects that compose the cache server pool
13
 * into this constructor how you want the data to be sharded. The cache data
14
 * will be sharded over them according to the order they were in when they were
15
 * passed into this constructor (so make sure to always keep the order the same)
16
 *
17
 * The sharding is spread evenly and all cache servers will roughly receive the
18
 * same amount of cache keys. If some servers are bigger than others, you can
19
 * offset this by adding the KeyValueStore object more than once.
20
 *
21
 * Data can even be sharded among different adapters: one server in the shard
22
 * pool can be Redis while another can be Memcached. Not sure why you would even
23
 * want that, but you could!
24
 *
25
 * @author Matthias Mullie <[email protected]>
26
 * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
27
 * @license LICENSE MIT
28
 */
29
class Shard implements KeyValueStore
30
{
31
    /**
32
     * @var KeyValueStore[]
33
     */
34
    protected $caches = array();
35
36
    /**
37
     * Overloadable with multiple KeyValueStore objects.
38
     */
39
    public function __construct(KeyValueStore $cache1, KeyValueStore $cache2 = null /* , [KeyValueStore $cache3, [...]] */)
40
    {
41
        $caches = func_get_args();
42
        $caches = array_filter($caches);
43
        $this->caches = $caches;
44
    }
45
46
    public function addCache(KeyValueStore $cache)
47
    {
48
        $this->caches[] = $cache;
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function get($key, &$token = null)
55
    {
56
        return $this->getShard($key)->get($key, $token);
57
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62
    public function getMulti(array $keys, array &$tokens = null)
63
    {
64
        $shards = $this->getShards($keys);
65
        $results = array();
66
        $tokens = array();
67
68
        /** @var KeyValueStore $shard */
69
        foreach ($shards as $shard) {
70
            $keysOnShard = $shards[$shard];
71
            $results += $shard->getMulti($keysOnShard, $shardTokens);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $shardTokens seems to be never defined.
Loading history...
72
            $tokens += $shardTokens ?: array();
73
        }
74
75
        return $results;
76
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81
    public function set($key, $value, $expire = 0)
82
    {
83
        return $this->getShard($key)->set($key, $value, $expire);
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    public function setMulti(array $items, $expire = 0)
90
    {
91
        $shards = $this->getShards(array_keys($items));
92
        $results = array();
93
94
        /** @var KeyValueStore $shard */
95
        foreach ($shards as $shard) {
96
            $keysOnShard = $shards[$shard];
97
            $itemsOnShard = array_intersect_key($items, array_flip($keysOnShard));
98
            $results += $shard->setMulti($itemsOnShard, $expire);
99
        }
100
101
        return $results;
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public function delete($key)
108
    {
109
        return $this->getShard($key)->delete($key);
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function deleteMulti(array $keys)
116
    {
117
        $shards = $this->getShards($keys);
118
        $results = array();
119
120
        /** @var KeyValueStore $shard */
121
        foreach ($shards as $shard) {
122
            $keysOnShard = $shards[$shard];
123
            $results += $shard->deleteMulti($keysOnShard);
124
        }
125
126
        return $results;
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132
    public function add($key, $value, $expire = 0)
133
    {
134
        return $this->getShard($key)->add($key, $value, $expire);
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140
    public function replace($key, $value, $expire = 0)
141
    {
142
        return $this->getShard($key)->replace($key, $value, $expire);
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148
    public function cas($token, $key, $value, $expire = 0)
149
    {
150
        return $this->getShard($key)->cas($token, $key, $value, $expire);
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function increment($key, $offset = 1, $initial = 0, $expire = 0)
157
    {
158
        return $this->getShard($key)->increment($key, $offset, $initial, $expire);
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
165
    {
166
        return $this->getShard($key)->decrement($key, $offset, $initial, $expire);
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    public function touch($key, $expire)
173
    {
174
        return $this->getShard($key)->touch($key, $expire);
175
    }
176
177
    /**
178
     * {@inheritdoc}
179
     */
180
    public function flush()
181
    {
182
        $result = true;
183
184
        foreach ($this->caches as $cache) {
185
            $result &= $cache->flush();
186
        }
187
188
        return (bool) $result;
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194
    public function getCollection($name)
195
    {
196
        $shard = new static($this->caches[0]->getCollection($name));
197
198
        $count = count($this->caches);
199
        for ($i = 1; $i < $count; ++$i) {
200
            $shard->addCache($this->caches[$i]->getCollection($name));
201
        }
202
203
        return $shard;
204
    }
205
206
    /**
207
     * Get the shard (KeyValueStore object) that corresponds to a particular
208
     * cache key.
209
     *
210
     * @param string $key
211
     *
212
     * @return KeyValueStore
213
     */
214
    protected function getShard($key)
215
    {
216
        /*
217
         * The hash is so we can deterministically randomize the spread of keys
218
         * over servers: if we were to just spread them based on key name, we
219
         * may end up with a large chunk of similarly prefixed keys on the same
220
         * server. Hashing the key will ensure similar looking keys can still
221
         * result in very different values, yet they result will be the same
222
         * every time it's repeated for the same key.
223
         * Since we don't use the hash for encryption, the fastest algorithm
224
         * will do just fine here.
225
         */
226
        $hash = crc32($key);
227
228
        // crc32 on 32-bit machines can produce a negative int
229
        $hash = abs($hash);
230
231
        $index = $hash % count($this->caches);
232
233
        return $this->caches[$index];
234
    }
235
236
    /**
237
     * Get a [KeyValueStore => array of cache keys] map (SplObjectStorage) for
238
     * multiple cache keys.
239
     *
240
     * @return \SplObjectStorage
241
     */
242
    protected function getShards(array $keys)
243
    {
244
        $shards = new \SplObjectStorage();
245
246
        foreach ($keys as $key) {
247
            $shard = $this->getShard($key);
248
            if (!isset($shards[$shard])) {
249
                $shards[$shard] = array();
250
            }
251
252
            $shards[$shard] = array_merge($shards[$shard], array($key));
253
        }
254
255
        return $shards;
256
    }
257
}
258