-
Notifications
You must be signed in to change notification settings - Fork 1.4k
PHPORM-99 Enable TTL index to auto-purge of expired cache and lock items #2891
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Conversation
665930a
to
aa7bff1
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.
LGTM. Not sure about what prompted the change for the return value of the acquire
method, but it looks like it achieves the same result.
I think it saves the server from having to return the modified document, which means fewer operations. It's probably negligible, but in any case unnecessary. |
I'm not an expert, but IIRC |
@@ -107,6 +107,17 @@ public function forceRelease(): void | |||
]); | |||
} | |||
|
|||
/** Creates a TTL index that automatically deletes expired objects. */ | |||
public function createTTLIndex(): void |
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 intentionally didn't suggest using a TTL index in #2877 because it seemed redundant with the $lottery
constructor argument that remained in place.
Given that these are just public methods on the MongoLock and MongoStore classes, how do you expect users to interact with them? My understanding is that both classes would typically be used while handling a request, and this method seems more like schema setup. It makes sense for MongoDB, of course, but doesn't fit very well into whatever common API Laravel intended for these classes.
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 is an additional feature that should be documented as I have described. The methods will probably be called in a migration. I think that for applications with a significant volume of traffic, using TTL indexes will have a positive impact on performance without having to search for the correct lottery value.
$lockTimeout = $this->seconds > 0 ? $this->seconds : $this->defaultTimeoutInSeconds; | ||
|
||
return $this->currentTime() + $lockTimeout; | ||
return new UTCDateTime(Carbon::now()->addSeconds($additionalSeconds)); |
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.
What allows UTCDateTime to be constructed from a Carbon instance? Is this relying on Carbon's string representation?
I looked at the Addition and Subtraction docs for Carbon and the strings in the examples don't look correct.
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.
The Carbon
class extends DateTime
, and the UTCDateTime
constructor accepts any DateTimeInterface
. There is no string transformation.
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.
Ah, I missed Carbon's inheritance. SGTM!
$result = $this->collection->findOneAndUpdate( | ||
[ | ||
'_id' => $this->prefix . $key, | ||
'expiration' => ['$gte' => $this->currentTime()], |
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.
By removing this criteria and the preceding call to forgetIfExpired()
, you're potentially incrementing an expired document. Is that a concern?
Since the TTL index is optional, I wouldn't have expected this to change.
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 removed the previous forgetIfExpired
in order to avoid useless command. A nominal usage must be a single command.
Even if the expired item is incremented here, it is deleted just after.
src/Cache/MongoLock.php
Outdated
@@ -74,11 +74,11 @@ public function acquire(): bool | |||
], | |||
); | |||
|
|||
if (random_int(1, $this->lottery[1]) <= $this->lottery[0]) { | |||
$this->collection->deleteMany(['expiration' => ['$lte' => $this->currentTime()]]); | |||
if (! empty($this->lottery[0]) && random_int(1, $this->lottery[1]) <= $this->lottery[0]) { |
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.
Does anything enforce that $this->lottery
is a tuple? This only checks that the first element exists and is non-empty, but there is no check for a second element or both being integers.
Perhaps that should be done in the constructor.
I assume you intentionally decided not to make $lottery
nullable as a means to disable it.
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.
It seemed to me that the "zero chances on whatever" value was explicit enough to say that we wanted to disable this mechanism.
src/Cache/MongoLock.php
Outdated
} | ||
|
||
return $result['owner'] === $this->owner; | ||
return $result->getModifiedCount() > 0 || $result->getUpsertedCount() > 0; |
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.
Wasn't this logic problematic in the original PR? Are you now relying on the UTCDateTime always changing due to sub-second precision?
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.
No problem with the previous logic. I said to myself that it was impossible to have the same lock with the same owner acquired in the same microsecond. But I've reverted to that, so you don't have to worry.
*/ | ||
public function __construct( | ||
private readonly Collection $collection, | ||
string $name, | ||
int $seconds, | ||
?string $owner = null, | ||
private readonly array $lottery = [2, 100], | ||
private readonly int $defaultTimeoutInSeconds = 86400, |
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.
Why was this removed entirely? It looks like it previously served a purpose if $seconds
was zero. Now, specifying zero for $seconds
would cause locks to immediately expire.
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.
Since $defaultTimeoutInSeconds
was only a default value, I moved the processing to MongoStore::lock
. It is not possible to disable lock 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.
I trust there will be a separate PR for documentation (since this was a follow-up on an original PR).
Fix PHPORM-99
Complement to #2877
By using an
UTCDateTime
for the expiration field of cache and lock collections, we can create a TTL index.Once TTL index have been created (using a migration), the
lock_lottery
value can be set to[0, 0]
in order to disable random pruning of expired locks.Also, using a
UTCDateTime
is more precise than the int timestamp in seconds. So we can useupdateOne
as discussed here.