10000 [Cache] Improve packing tags into items · symfony/symfony@e25b80c · GitHub
[go: up one dir, main page]

Skip to content

Commit e25b80c

Browse files
[Cache] Improve packing tags into items
1 parent c878e52 commit e25b80c

13 files changed

+329
-390
lines changed

composer.json

+3
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@
175175
"files": [
176176
"src/Symfony/Component/String/Resources/functions.php"
177177
],
178+
"classmap": [
179+
"src/Symfony/Component/Cache/Traits/ValueWrapper.php"
180+
],
178181
"exclude-from-classmap": [
179182
"**/Tests/"
180183
]

src/Symfony/Component/Cache/Adapter/AbstractAdapter.php

+2-14
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,7 @@ static function ($key, $value, $isHit) {
4949
$item->key = $key;
5050
$item->value = $v = $value;
5151
$item->isHit = $isHit;
52-
// Detect wrapped values that encode for their expiry and creation duration
53-
// For compactness, these values are packed in the key of an array using
54-
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
55-
if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
56-
$item->value = $v[$k];
57-
$v = unpack('Ve/Nc', substr($k, 1, -1));
58-
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
59-
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
60-
}
52+
$item->unpack();
6153

6254
return $item;
6355
},
@@ -80,11 +72,7 @@ static function ($deferred, $namespace, &$expiredIds, $getId, $defaultLifetime)
8072
$expiredIds[] = $getId($key);
8173
continue;
8274
}
83-
if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
84-
unset($metadata[CacheItem::METADATA_TAGS]);
85-
}
86-
// For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
87-
$byLifetime[$ttl][$getId($key)] = $metadata ? ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item->value] : $item->value;
75+
$byLifetime[$ttl][$getId($key)] = $item->pack();
8876
}
8977

9078
return $byLifetime;

src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php

+5-4
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ static function ($key, $value, $isHit) {
5656
$item->isHit = $isHit;
5757
// Extract value, tags and meta data from the cache value
5858
$item->value = $value['value'];
59-
$item->metadata[CacheItem::METADATA_TAGS] = $value['tags'] ?? [];
59+
$item->metadata[CacheItem::METADATA_TAGS] = isset($value['tags']) ? array_combine($value['tags'], $value['tags']) : [];
6060
if (isset($value['meta'])) {
6161
// For compactness these values are packed, & expiry is offset to reduce size
6262
$v = unpack('Ve/Nc', $value['meta']);
@@ -95,18 +95,19 @@ static function ($deferred, &$expiredIds, $getId, $tagPrefix, $defaultLifetime)
9595

9696
if ($metadata) {
9797
// For compactness, expiry and creation duration are packed, using magic numbers as separators
98-
$value['meta'] = pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]);
98+
$value['meta'] = pack('VN', (int) (0.1 + $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET), $metadata[CacheItem::METADATA_CTIME]);
9999
}
100100

101101
// Extract tag changes, these should be removed from values in doSave()
102102
$value['tag-operations'] = ['add' => [], 'remove' => []];
103103
$oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? [];
104-
foreach (array_diff($value['tags'], $oldTags) as $addedTag) {
104+
foreach (array_diff_key($value['tags'], $oldTags) as $addedTag) {
105105
$value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag);
106106
}
107-
foreach (array_diff($oldTags, $value['tags']) as $removedTag) {
107+
foreach (array_diff_key($oldTags, $value['tags']) as $removedTag) {
108108
$value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag);
109109
}
110+
$value['tags'] = array_keys($value['tags']);
110111

111112
$byLifetime[$ttl][$getId($key)] = $value;
112113
$item->metadata = $item->newMetadata;

src/Symfony/Component/Cache/Adapter/ArrayAdapter.php

+17-9
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter
3232

3333
private bool $storeSerialized;
3434
private array $values = [];
35+
private array $tags = [];
3536
private array $expiries = [];
3637
private int $defaultLifetime;
3738
private float $maxLifetime;
@@ -57,11 +58,14 @@ public function __construct(int $defaultLifetime = 0, bool $storeSerialized = tr
5758
$this->maxLifetime = $maxLifetime;
5859
$this->maxItems = $maxItems;
5960
self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
60-
static function ($key, $value, $isHit) {
61+
static function ($key, $value, $isHit, $tags) {
6162
$item = new CacheItem();
6263
$item->key = $key;
6364
$item->value = $value;
6465
$item->isHit = $isHit;
66+
if (null !== $tags) {
67+
$item->metadata[CacheItem::METADATA_TAGS] = $tags;
68+
}
6569

6670
return $item;
6771
},
@@ -131,7 +135,7 @@ public function getItem(mixed $key): CacheItem
131135
$value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
132136
}
133137

134-
return (self::$createCacheItem)($key, $value, $isHit);
138+
return (self::$createCacheItem)($key, $value, $isHit, $this->tags[$key] ?? null);
135139
}
136140

137141
/**
@@ -150,7 +154,7 @@ public function getItems(array $keys = []): iterable
150154
public function deleteItem(mixed $key): bool
151155
{
152156
\assert('' !== CacheItem::validateKey($key));
153-
unset($this->values[$key], $this->expiries[$key]);
157+
unset($this->values[$key], $this->tags[$key], $this->expiries[$key]);
154158

155159
return true;
156160
}
@@ -202,21 +206,25 @@ public function save(CacheItemInterface $item): bool
202206
}
203207

204208
if ($this->maxItems) {
205-
unset($this->values[$key]);
209+
unset($this->values[$key], $this->tags[$key]);
206210

207211
// Iterate items and vacuum expired ones while we are at it
208212
foreach ($this->values as $k => $v) {
209213
if ($this->expiries[$k] > $now && \count($this->values) < $this->maxItems) {
210214
break;
211215
}
212216

213-
unset($this->values[$k], $this->expiries[$k]);
217+
unset($this->values[$k], $this->tags[$k], $this->expiries[$k]);
214218
}
215219
}
216220

217221
$this->values[$key] = $value;
218222
$this->expiries[$key] = $expiry ?? \PHP_INT_MAX;
219223

224+
if (null === $this->tags[$key] = $item["\0*\0newMetadata"][CacheItem::METADATA_TAGS] ?? null) {
225+
unset($this->tags[$key]);
226+
}
227+
220228
return true;
221229
}
222230

@@ -246,7 +254,7 @@ public function clear(string $prefix = ''): bool
246254

247255
foreach ($this->values as $key => $value) {
248256
if (!isset($this->expiries[$key]) || $this->expiries[$key] <= $now || str_starts_with($key, $prefix)) {
249-
unset($this->values[$key], $this->expiries[$key]);
257+
unset($this->values[$key], $this->tags[$key], $this->expiries[$key]);
250258
}
251259
}
252260

@@ -255,7 +263,7 @@ public function clear(string $prefix = ''): bool
255263
}
256264
}
257265

258-
$this->values = $this->expiries = [];
266+
$this->values = $this->tags = $this->expiries = [];
259267

260268
return true;
261269
}
@@ -312,7 +320,7 @@ private function generateItems(array $keys, float $now, \Closure $f): \Generator
312320
}
313321
unset($keys[$i]);
314322

315-
yield $key => $f($key, $value, $isHit);
323+
yield $key => $f($key, $value, $isHit, $this->tags[$key] ?? null);
316324
}
317325

318326
foreach ($keys as $key) {
@@ -334,7 +342,7 @@ private function freeze($value, string $key)
334342
try {
335343
$serialized = serialize($value);
336344
} catch (\Exception $e) {
337-
unset($this->values[$key]);
345+
unset($this->values[$key], $this->tags[$key]);
338346
$type = get_debug_type($value);
339347
$message = sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage());
340348
CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);

src/Symfony/Component/Cache/Adapter/ChainAdapter.php

-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ public function __construct(array $adapters, int $defaultLifetime = 0)
7070
static function ($sourceItem, $item, $defaultLifetime, $sourceMetadata = null) {
7171
$sourceItem->isTaggable = false;
7272
$sourceMetadata ??= $sourceItem->metadata;
73-
unset($sourceMetadata[CacheItem::METADATA_TAGS]);
7473

7574
$item->value = $sourceItem->value;
7675
$item->isHit = $sourceItem->isHit;

src/Symfony/Component/Cache/Adapter/ProxyAdapter.php

+17-31
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public function __construct(CacheItemPoolInterface $pool, string $namespace = ''
4646
}
4747
$this->namespaceLen = \strlen($namespace);
4848
$this->defaultLifetime = $defaultLifetime;
49-
self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
49+
self::$createCacheItem ??= \Closure::bind(
5050
static function ($key, $innerItem, $poolHash) {
5151
$item = new CacheItem();
5252
$item->key = $key;
@@ -55,20 +55,12 @@ static function ($key, $innerItem, $poolHash) {
5555
return $item;
5656
}
5757

58-
$item->value = $v = $innerItem->get();
58+
$item->value = $innerItem->get();
5959
$item->isHit = $innerItem->isHit();
6060
$item->innerItem = $innerItem;
6161
$item->poolHash = $poolHash;
6262

63-
// Detect wrapped values that encode for their expiry and creation duration
64-
// For compactness, these values are packed in the key of an array using
65-
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
66-
if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
67-
$item->value = $v[$k];
68-
$v = unpack('Ve/Nc', substr($k, 1, -1));
69-
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
70-
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
71-
} elseif ($innerItem instanceof CacheItem) {
63+
if (!$item->unpack() && $innerItem instanceof CacheItem) {
7264
$item->metadata = $innerItem->metadata;
7365
}
7466
$innerItem->set(null);
@@ -78,17 +70,10 @@ static function ($key, $innerItem, $poolHash) {
7870
null,
7971
CacheItem::class
8072
);
81-
self::$setInnerItem ?? self::$setInnerItem = \Closure::bind(
82-
/**
83-
* @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix
84-
*/
85-
static function (CacheItemInterface $innerItem, array $item) {
86-
if ($metadata = $item["\0*\0newMetadata"]) {
87-
// For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
88-
$item["\0*\0value"] = ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item["\0*\0value"]];
89-
}
90-
$innerItem->set($item["\0*\0value"]);
91-
$innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6F', $item["\0*\0expiry"])) : null);
73+
self::$setInnerItem ??= \Closure::bind(
74+
static function (CacheItemInterface $innerItem, CacheItem $item, $expiry = null) {
75+
$innerItem->set($item->pack());
76+
$innerItem->expiresAt(($expiry ?? $item->expiry) ? \DateTime::createFromFormat('U.u', sprintf('%.6F', $expiry ?? $item->expiry)) : null);
9277
},
9378
null,
9479
CacheItem::class
@@ -107,7 +92,7 @@ public function get(string $key, callable $callback, float $beta = null, array &
10792
return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use ($key, $callback) {
10893
$item = (self::$createCacheItem)($key, $innerItem, $this->poolHash);
10994
$item->set($value = $callback($item, $save));
110-
(self::$setInnerItem)($innerItem, (array) $item);
95+
(self::$setInnerItem)($innerItem, $item);
11196

11297
return $value;
11398
}, $beta, $metadata);
@@ -208,22 +193,23 @@ private function doSave(CacheItemInterface $item, string $method): bool
208193
if (!$item instanceof CacheItem) {
209194
return false;
210195
}
211-
$item = (array) $item;
212-
if (null === $item["\0*\0expiry"] && 0 < $this->defaultLifetime) {
213-
$item["\0*\0expiry"] = microtime(true) + $this->defaultLifetime;
196+
$castItem = (array) $item;
197+
198+
if (null === $castItem["\0*\0expiry"] && 0 < $this->defaultLifetime) {
199+
$castItem["\0*\0expiry"] = microtime(true) + $this->defaultLifetime;
214200
}
215201

216-
if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) {
217-
$innerItem = $item["\0*\0innerItem"];
202+
if ($castItem["\0*\0poolHash"] === $this->poolHash && $castItem["\0*\0innerItem"]) {
203+
$innerItem = $castItem["\0*\0innerItem"];
218204
} elseif ($this->pool instanceof AdapterInterface) {
219205
// this is an optimization specific for AdapterInterface implementations
220206
// so we can save a round-trip to the backend by just creating a new item
221-
$innerItem = (self::$createCacheItem)($this->namespace.$item["\0*\0key"], null, $this->poolHash);
207+
$innerItem = (self::$createCacheItem)($this->namespace.$castItem["\0*\0key"], null, $this->poolHash);
222208
} else {
223-
$innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]);
209+
$innerItem = $this->pool->getItem($this->namespace.$castItem["\0*\0key"]);
224210
}
225211

226-
(self::$setInnerItem)($innerItem, $item);
212+
(self::$setInnerItem)($innerItem, $item, $castItem["\0*\0expiry"]);
227213

228214
return $this->pool->$method($innerItem);
229215
}

0 commit comments

Comments
 (0)
0