Movatterモバイル変換


[0]ホーム

URL:


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

Sign up
Appearance settings

Commit7ec3d50

Browse files
[Cache] Add stampede protection via probabilistic early expiration
1 parentf557f94 commit7ec3d50

21 files changed

+247
-44
lines changed

‎UPGRADE-4.2.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
UPGRADE FROM 4.1 to 4.2
22
=======================
33

4+
Cache
5+
-----
6+
7+
* Deprecated`CacheItem::getPreviousTags()`, use`CacheItem::getStats()` instead.
8+
49
Security
510
--------
611

‎UPGRADE-5.0.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
UPGRADE FROM 4.x to 5.0
22
=======================
33

4+
Cache
5+
-----
6+
7+
* Removed`CacheItem::getPreviousTags()`, use`CacheItem::getStats()` instead.
8+
49
Config
510
------
611

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,15 @@ protected function __construct(string $namespace = '', int $defaultLifetime = 0)
4646
function ($key,$value,$isHit)use ($defaultLifetime) {
4747
$item =newCacheItem();
4848
$item->key =$key;
49-
$item->value =$value;
49+
$item->value =$v =$value;
5050
$item->isHit =$isHit;
5151
$item->defaultLifetime =$defaultLifetime;
52+
if (\is_array($v) &&1 ===\count($v) &&10 ===\strlen($k =\key($v)) &&"\x9D" ===$k[0] &&"\0" ===$k[5] &&"\x5F" ===$k[9]) {
53+
$item->value =$v[$k];
54+
$v =\unpack('Ve/Nc',\substr($k,1, -1));
55+
$item->stats[CacheItem::STATS_EXPIRY] =$v['e'] + CacheItem::STATS_EXPIRY_OFFSET;
56+
$item->stats[CacheItem::STATS_CTIME] =$v['c'];
57+
}
5258

5359
return$item;
5460
},
@@ -64,12 +70,17 @@ function ($deferred, $namespace, &$expiredIds) use ($getId) {
6470

6571
foreach ($deferredas$key =>$item) {
6672
if (null ===$item->expiry) {
67-
$byLifetime[0 <$item->defaultLifetime ?$item->defaultLifetime :0][$getId($key)] =$item->value;
73+
$ttl =0 <$item->defaultLifetime ?$item->defaultLifetime :0;
6874
}elseif ($item->expiry >$now) {
69-
$byLifetime[$item->expiry -$now][$getId($key)] =$item->value;
75+
$ttl =$item->expiry -$now;
7076
}else {
7177
$expiredIds[] =$getId($key);
78+
continue;
79+
}
80+
if (isset(($stats =$item->newStats)[CacheItem::STATS_TAGS])) {
81+
unset($stats[CacheItem::STATS_TAGS]);
7282
}
83+
$byLifetime[$ttl][$getId($key)] =$stats ?array("\x9D".pack('VN',$stats[CacheItem::STATS_EXPIRY] - CacheItem::STATS_EXPIRY_OFFSET,$stats[CacheItem::STATS_CTIME])."\x5F" =>$item->value) :$item->value;
7384
}
7485

7586
return$byLifetime;

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,10 @@ function ($sourceItem, $item) use ($defaultLifetime) {
6464
$item->value =$sourceItem->value;
6565
$item->expiry =$sourceItem->expiry;
6666
$item->isHit =$sourceItem->isHit;
67+
$item->stats =$sourceItem->stats;
6768

6869
$sourceItem->isTaggable =false;
70+
unset($sourceItem->stats[CacheItem::STATS_TAGS]);
6971

7072
if (0 <$sourceItem->defaultLifetime &&$sourceItem->defaultLifetime <$defaultLifetime) {
7173
$defaultLifetime =$sourceItem->defaultLifetime;
@@ -84,19 +86,20 @@ function ($sourceItem, $item) use ($defaultLifetime) {
8486
/**
8587
* {@inheritdoc}
8688
*/
87-
publicfunctionget(string$key,callable$callback)
89+
publicfunctionget(string$key,callable$callback,float$beta =null)
8890
{
8991
$lastItem =null;
9092
$i =0;
91-
$wrap =function (CacheItem$item =null)use ($key,$callback, &$wrap, &$i, &$lastItem) {
93+
$wrap =function (CacheItem$item =null)use ($key,$callback,$beta,&$wrap, &$i, &$lastItem) {
9294
$adapter =$this->adapters[$i];
9395
if (isset($this->adapters[++$i])) {
9496
$callback =$wrap;
97+
$beta =INF ===$beta ?INF :0;
9598
}
9699
if ($adapterinstanceof CacheInterface) {
97-
$value =$adapter->get($key,$callback);
100+
$value =$adapter->get($key,$callback,$beta);
98101
}else {
99-
$value =$this->doGet($adapter,$key,$callback);
102+
$value =$this->doGet($adapter,$key,$callback,$beta ??1.0);
100103
}
101104
if (null !==$item) {
102105
($this->syncItem)($lastItem =$lastItem ??$item,$item);

‎src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,17 @@ public static function create($file, CacheItemPoolInterface $fallbackPool)
8383
/**
8484
* {@inheritdoc}
8585
*/
86-
publicfunctionget(string$key,callable$callback)
86+
publicfunctionget(string$key,callable$callback,float$beta =null)
8787
{
8888
if (null ===$this->values) {
8989
$this->initialize();
9090
}
9191
if (null ===$value =$this->values[$key] ??null) {
9292
if ($this->poolinstanceof CacheInterface) {
93-
return$this->pool->get($key,$callback);
93+
return$this->pool->get($key,$callback,$beta);
9494
}
9595

96-
return$this->doGet($this->pool,$key,$callback);
96+
return$this->doGet($this->pool,$key,$callback,$beta ??1.0);
9797
}
9898
if ('N;' ===$value) {
9999
returnnull;

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

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
3131
private$namespace;
3232
private$namespaceLen;
3333
private$createCacheItem;
34+
private$setInnerItem;
3435
private$poolHash;
3536

3637
publicfunction__construct(CacheItemPoolInterface$pool,string$namespace ='',int$defaultLifetime =0)
@@ -43,32 +44,58 @@ public function __construct(CacheItemPoolInterface $pool, string $namespace = ''
4344
function ($key,$innerItem)use ($defaultLifetime,$poolHash) {
4445
$item =newCacheItem();
4546
$item->key =$key;
46-
$item->value =$innerItem->get();
47+
$item->value =$v =$innerItem->get();
4748
$item->isHit =$innerItem->isHit();
4849
$item->defaultLifetime =$defaultLifetime;
4950
$item->innerItem =$innerItem;
5051
$item->poolHash =$poolHash;
52+
if (\is_array($v) &&1 ===\count($v) &&10 ===\strlen($k =\key($v)) &&"\x9D" ===$k[0] &&"\0" ===$k[5] &&"\x5F" ===$k[9]) {
53+
$item->value =$v[$k];
54+
$v =\unpack('Ve/Nc',\substr($k,1, -1));
55+
$item->stats[CacheItem::STATS_EXPIRY] =$v['e'] + CacheItem::STATS_EXPIRY_OFFSET;
56+
$item->stats[CacheItem::STATS_CTIME] =$v['c'];
57+
}elseif ($innerIteminstanceof CacheItem) {
58+
$item->stats =$innerItem->stats;
59+
}
5160
$innerItem->set(null);
5261

5362
return$item;
5463
},
5564
null,
5665
CacheItem::class
5766
);
67+
$this->setInnerItem = \Closure::bind(
68+
function (CacheItemInterface$innerItem,array$item) {
69+
if (isset(($stats =$item["\0*\0newStats"])[CacheItem::STATS_TAGS])) {
70+
unset($stats[CacheItem::STATS_TAGS]);
71+
}
72+
if ($stats) {
73+
$item["\0*\0value"] =array("\x9D".pack('VN',$stats[CacheItem::STATS_EXPIRY] - CacheItem::STATS_EXPIRY_OFFSET,$stats[CacheItem::STATS_CTIME])."\x5F" =>$item["\0*\0value"]);
74+
}
75+
$innerItem->set($item["\0*\0value"]);
76+
$innerItem->expiresAt(null !==$item["\0*\0expiry"] ? \DateTime::createFromFormat('U',$item["\0*\0expiry"]) :null);
77+
},
78+
null,
79+
CacheItem::class
80+
);
5881
}
5982

6083
/**
6184
* {@inheritdoc}
6285
*/
63-
publicfunctionget(string$key,callable$callback)
86+
publicfunctionget(string$key,callable$callback,float$beta =null)
6487
{
6588
if (!$this->poolinstanceof CacheInterface) {
66-
return$this->doGet($this->pool,$key,$callback);
89+
return$this->doGet($this,$key,$callback,$beta ??1.0);
6790
}
6891

6992
return$this->pool->get($this->getId($key),function ($innerItem)use ($key,$callback) {
70-
return$callback(($this->createCacheItem)($key,$innerItem));
71-
});
93+
$item = ($this->createCacheItem)($key,$innerItem);
94+
$item->set($value =$callback($item));
95+
($this->setInnerItem)($innerItem, (array)$item);
96+
97+
return$value;
98+
},$beta);
7299
}
73100

74101
/**
@@ -164,13 +191,11 @@ private function doSave(CacheItemInterface $item, $method)
164191
returnfalse;
165192
}
166193
$item = (array)$item;
167-
$expiry =$item["\0*\0expiry"];
168-
if (null ===$expiry &&0 <$item["\0*\0defaultLifetime"]) {
169-
$expiry =time() +$item["\0*\0defaultLifetime"];
194+
if (null ===$item["\0*\0expiry"] &&0 <$item["\0*\0defaultLifetime"]) {
195+
$item["\0*\0expiry"] =time() +$item["\0*\0defaultLifetime"];
170196
}
171197
$innerItem =$item["\0*\0poolHash"] ===$this->poolHash ?$item["\0*\0innerItem"] :$this->pool->getItem($this->namespace.$item["\0*\0key"]);
172-
$innerItem->set($item["\0*\0value"]);
173-
$innerItem->expiresAt(null !==$expiry ? \DateTime::createFromFormat('U',$expiry) :null);
198+
($this->setInnerItem)($innerItem,$item);
174199

175200
return$this->pool->$method($innerItem);
176201
}

‎src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ function (CacheItem $item, $key, array &$itemTags) {
6767
}
6868
if (isset($itemTags[$key])) {
6969
foreach ($itemTags[$key]as$tag =>$version) {
70-
$item->prevTags[$tag] =$tag;
70+
$item->stats[CacheItem::STATS_TAGS][$tag] =$tag;
7171
}
7272
unset($itemTags[$key]);
7373
}else {
@@ -84,7 +84,7 @@ function (CacheItem $item, $key, array &$itemTags) {
8484
function ($deferred) {
8585
$tagsByKey =array();
8686
foreach ($deferredas$key =>$item) {
87-
$tagsByKey[$key] =$item->tags;
87+
$tagsByKey[$key] =$item->newStats[CacheItem::STATS_TAGS] ??array();
8888
}
8989

9090
return$tagsByKey;

‎src/Symfony/Component/Cache/Adapter/TraceableAdapter.php‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public function __construct(AdapterInterface $pool)
3737
/**
3838
* {@inheritdoc}
3939
*/
40-
publicfunctionget(string$key,callable$callback)
40+
publicfunctionget(string$key,callable$callback,float$beta =null)
4141
{
4242
if (!$this->poolinstanceof CacheInterface) {
4343
thrownew \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".',get_class($this->pool), CacheInterface::class));
@@ -52,7 +52,7 @@ public function get(string $key, callable $callback)
5252

5353
$event =$this->start(__FUNCTION__);
5454
try {
55-
$value =$this->pool->get($key,$callback);
55+
$value =$this->pool->get($key,$callback,$beta);
5656
$event->result[$key] =\is_object($value) ?\get_class($value) :gettype($value);
5757
}finally {
5858
$event->end =microtime(true);

‎src/Symfony/Component/Cache/CHANGELOG.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ CHANGELOG
44
4.2.0
55
-----
66

7-
* added`CacheInterface` and`TaggableCacheInterface`
7+
* added`CacheInterface` and`TaggableCacheInterface`, providing stampede protection via probabilistic early expiration
88
* throw`LogicException` when`CacheItem::tag()` is called on an item coming from a non tag-aware pool
9+
* deprecated`CacheItem::getPreviousTags()`, use`CacheItem::getStats()` instead
910

1011
3.4.0
1112
-----
@@ -19,7 +20,7 @@ CHANGELOG
1920
3.3.0
2021
-----
2122

22-
*[EXPERIMENTAL]added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any
23+
* added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any
2324
* added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters
2425
* added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16
2526
* added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16)

‎src/Symfony/Component/Cache/CacheInterface.php‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@ interface CacheInterface
3030
{
3131
/**
3232
* @param callable(CacheItemInterface):mixed $callback Should return the computed value for the given key/item
33+
* @param float|null $beta A float that controls the likeliness of triggering early expiration.
34+
* 0 disables it, INF forces immediate expiration.
35+
* The default (or providing null) is implementation dependent but should
36+
* typically be 1.0, which should provide optimal stampede protection.
3337
*
3438
* @return mixed The value corresponding to the provided key
3539
*/
36-
publicfunctionget(string$key,callable$callback);
40+
publicfunctionget(string$key,callable$callback,float$beta =null);
3741
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp