Aaron Schulz has uploaded a new change for review.
https://gerrit.wikimedia.org/r/288347
Change subject: [WIP] Support key versioning in WANObjectCache
......................................................................
[WIP] Support key versioning in WANObjectCache
* getWithSetCallback() takes a 'version' parameter.
* If the value at a key has a different version, then
getWithSetcallback() will automatically use a separate
key. Which value "wins" the main key does not matter.
* Purges are handing by using the main key as a sort of
check key (with no hold-off). Note that this key is always
purged on delete().
* Changed stash keys to track the same info as other keys
both for consistency and because this change needs the
generation timestamp. Renamed the stash prefix to avoid
corrupt results with Het Deploy.
* This is useful for things like the User class that use
versioning and have cross-wiki key access and purges.
Currently, bumps to version must be deployed to all wikis
at once, which this aims to avoid.
Change-Id: I26ae62f116e32b48bcf06bc13f8b9e79ae976745
---
M includes/libs/objectcache/WANObjectCache.php
1 file changed, 89 insertions(+), 18 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core
refs/changes/47/288347/1
diff --git a/includes/libs/objectcache/WANObjectCache.php
b/includes/libs/objectcache/WANObjectCache.php
index 18cc10e..aca08e1 100644
--- a/includes/libs/objectcache/WANObjectCache.php
+++ b/includes/libs/objectcache/WANObjectCache.php
@@ -127,10 +127,13 @@
const ERR_RELAY = 4; // relay broadcast failed
const VALUE_KEY_PREFIX = 'WANCache:v:';
- const STASH_KEY_PREFIX = 'WANCache:s:';
+ const INTERIM_KEY_PREFIX = 'WANCache:i:';
const TIME_KEY_PREFIX = 'WANCache:t:';
const PURGE_VAL_PREFIX = 'PURGED:';
+
+ const VFLD_DATA = 'WC:d'; // key to the value of versioned data
+ const VFLD_VERSION = 'WC:v'; // key to the version of the value present
const MAX_PC_KEYS = 1000; // max keys to keep in process cache
@@ -207,14 +210,17 @@
* That method has cache slam avoiding features for hot/expensive keys.
*
* @param string $key Cache key
- * @param mixed $curTTL Approximate TTL left on the key if present
[returned]
+ * @param mixed $curTTL Approximate TTL left on the key if
present/tombstoned [returned]
* @param array $checkKeys List of "check" keys
+ * @param float &$asOf Key generation UNIX timestamp or null [returned]
* @return mixed Value of cache key or false on failure
*/
- final public function get( $key, &$curTTL = null, array $checkKeys = []
) {
+ final public function get( $key, &$curTTL = null, array $checkKeys =
[], &$asOf = null ) {
$curTTLs = [];
- $values = $this->getMulti( [ $key ], $curTTLs, $checkKeys );
+ $asOfs = [];
+ $values = $this->getMulti( [ $key ], $curTTLs, $checkKeys,
$asOfs );
$curTTL = isset( $curTTLs[$key] ) ? $curTTLs[$key] : null;
+ $asOf = isset( $asOfs[$key] ) ? $asOfs[$key] : null;
return isset( $values[$key] ) ? $values[$key] : false;
}
@@ -228,13 +234,15 @@
* @param array $curTTLs Map of (key => approximate TTL left) for
existing keys [returned]
* @param array $checkKeys List of check keys to apply to all $keys.
May also apply "check"
* keys to specific cache keys only by using cache keys as keys in the
$checkKeys array.
+ * @param float[] &$asOfs Map of (key => generation UNIX timestamp or
null)
* @return array Map of (key => value) for keys that exist
*/
final public function getMulti(
- array $keys, &$curTTLs = [], array $checkKeys = []
+ array $keys, &$curTTLs = [], array $checkKeys = [], array
&$asOfs = []
) {
$result = [];
$curTTLs = [];
+ $asOfs = [];
$vPrefixLen = strlen( self::VALUE_KEY_PREFIX );
$valueKeys = self::prefixCacheKeys( $keys,
self::VALUE_KEY_PREFIX );
@@ -297,6 +305,7 @@
}
}
$curTTLs[$key] = $curTTL;
+ $asOfs[$key] = $value ? $wrappedValues[self::FLD_TIME]
: null;
}
return $result;
@@ -760,13 +769,17 @@
* higher this is set, the higher the worst-case staleness can be.
* Use WANObjectCache::TSE_NONE to disable this logic.
* Default: WANObjectCache::TSE_NONE.
- * - pcTTL : process cache the value in this PHP instance with this
TTL. This avoids
+ * - pcTTL : Process cache the value in this PHP instance with this
TTL. This avoids
* network I/O when a key is read several times. This will not
cache if the callback
* returns false however. Note that any purges will not be seen
while process cached;
* since the callback should use slave DBs and they may be lagged
or have snapshot
* isolation anyway, this should not typically matter.
* Default: WANObjectCache::TTL_UNCACHEABLE.
- * @return mixed Value to use for the key
+ * - version : Integer version number. If set, this allows for
callers to make breaking
+ * changes to how values are stored while maintaining
compatability and correct cache
+ * purges. New versions are stored alongside older versions
concurrently. Avoid storing
+ * class objects however, as this reduces compatibility (due to
serialization).
+ * @return mixed Value found or written to the key
*/
final public function getWithSetCallback( $key, $ttl, $callback, array
$opts = [] ) {
$pcTTL = isset( $opts['pcTTL'] ) ? $opts['pcTTL'] :
self::TTL_UNCACHEABLE;
@@ -776,7 +789,38 @@
if ( $value === false ) {
// Fetch the value over the network
- $value = $this->doGetWithSetCallback( $key, $ttl,
$callback, $opts );
+ if ( isset( $opts['version'] ) ) {
+ $version = $opts['version'];
+ $asOf = 0.0;
+ $cur = $this->doGetWithSetCallback(
+ $key,
+ $ttl,
+ function ( $oldValue, &$ttl, &$setOpts
) use ( $callback, $version ) {
+ return [
+ self::VFLD_DATA =>
$callback(),
+ self::VFLD_VERSION =>
$version
+ ];
+ },
+ $opts,
+ $asOf
+ );
+ if ( $cur[self::VFLD_VERSION] === $version ) {
+ // Value created or existed before with
version; use it
+ $value = $cur[self::VFLD_DATA];
+ } else {
+ // Value existed before with a
different version; use variant key.
+ // Reflect purges to $key by requiring
than this key value be newer.
+ $value = $this->doGetWithSetCallback(
+ 'wan-cache-variant:' . md5(
$key ) . ":$version",
+ $ttl,
+ $callback,
+ $opts + [ 'minTime' => $asOf ]
// regenerate if not newer than $key
+ );
+ }
+ } else {
+ $value = $this->doGetWithSetCallback( $key,
$ttl, $callback, $opts );
+ }
+
// Update the process cache if enabled
if ( $pcTTL >= 0 && $value !== false ) {
$this->procCache->set( $key, $value, $pcTTL );
@@ -795,20 +839,27 @@
* @param integer $ttl
* @param callback $callback
* @param array $opts
+ * @param float &$asOf
* @return mixed
*/
- protected function doGetWithSetCallback( $key, $ttl, $callback, array
$opts ) {
+ protected function doGetWithSetCallback( $key, $ttl, $callback, array
$opts, &$asOf = 0.0 ) {
$lowTTL = isset( $opts['lowTTL'] ) ? $opts['lowTTL'] : min(
self::LOW_TTL, $ttl );
$lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] :
self::TSE_NONE;
$checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] :
[];
+ $minTime = isset( $opts['minTime'] ) ? $opts['minTime'] : 0.0;
+ $versioned = isset( $opts['version'] );
// Get the current key value
$curTTL = null;
- $cValue = $this->get( $key, $curTTL, $checkKeys ); // current
value
+ $cValue = $this->get( $key, $curTTL, $checkKeys, $asOf ); //
current value
$value = $cValue; // return value
// Determine if a regeneration is desired
- if ( $value !== false && $curTTL > 0 && !$this->worthRefresh(
$curTTL, $lowTTL ) ) {
+ if ( $value !== false
+ && $curTTL > 0
+ && $this->isValid( $value, $versioned, $asOf, $minTime )
+ && !$this->worthRefresh( $curTTL, $lowTTL )
+ ) {
return $value;
}
@@ -828,15 +879,18 @@
if ( $this->cache->lock( $key, 0, self::LOCK_TTL ) ) {
// Lock acquired; this thread should update the
key
$lockAcquired = true;
- } elseif ( $value !== false ) {
+ } elseif ( $value !== false && $this->isValid( $value,
$versioned, $asOf, $minTime ) ) {
// If it cannot be acquired; then the stale
value can be used
return $value;
} else {
- // Use the stash value for tombstoned keys to
reduce regeneration load.
+ // Use the INTERIM value for tombstoned keys to
reduce regeneration load.
// For hot keys, either another thread has the
lock or the lock failed;
- // use the stash value from the last thread
that regenerated it.
- $value = $this->cache->get(
self::STASH_KEY_PREFIX . $key );
- if ( $value !== false ) {
+ // use the INTERIM value from the last thread
that regenerated it.
+ $wrapped = $this->cache->get(
self::INTERIM_KEY_PREFIX . $key );
+ $value = $this->unwrap( $wrapped, microtime(
true ) );
+ if ( $value !== false && $this->isValid(
$value, $versioned, $asOf, $minTime ) ) {
+ $asOf = $wrapped[self::FLD_TIME];
+
return $value;
}
}
@@ -849,11 +903,13 @@
// Generate the new value from the callback...
$setOpts = [];
$value = call_user_func_array( $callback, [ $cValue, &$ttl,
&$setOpts ] );
+ $asOf = microtime( true );
// When delete() is called, writes are write-holed by the
tombstone,
- // so use a special stash key to pass the new value around
threads.
+ // so use a special INTERIM key to pass the new value around
threads.
if ( $useMutex && $value !== false && $ttl >= 0 ) {
$tempTTL = max( 1, (int)$lockTSE ); // set() expects
seconds
- $this->cache->set( self::STASH_KEY_PREFIX . $key,
$value, $tempTTL );
+ $wrapped = $this->wrap( $value, $tempTTL );
+ $this->cache->set( self::INTERIM_KEY_PREFIX . $key,
$wrapped, $tempTTL );
}
if ( $lockAcquired ) {
@@ -1005,6 +1061,21 @@
}
/**
+ * @param array $value
+ * @param bool $versioned
+ * @param float $minTime The last time the main value was generated
(0.0 if unknown)
+ * @param float $asOf The time $value was generated
+ * @return bool Whether $value is versioned or $version is false
+ */
+ protected function isValid( $value, $versioned, $minTime, $asOf ) {
+ if ( $versioned && !isset( $value[self::VFLD_VERSION] ) ) {
+ return false;
+ }
+
+ return ( $asOf > $minTime );
+ }
+
+ /**
* Do not use this method outside WANObjectCache
*
* @param mixed $value
--
To view, visit https://gerrit.wikimedia.org/r/288347
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I26ae62f116e32b48bcf06bc13f8b9e79ae976745
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Aaron Schulz <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits