Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit eda0753

Browse filesBrowse files
committed
[Cache] Add optimized FileSystem & Redis TagAware Adapters
Reduces cache lookups by 50% by changing logic of how tag information is stored to avoid having to look it up on getItem(s) calls. For Filesystem symlinks are used, for Redis "Set" datatype is used.
1 parent 3895acd commit eda0753
Copy full SHA for eda0753

File tree

3 files changed

+558
-0
lines changed
Filter options

3 files changed

+558
-0
lines changed
+264Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Symfony\Component\Cache\Adapter\TagAware;
15+
16+
use Psr\Cache\CacheItemInterface;
17+
use Psr\Log\LoggerAwareInterface;
18+
use Symfony\Component\Cache\Adapter\AdapterInterface;
19+
use Symfony\Component\Cache\CacheItem;
20+
use Symfony\Component\Cache\Exception\InvalidArgumentException;
21+
use Symfony\Component\Cache\ResettableInterface;
22+
use Symfony\Component\Cache\Traits\AbstractTrait;
23+
use Symfony\Component\Cache\Traits\ContractsTrait;
24+
use Symfony\Contracts\Cache\CacheInterface;
25+
26+
/**
27+
* @author Nicolas Grekas <p@tchwork.com>
28+
*/
29+
abstract class AbstractTagAwareAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
30+
{
31+
use AbstractTrait { getId as protected; }
32+
use ContractsTrait;
33+
34+
protected const TAGS_PREFIX = "\0tags\0";
35+
36+
private $createCacheItem;
37+
private $mergeByLifetime;
38+
39+
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
40+
{
41+
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
42+
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
43+
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
44+
}
45+
$this->createCacheItem = \Closure::bind(
46+
function ($key, $value, $isHit) use ($defaultLifetime) {
47+
$item = new CacheItem();
48+
$item->key = $key;
49+
$item->isHit = $isHit;
50+
$item->defaultLifetime = $defaultLifetime;
51+
//<diff:AbstractAdapter> extract Value and Tags from the cache value
52+
$item->value = $v = $value['value'];
53+
$item->metadata[CacheItem::METADATA_TAGS] = $value['tags'] ?? [];
54+
// Detect wrapped values that encode for their expiry and creation duration
55+
// For compactness, these values are packed
56+
if (isset($value['meta'])) {
57+
$v = \unpack('Ve/Nc', $value['meta']);
58+
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
59+
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
60+
}
61+
//</diff:AbstractAdapter>
62+
63+
return $item;
64+
},
65+
null,
66+
CacheItem::class
67+
);
68+
$getId = \Closure::fromCallable([$this, 'getId']);
69+
$this->mergeByLifetime = \Closure::bind(
70+
function ($deferred, $namespace, &$expiredIds) use ($getId) {
71+
$byLifetime = [];
72+
$now = microtime(true);
73+
$expiredIds = [];
74+
75+
foreach ($deferred as $key => $item) {
76+
$key = (string) $key;
77+
if (null === $item->expiry) {
78+
$ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0;
79+
} elseif (0 >= $ttl = (int) ($item->expiry - $now)) {
80+
$expiredIds[] = $getId($key);
81+
continue;
82+
}
83+
//<diff:AbstractAdapter> store Value and Tags on the cache value
84+
if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
85+
$value = ['value' => $item->value, 'tags' => $metadata[CacheItem::METADATA_TAGS]];
86+
unset($metadata[CacheItem::METADATA_TAGS]);
87+
} else {
88+
$value = ['value' => $item->value, 'tags' => []];
89+
}
90+
91+
if ($metadata) {
92+
// For compactness, expiry and creation duration are packed, using magic numbers as separators
93+
$value['meta'] = pack('VN', (int) $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET, $metadata[CacheItem::METADATA_CTIME]);
94+
}
95+
$byLifetime[$ttl][$getId($key)] = $value;
96+
//</diff:AbstractAdapter>
97+
}
98+
99+
return $byLifetime;
100+
},
101+
null,
102+
CacheItem::class
103+
);
104+
}
105+
106+
/**
107+
* {@inheritdoc}
108+
*/
109+
public function getItem($key)
110+
{
111+
if ($this->deferred) {
112+
$this->commit();
113+
}
114+
$id = $this->getId($key);
115+
116+
$f = $this->createCacheItem;
117+
$isHit = false;
118+
$value = null;
119+
120+
try {
121+
foreach ($this->doFetch([$id]) as $value) {
122+
$isHit = true;
123+
}
124+
} catch (\Exception $e) {
125+
CacheItem::log($this->logger, 'Failed to fetch key "{key}"', ['key' => $key, 'exception' => $e]);
126+
}
127+
128+
return $f($key, $value, $isHit);
129+
}
130+
131+
/**
132+
* {@inheritdoc}
133+
*/
134+
public function getItems(array $keys = [])
135+
{
136+
if ($this->deferred) {
137+
$this->commit();
138+
}
139+
$ids = [];
140+
141+
foreach ($keys as $key) {
142+
$ids[] = $this->getId($key);
143+
}
144+
try {
145+
$items = $this->doFetch($ids);
146+
} catch (\Exception $e) {
147+
CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => $keys, 'exception' => $e]);
148+
$items = [];
149+
}
150+
$ids = array_combine($ids, $keys);
151+
152+
return $this->generateItems($items, $ids);
153+
}
154+
155+
/**
156+
* {@inheritdoc}
157+
*/
158+
public function save(CacheItemInterface $item)
159+
{
160+
if (!$item instanceof CacheItem) {
161+
return false;
162+
}
163+
$this->deferred[$item->getKey()] = $item;
164+
165+
return $this->commit();
166+
}
167+
168+
/**
169+
* {@inheritdoc}
170+
*/
171+
public function saveDeferred(CacheItemInterface $item)
172+
{
173+
if (!$item instanceof CacheItem) {
174+
return false;
175+
}
176+
$this->deferred[$item->getKey()] = $item;
177+
178+
return true;
179+
}
180+
181+
/**
182+
* {@inheritdoc}
183+
*/
184+
public function commit()
185+
{
186+
$ok = true;
187+
$byLifetime = $this->mergeByLifetime;
188+
$byLifetime = $byLifetime($this->deferred, $this->namespace, $expiredIds);
189+
$retry = $this->deferred = [];
190+
191+
if ($expiredIds) {
192+
$this->doDelete($expiredIds);
193+
}
194+
foreach ($byLifetime as $lifetime => $values) {
195+
try {
196+
$e = $this->doSave($values, $lifetime);
197+
} catch (\Exception $e) {
198+
}
199+
if (true === $e || [] === $e) {
200+
continue;
201+
}
202+
if (\is_array($e) || 1 === \count($values)) {
203+
foreach (\is_array($e) ? $e : array_keys($values) as $id) {
204+
$ok = false;
205+
$v = $values[$id];
206+
$type = \is_object($v) ? \get_class($v) : \gettype($v);
207+
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
208+
}
209+
} else {
210+
foreach ($values as $id => $v) {
211+
$retry[$lifetime][] = $id;
212+
}
213+
}
214+
}
215+
216+
// When bulk-save failed, retry each item individually
217+
foreach ($retry as $lifetime => $ids) {
218+
foreach ($ids as $id) {
219+
try {
220+
$v = $byLifetime[$lifetime][$id];
221+
$e = $this->doSave([$id => $v], $lifetime);
222+
} catch (\Exception $e) {
223+
}
224+
if (true === $e || [] === $e) {
225+
continue;
226+
}
227+
$ok = false;
228+
$type = \is_object($v) ? \get_class($v) : \gettype($v);
229+
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
230+
}
231+
}
232+
233+
return $ok;
234+
}
235+
236+
public function __destruct()
237+
{
238+
if ($this->deferred) {
239+
$this->commit();
240+
}
241+
}
242+
243+
private function generateItems($items, &$keys)
244+
{
245+
$f = $this->createCacheItem;
246+
247+
try {
248+
foreach ($items as $id => $value) {
249+
if (!isset($keys[$id])) {
250+
$id = key($keys);
251+
}
252+
$key = $keys[$id];
253+
unset($keys[$id]);
254+
yield $key => $f($key, $value, true);
255+
}
256+
} catch (\Exception $e) {
257+
CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => array_values($keys), 'exception' => $e]);
258+
}
259+
260+
foreach ($keys as $key) {
261+
yield $key => $f($key, null, false);
262+
}
263+
}
264+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.