Summary
Clock skew was implemented in #2484595: Add mechanism to support invalidateAll and deleteAll, but only uses 0.001 and it uses the wrong direction:
(($item_created + 0.001) > $last_bin_invalidation_time)
e.g. if we used 1000 ms tolerance it would be:
(($item_created + 1000) > $last_bin_invalidation_time)
- Item created at: 0 ms
- Bin invalidated at: 50 ms
- Item valid till forever [uh, okay ...]
Proper implementation:
- Item created at: 0 ms
- Bin invalidated at: 50 ms, bin invalidation time is set to 1050 ms
- Items created _till_ a time of 1051 ms are considered invalid.
As this only concerns deleteAll() which is seldom, we probably do not need 1 second of tolerance, but I think best is to make this a configurable and injectable parameter. Same host implementations can set it to 0 then.
Background
During development of #2989601: Support for cache tags natively in memcache we found out that time() has problems with how to deal with items written and invalidated at the same time and microtime() has the problem of needing exact clocks for multi-web-head environments (e.g. the sites we target mostly):
While we have decided to finally try to use microtime() instead of time() as in Drupal 7, we lost an important property: clock skew tolerance of 1000 ms and that means we opened ourselves to bad race conditions and hence potential *data corruption issues*, which is why this is major.
The main algorithm for tags is really simple():
- invalidate() => time()+1
- getCurrentChecksum() = min(time(), calculateChecksum())
and because here we just have:
- invalidate() => microtime(TRUE)
- item->created = microtime(TRUE)
all we need to do is
- invalidate() => microtime(TRUE) + $tolerance
- item->created = microtime(TRUE)
Items created within $tolerance microseconds will be invalid.
We could decide to go to as far as 1000 ms (like in Drupal 7) or play it more like the ChainedFast with 0.001 s, but best would be to make it configurable.
Comments
Comment #2
fabianx commentedTo comprehend this a little bit:
We really should be using this approach when writing the timestamp + configurable skew for the decision of safe vs. fast.
This does another thing (and yes it is correct):
If someone else has already written a later timestamp we _never_ write an earlier timestamp.
which is super important to fix clock skew in both directions.
Which is another reason why I think we should have a timestamp service in memcache and later in core, which we inject so that clock skew tolerance can be taken into account without having to re-implement the wheel 10x.
Cause now it seems both cache tags and deleteAll() need to implement the exact same code.
And in a way thats true, a cache tag is just one invalidation timestamp, so sure it has the same properties as the deleteAll deletion timestamp for the bin ...
Comment #3
fabianx commentedHere is a first patch based on the new invalidator service:
Comment #4
bdragon commentedsemi-blind reroll of Fabian's patch.
Comment #5
bdragon commentedFix / Tidy up the service definitions so usage is more clear.
Comment #6
bdragon commentedComment #7
bdragon commentedUpdate service definitions and documentation to use a better service naming scheme for the memcache timestamp invalidators.
Comment #8
bdragon commentedRemove default bin from MemcacheTimestampInvalidator's constructor to force the service definition to correctly define the bin.
Comment #9
fabianx commentedJust a question, but otherwise RTBC - let's get this in!
Are IDE's now namespace aware? Before it was needed to put the full class for IDE's to find the classes.
Comment #11
bdragon commentedOK, committed.
Comment #12
fabianx commented