-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[Cache] Add stampede protection via probabilistic early expiration #27009
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
8e556be to
0b647d2
Compare
| */ | ||
| public function get(string $key, callable $callback, float $beta = null) | ||
| { | ||
| if (!\is_string($key)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just asking: above we have string $key in the method argument ... will this if (!is_string($key)) code be ever executed if we pass a non-string argument?
| ); | ||
| $this->setInnerItem = \Closure::bind( | ||
| function (CacheItemInterface $innerItem, array $item) { | ||
| if ($stats = $item["\0*\0newStats"]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure it it'd help much, but maybe move \0*\0 to private const PREFIX = '\0*\0'; in this class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure also, using the const would just add indirection IMHO, for a purely internal thing.
Other comments addressed thanks.
| { | ||
| /** | ||
| * @param callable(CacheItemInterface $item):mixed $callback Should return the computed value for the given key/item | ||
| * @param float $beta A float that controls the likelyness of triggering early expiration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
likelyness -> likeness
| /** | ||
| * @param callable(CacheItemInterface $item):mixed $callback Should return the computed value for the given key/item | ||
| * @param float $beta A float that controls the likelyness of triggering early expiration. | ||
| * 0 disables it, INF forces immediate expiration. The default is implementation dependend |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dependend -> dependent
| final class CacheItem implements CacheItemInterface | ||
| { | ||
| /** | ||
| * References the unix timestamp stating when the item will expire, as integer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unix -> Unix
c2e16bf to
6ae48dc
Compare
|
ping @Nyholm FYI: here, I deprecate getPreviousTags() and replace it by getMetadata() |
7a76d9f to
af895ff
Compare
af895ff to
aca75fa
Compare
I designed it this way so that the interface is generic enough. Hardcoding 1.0 as default would prevent any other implementations from providing a different default. Yes, there are no other implementations than the ones in this PR, but that's not a reason to break the abstraction provided by the interface. |
99634fc to
472b593
Compare
| { | ||
| /** | ||
| * @param callable(CacheItemInterface $item):mixed $callback Should return the computed value for the given key/item | ||
| * @param float|null $beta A float that controls the likeness of triggering early expiration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: likeliness
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did the same mistake as you, but @javiereguiluz made me fix it: it's really "likeness".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They have different meanings. You mean likeliness here.
likeliness = probability
likeness = resemblance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indeed, sorry for the misunderstanding (on my side)! (fixed)
5e7db51 to
d01a58b
Compare
d01a58b to
5076b6d
Compare
|
Rebased, PR ready for review. |
5076b6d to
7ec3d50
Compare
|
PR is ready. The protection needs storing expiry+computation time. This is done by encoding these values in the key of a wrapping array: by using this magic-numbered structure, we are able to persist both raw + wrapped values in the same store, providing forward/backward compat at the storage level. Stampede protection is always enabled when accessing values through the new |
|
Does the |
|
That's the thing we need to discuss :) |
a81d3ba to
fdce538
Compare
|
Rebased |
|
ping @symfony/deciders |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've looked at this PR. It is real hard to understand what you are doing. Even though I know what you are trying to do. :/
I do not mind the API changes but I would be happy to see some more comments.
| $item->value = $v = $value; | ||
| $item->isHit = $isHit; | ||
| $item->defaultLifetime = $defaultLifetime; | ||
| if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = \key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line looks really complicated. I cannot tell what it is doing. Maybe add a comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments added, I hope it will be more clear.
| ); | ||
| $this->setInnerItem = \Closure::bind( | ||
| function (CacheItemInterface $innerItem, array $item) { | ||
| if (isset(($metadata = $item["\0*\0newMetadata"])[CacheItem::METADATA_TAGS])) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, Im really trying to understand this, but I can't =)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment added, better?
fdce538 to
f6ca712
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with minor comments
| $item->isHit = $isHit; | ||
| $item->defaultLifetime = $defaultLifetime; | ||
| // Detect wrapped values that encode for their expiry and creation duration | ||
| // For compacity, these values are packed in the key of an array using magic numbers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for compactness?
| $item->defaultLifetime = $defaultLifetime; | ||
| // Detect wrapped values that encode for their expiry and creation duration | ||
| // For compacity, these values are packed in the key of an array using magic numbers | ||
| if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = \key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic 10 here, not sure if we want to make it explicit (as we would want to make 5 and 9 explicit as well).
| if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) { | ||
| unset($metadata[CacheItem::METADATA_TAGS]); | ||
| } | ||
| // For compacity, expiry and creation duration are packed in the key of a array, using magic numbers as separators |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
compactness as well?
| $item->innerItem = $innerItem; | ||
| $item->poolHash = $poolHash; | ||
| // Detect wrapped values that encode for their expiry and creation duration | ||
| // For compacity, these values are packed in the key of an array using magic numbers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same?
f6ca712 to
fe26414
Compare
|
comments addressed thanks |
| * | ||
| * @return array | ||
| * | ||
| * @deprecated since Symfony 4.2. Use the "getMetadata()" method instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4.2, use the
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated
fe26414 to
13523ad
Compare
|
Thank you @nicolas-grekas. |
…y expiration (nicolas-grekas) This PR was merged into the 4.2-dev branch. Discussion ---------- [Cache] Add stampede protection via probabilistic early expiration | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | This PR implements [probabilistic early expiration](https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration) on top of `$cache->get($key, $callback);` It adds a 3rd arg to `CacheInterface::get`: > float $beta A float that controls the likelyness of triggering early expiration. 0 disables it, INF forces immediate expiration. The default is implementation dependend but should typically be 1.0, which should provide optimal stampede protection. Commits ------- 13523ad [Cache] Add stampede protection via probabilistic early expiration
…lculations (nicolas-grekas) This PR was merged into the 4.2-dev branch. Discussion ---------- [Cache] Use sub-second accuracy for internal expiry calculations | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | not really | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - Embeds #26929, #27009 and #27028, let's focus on the 4th commit for now. This is my last significant PR in the Cache series :) By using integer expiries internally, our current implementations are sensitive to abrupt transitions when time() goes to next second: `$s = time(); sleep(1); echo time() - $s;` *can* display 2 from time to time. This means that we do expire items earlier than required by the expiration settings on items. This also means that there is no way to have a sub-second expiry. For remote backends, that's fine, but for ArrayAdapter, that's a limitation we can remove. This PR replaces calls to `time()` by `microtime(true)`, providing more accurate timing measurements internally. Commits ------- 08554ea [Cache] Use sub-second accuracy for internal expiry calculations
This PR implements probabilistic early expiration on top of
$cache->get($key, $callback);It adds a 3rd arg to
CacheInterface::get: