MediaWiki REL1_31
WANObjectCache.php
Go to the documentation of this file.
1<?php
22use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
23use Psr\Log\LoggerAwareInterface;
24use Psr\Log\LoggerInterface;
25use Psr\Log\NullLogger;
26
87class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
89 protected $cache;
91 protected $processCaches = [];
93 protected $purgeChannel;
95 protected $purgeRelayer;
97 protected $mcrouterAware;
99 protected $region;
101 protected $cluster;
103 protected $logger;
105 protected $stats;
109 protected $asyncHandler;
110
112 protected $lastRelayError = self::ERR_NONE;
113
115 private $callbackDepth = 0;
117 private $warmupCache = [];
119 private $warmupKeyMisses = 0;
120
123
127 const MAX_READ_LAG = 7;
129 const HOLDOFF_TTL = 11; // MAX_COMMIT_DELAY + MAX_READ_LAG + 1
130
132 const CHECK_KEY_TTL = self::TTL_YEAR;
135
137 const LOCK_TTL = 10;
139 const LOW_TTL = 30;
140
142 const AGE_NEW = 60;
144 const HOT_TTR = 900;
146 const HIT_RATE_HIGH = 1;
148 const RAMPUP_TTL = 30;
149
151 const TTL_UNCACHEABLE = -1;
153 const TSE_NONE = -1;
155 const TTL_LAGGED = 30;
157 const HOLDOFF_NONE = 0;
159 const STALE_TTL_NONE = 0;
161 const GRACE_TTL_NONE = 0;
162
165
167 const TINY_NEGATIVE = -0.000001;
168
170 const VERSION = 1;
171
172 const FLD_VERSION = 0; // key to cache version number
173 const FLD_VALUE = 1; // key to the cached value
174 const FLD_TTL = 2; // key to the original TTL
175 const FLD_TIME = 3; // key to the cache time
176 const FLD_FLAGS = 4; // key to the flags bitfield
177 const FLD_HOLDOFF = 5; // key to any hold-off TTL
178
180 const FLG_STALE = 1;
181
182 const ERR_NONE = 0; // no error
183 const ERR_NO_RESPONSE = 1; // no response
184 const ERR_UNREACHABLE = 2; // can't connect
185 const ERR_UNEXPECTED = 3; // response gave some error
186 const ERR_RELAY = 4; // relay broadcast failed
187
188 const VALUE_KEY_PREFIX = 'WANCache:v:';
189 const INTERIM_KEY_PREFIX = 'WANCache:i:';
190 const TIME_KEY_PREFIX = 'WANCache:t:';
191 const MUTEX_KEY_PREFIX = 'WANCache:m:';
192
193 const PURGE_VAL_PREFIX = 'PURGED:';
194
195 const VFLD_DATA = 'WOC:d'; // key to the value of versioned data
196 const VFLD_VERSION = 'WOC:v'; // key to the version of the value present
197
198 const PC_PRIMARY = 'primary:1000'; // process cache name and max key count
199
200 const DEFAULT_PURGE_CHANNEL = 'wancache-purge';
201
227 public function __construct( array $params ) {
228 $this->cache = $params['cache'];
229 $this->purgeChannel = isset( $params['channels']['purge'] )
230 ? $params['channels']['purge']
231 : self::DEFAULT_PURGE_CHANNEL;
232 $this->purgeRelayer = isset( $params['relayers']['purge'] )
233 ? $params['relayers']['purge']
234 : new EventRelayerNull( [] );
235 $this->region = isset( $params['region'] ) ? $params['region'] : 'main';
236 $this->cluster = isset( $params['cluster'] ) ? $params['cluster'] : 'wan-main';
237 $this->mcrouterAware = !empty( $params['mcrouterAware'] );
238
239 $this->setLogger( isset( $params['logger'] ) ? $params['logger'] : new NullLogger() );
240 $this->stats = isset( $params['stats'] ) ? $params['stats'] : new NullStatsdDataFactory();
241 $this->asyncHandler = isset( $params['asyncHandler'] ) ? $params['asyncHandler'] : null;
242 }
243
247 public function setLogger( LoggerInterface $logger ) {
248 $this->logger = $logger;
249 }
250
256 public static function newEmpty() {
257 return new static( [
258 'cache' => new EmptyBagOStuff()
259 ] );
260 }
261
303 final public function get( $key, &$curTTL = null, array $checkKeys = [], &$asOf = null ) {
304 $curTTLs = [];
305 $asOfs = [];
306 $values = $this->getMulti( [ $key ], $curTTLs, $checkKeys, $asOfs );
307 $curTTL = isset( $curTTLs[$key] ) ? $curTTLs[$key] : null;
308 $asOf = isset( $asOfs[$key] ) ? $asOfs[$key] : null;
309
310 return isset( $values[$key] ) ? $values[$key] : false;
311 }
312
325 final public function getMulti(
326 array $keys, &$curTTLs = [], array $checkKeys = [], array &$asOfs = []
327 ) {
328 $result = [];
329 $curTTLs = [];
330 $asOfs = [];
331
332 $vPrefixLen = strlen( self::VALUE_KEY_PREFIX );
333 $valueKeys = self::prefixCacheKeys( $keys, self::VALUE_KEY_PREFIX );
334
335 $checkKeysForAll = [];
336 $checkKeysByKey = [];
337 $checkKeysFlat = [];
338 foreach ( $checkKeys as $i => $checkKeyGroup ) {
339 $prefixed = self::prefixCacheKeys( (array)$checkKeyGroup, self::TIME_KEY_PREFIX );
340 $checkKeysFlat = array_merge( $checkKeysFlat, $prefixed );
341 // Is this check keys for a specific cache key, or for all keys being fetched?
342 if ( is_int( $i ) ) {
343 $checkKeysForAll = array_merge( $checkKeysForAll, $prefixed );
344 } else {
345 $checkKeysByKey[$i] = isset( $checkKeysByKey[$i] )
346 ? array_merge( $checkKeysByKey[$i], $prefixed )
347 : $prefixed;
348 }
349 }
350
351 // Fetch all of the raw values
352 $keysGet = array_merge( $valueKeys, $checkKeysFlat );
353 if ( $this->warmupCache ) {
354 $wrappedValues = array_intersect_key( $this->warmupCache, array_flip( $keysGet ) );
355 $keysGet = array_diff( $keysGet, array_keys( $wrappedValues ) ); // keys left to fetch
356 $this->warmupKeyMisses += count( $keysGet );
357 } else {
358 $wrappedValues = [];
359 }
360 if ( $keysGet ) {
361 $wrappedValues += $this->cache->getMulti( $keysGet );
362 }
363 // Time used to compare/init "check" keys (derived after getMulti() to be pessimistic)
364 $now = $this->getCurrentTime();
365
366 // Collect timestamps from all "check" keys
367 $purgeValuesForAll = $this->processCheckKeys( $checkKeysForAll, $wrappedValues, $now );
368 $purgeValuesByKey = [];
369 foreach ( $checkKeysByKey as $cacheKey => $checks ) {
370 $purgeValuesByKey[$cacheKey] =
371 $this->processCheckKeys( $checks, $wrappedValues, $now );
372 }
373
374 // Get the main cache value for each key and validate them
375 foreach ( $valueKeys as $vKey ) {
376 if ( !isset( $wrappedValues[$vKey] ) ) {
377 continue; // not found
378 }
379
380 $key = substr( $vKey, $vPrefixLen ); // unprefix
381
382 list( $value, $curTTL ) = $this->unwrap( $wrappedValues[$vKey], $now );
383 if ( $value !== false ) {
384 $result[$key] = $value;
385
386 // Force dependant keys to be invalid for a while after purging
387 // to reduce race conditions involving stale data getting cached
388 $purgeValues = $purgeValuesForAll;
389 if ( isset( $purgeValuesByKey[$key] ) ) {
390 $purgeValues = array_merge( $purgeValues, $purgeValuesByKey[$key] );
391 }
392 foreach ( $purgeValues as $purge ) {
393 $safeTimestamp = $purge[self::FLD_TIME] + $purge[self::FLD_HOLDOFF];
394 if ( $safeTimestamp >= $wrappedValues[$vKey][self::FLD_TIME] ) {
395 // How long ago this value was expired by *this* check key
396 $ago = min( $purge[self::FLD_TIME] - $now, self::TINY_NEGATIVE );
397 // How long ago this value was expired by *any* known check key
398 $curTTL = min( $curTTL, $ago );
399 }
400 }
401 }
402 $curTTLs[$key] = $curTTL;
403 $asOfs[$key] = ( $value !== false ) ? $wrappedValues[$vKey][self::FLD_TIME] : null;
404 }
405
406 return $result;
407 }
408
416 private function processCheckKeys( array $timeKeys, array $wrappedValues, $now ) {
417 $purgeValues = [];
418 foreach ( $timeKeys as $timeKey ) {
419 $purge = isset( $wrappedValues[$timeKey] )
420 ? $this->parsePurgeValue( $wrappedValues[$timeKey] )
421 : false;
422 if ( $purge === false ) {
423 // Key is not set or invalid; regenerate
424 $newVal = $this->makePurgeValue( $now, self::HOLDOFF_TTL );
425 $this->cache->add( $timeKey, $newVal, self::CHECK_KEY_TTL );
426 $purge = $this->parsePurgeValue( $newVal );
427 }
428 $purgeValues[] = $purge;
429 }
430 return $purgeValues;
431 }
432
498 final public function set( $key, $value, $ttl = 0, array $opts = [] ) {
499 $now = $this->getCurrentTime();
500 $lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
501 $staleTTL = isset( $opts['staleTTL'] ) ? $opts['staleTTL'] : self::STALE_TTL_NONE;
502 $age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
503 $lag = isset( $opts['lag'] ) ? $opts['lag'] : 0;
504
505 // Do not cache potentially uncommitted data as it might get rolled back
506 if ( !empty( $opts['pending'] ) ) {
507 $this->logger->info( 'Rejected set() for {cachekey} due to pending writes.',
508 [ 'cachekey' => $key ] );
509
510 return true; // no-op the write for being unsafe
511 }
512
513 $wrapExtra = []; // additional wrapped value fields
514 // Check if there's a risk of writing stale data after the purge tombstone expired
515 if ( $lag === false || ( $lag + $age ) > self::MAX_READ_LAG ) {
516 // Case A: read lag with "lockTSE"; save but record value as stale
517 if ( $lockTSE >= 0 ) {
518 $ttl = max( 1, (int)$lockTSE ); // set() expects seconds
519 $wrapExtra[self::FLD_FLAGS] = self::FLG_STALE; // mark as stale
520 // Case B: any long-running transaction; ignore this set()
521 } elseif ( $age > self::MAX_READ_LAG ) {
522 $this->logger->info( 'Rejected set() for {cachekey} due to snapshot lag.',
523 [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
524
525 return true; // no-op the write for being unsafe
526 // Case C: high replication lag; lower TTL instead of ignoring all set()s
527 } elseif ( $lag === false || $lag > self::MAX_READ_LAG ) {
528 $ttl = $ttl ? min( $ttl, self::TTL_LAGGED ) : self::TTL_LAGGED;
529 $this->logger->warning( 'Lowered set() TTL for {cachekey} due to replication lag.',
530 [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
531 // Case D: medium length request with medium replication lag; ignore this set()
532 } else {
533 $this->logger->info( 'Rejected set() for {cachekey} due to high read lag.',
534 [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
535
536 return true; // no-op the write for being unsafe
537 }
538 }
539
540 // Wrap that value with time/TTL/version metadata
541 $wrapped = $this->wrap( $value, $ttl, $now ) + $wrapExtra;
542
543 $func = function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
544 return ( is_string( $cWrapped ) )
545 ? false // key is tombstoned; do nothing
546 : $wrapped;
547 };
548
549 return $this->cache->merge( self::VALUE_KEY_PREFIX . $key, $func, $ttl + $staleTTL, 1 );
550 }
551
613 final public function delete( $key, $ttl = self::HOLDOFF_TTL ) {
614 $key = self::VALUE_KEY_PREFIX . $key;
615
616 if ( $ttl <= 0 ) {
617 // Publish the purge to all datacenters
618 $ok = $this->relayDelete( $key );
619 } else {
620 // Publish the purge to all datacenters
621 $ok = $this->relayPurge( $key, $ttl, self::HOLDOFF_NONE );
622 }
623
624 return $ok;
625 }
626
646 final public function getCheckKeyTime( $key ) {
647 return $this->getMultiCheckKeyTime( [ $key ] )[$key];
648 }
649
711 final public function getMultiCheckKeyTime( array $keys ) {
712 $rawKeys = [];
713 foreach ( $keys as $key ) {
714 $rawKeys[$key] = self::TIME_KEY_PREFIX . $key;
715 }
716
717 $rawValues = $this->cache->getMulti( $rawKeys );
718 $rawValues += array_fill_keys( $rawKeys, false );
719
720 $times = [];
721 foreach ( $rawKeys as $key => $rawKey ) {
722 $purge = $this->parsePurgeValue( $rawValues[$rawKey] );
723 if ( $purge !== false ) {
724 $time = $purge[self::FLD_TIME];
725 } else {
726 // Casting assures identical floats for the next getCheckKeyTime() calls
727 $now = (string)$this->getCurrentTime();
728 $this->cache->add(
729 $rawKey,
730 $this->makePurgeValue( $now, self::HOLDOFF_TTL ),
731 self::CHECK_KEY_TTL
732 );
733 $time = (float)$now;
734 }
735
736 $times[$key] = $time;
737 }
738
739 return $times;
740 }
741
776 final public function touchCheckKey( $key, $holdoff = self::HOLDOFF_TTL ) {
777 // Publish the purge to all datacenters
778 return $this->relayPurge( self::TIME_KEY_PREFIX . $key, self::CHECK_KEY_TTL, $holdoff );
779 }
780
808 final public function resetCheckKey( $key ) {
809 // Publish the purge to all datacenters
810 return $this->relayDelete( self::TIME_KEY_PREFIX . $key );
811 }
812
1057 final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [] ) {
1058 $pcTTL = isset( $opts['pcTTL'] ) ? $opts['pcTTL'] : self::TTL_UNCACHEABLE;
1059
1060 // Try the process cache if enabled and the cache callback is not within a cache callback.
1061 // Process cache use in nested callbacks is not lag-safe with regard to HOLDOFF_TTL since
1062 // the in-memory value is further lagged than the shared one since it uses a blind TTL.
1063 if ( $pcTTL >= 0 && $this->callbackDepth == 0 ) {
1064 $group = isset( $opts['pcGroup'] ) ? $opts['pcGroup'] : self::PC_PRIMARY;
1065 $procCache = $this->getProcessCache( $group );
1066 $value = $procCache->get( $key );
1067 } else {
1068 $procCache = false;
1069 $value = false;
1070 }
1071
1072 if ( $value === false ) {
1073 // Fetch the value over the network
1074 if ( isset( $opts['version'] ) ) {
1075 $version = $opts['version'];
1076 $asOf = null;
1077 $cur = $this->doGetWithSetCallback(
1078 $key,
1079 $ttl,
1080 function ( $oldValue, &$ttl, &$setOpts, $oldAsOf )
1081 use ( $callback, $version ) {
1082 if ( is_array( $oldValue )
1083 && array_key_exists( self::VFLD_DATA, $oldValue )
1084 && array_key_exists( self::VFLD_VERSION, $oldValue )
1085 && $oldValue[self::VFLD_VERSION] === $version
1086 ) {
1087 $oldData = $oldValue[self::VFLD_DATA];
1088 } else {
1089 // VFLD_DATA is not set if an old, unversioned, key is present
1090 $oldData = false;
1091 $oldAsOf = null;
1092 }
1093
1094 return [
1095 self::VFLD_DATA => $callback( $oldData, $ttl, $setOpts, $oldAsOf ),
1096 self::VFLD_VERSION => $version
1097 ];
1098 },
1099 $opts,
1100 $asOf
1101 );
1102 if ( $cur[self::VFLD_VERSION] === $version ) {
1103 // Value created or existed before with version; use it
1104 $value = $cur[self::VFLD_DATA];
1105 } else {
1106 // Value existed before with a different version; use variant key.
1107 // Reflect purges to $key by requiring that this key value be newer.
1109 $this->makeGlobalKey( 'WANCache-key-variant', md5( $key ), $version ),
1110 $ttl,
1111 $callback,
1112 // Regenerate value if not newer than $key
1113 [ 'version' => null, 'minAsOf' => $asOf ] + $opts
1114 );
1115 }
1116 } else {
1117 $value = $this->doGetWithSetCallback( $key, $ttl, $callback, $opts );
1118 }
1119
1120 // Update the process cache if enabled
1121 if ( $procCache && $value !== false ) {
1122 $procCache->set( $key, $value, $pcTTL );
1123 }
1124 }
1125
1126 return $value;
1127 }
1128
1142 protected function doGetWithSetCallback( $key, $ttl, $callback, array $opts, &$asOf = null ) {
1143 $lowTTL = isset( $opts['lowTTL'] ) ? $opts['lowTTL'] : min( self::LOW_TTL, $ttl );
1144 $lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
1145 $staleTTL = isset( $opts['staleTTL'] ) ? $opts['staleTTL'] : self::STALE_TTL_NONE;
1146 $graceTTL = isset( $opts['graceTTL'] ) ? $opts['graceTTL'] : self::GRACE_TTL_NONE;
1147 $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : [];
1148 $busyValue = isset( $opts['busyValue'] ) ? $opts['busyValue'] : null;
1149 $popWindow = isset( $opts['hotTTR'] ) ? $opts['hotTTR'] : self::HOT_TTR;
1150 $ageNew = isset( $opts['ageNew'] ) ? $opts['ageNew'] : self::AGE_NEW;
1151 $minTime = isset( $opts['minAsOf'] ) ? $opts['minAsOf'] : self::MIN_TIMESTAMP_NONE;
1152 $versioned = isset( $opts['version'] );
1153
1154 // Get a collection name to describe this class of key
1155 $kClass = $this->determineKeyClass( $key );
1156
1157 // Get the current key value
1158 $curTTL = null;
1159 $cValue = $this->get( $key, $curTTL, $checkKeys, $asOf ); // current value
1160 $value = $cValue; // return value
1161
1162 $preCallbackTime = $this->getCurrentTime();
1163 // Determine if a cached value regeneration is needed or desired
1164 if ( $value !== false
1165 && $this->isAliveOrInGracePeriod( $curTTL, $graceTTL )
1166 && $this->isValid( $value, $versioned, $asOf, $minTime )
1167 ) {
1168 $preemptiveRefresh = (
1169 $this->worthRefreshExpiring( $curTTL, $lowTTL ) ||
1170 $this->worthRefreshPopular( $asOf, $ageNew, $popWindow, $preCallbackTime )
1171 );
1172
1173 if ( !$preemptiveRefresh ) {
1174 $this->stats->increment( "wanobjectcache.$kClass.hit.good" );
1175
1176 return $value;
1177 } elseif ( $this->asyncHandler ) {
1178 // Update the cache value later, such during post-send of an HTTP request
1179 $func = $this->asyncHandler;
1180 $func( function () use ( $key, $ttl, $callback, $opts, $asOf ) {
1181 $opts['minAsOf'] = INF; // force a refresh
1182 $this->doGetWithSetCallback( $key, $ttl, $callback, $opts, $asOf );
1183 } );
1184 $this->stats->increment( "wanobjectcache.$kClass.hit.refresh" );
1185
1186 return $value;
1187 }
1188 }
1189
1190 // A deleted key with a negative TTL left must be tombstoned
1191 $isTombstone = ( $curTTL !== null && $value === false );
1192 if ( $isTombstone && $lockTSE <= 0 ) {
1193 // Use the INTERIM value for tombstoned keys to reduce regeneration load
1194 $lockTSE = self::INTERIM_KEY_TTL;
1195 }
1196 // Assume a key is hot if requested soon after invalidation
1197 $isHot = ( $curTTL !== null && $curTTL <= 0 && abs( $curTTL ) <= $lockTSE );
1198 // Use the mutex if there is no value and a busy fallback is given
1199 $checkBusy = ( $busyValue !== null && $value === false );
1200 // Decide whether a single thread should handle regenerations.
1201 // This avoids stampedes when $checkKeys are bumped and when preemptive
1202 // renegerations take too long. It also reduces regenerations while $key
1203 // is tombstoned. This balances cache freshness with avoiding DB load.
1204 $useMutex = ( $isHot || ( $isTombstone && $lockTSE > 0 ) || $checkBusy );
1205
1206 $lockAcquired = false;
1207 if ( $useMutex ) {
1208 // Acquire a datacenter-local non-blocking lock
1209 if ( $this->cache->add( self::MUTEX_KEY_PREFIX . $key, 1, self::LOCK_TTL ) ) {
1210 // Lock acquired; this thread should update the key
1211 $lockAcquired = true;
1212 } elseif ( $value !== false && $this->isValid( $value, $versioned, $asOf, $minTime ) ) {
1213 $this->stats->increment( "wanobjectcache.$kClass.hit.stale" );
1214 // If it cannot be acquired; then the stale value can be used
1215 return $value;
1216 } else {
1217 // Use the INTERIM value for tombstoned keys to reduce regeneration load.
1218 // For hot keys, either another thread has the lock or the lock failed;
1219 // use the INTERIM value from the last thread that regenerated it.
1220 $value = $this->getInterimValue( $key, $versioned, $minTime, $asOf );
1221 if ( $value !== false ) {
1222 $this->stats->increment( "wanobjectcache.$kClass.hit.volatile" );
1223
1224 return $value;
1225 }
1226 // Use the busy fallback value if nothing else
1227 if ( $busyValue !== null ) {
1228 $this->stats->increment( "wanobjectcache.$kClass.miss.busy" );
1229
1230 return is_callable( $busyValue ) ? $busyValue() : $busyValue;
1231 }
1232 }
1233 }
1234
1235 if ( !is_callable( $callback ) ) {
1236 throw new InvalidArgumentException( "Invalid cache miss callback provided." );
1237 }
1238
1239 // Generate the new value from the callback...
1240 $setOpts = [];
1241 ++$this->callbackDepth;
1242 try {
1243 $value = call_user_func_array( $callback, [ $cValue, &$ttl, &$setOpts, $asOf ] );
1244 } finally {
1245 --$this->callbackDepth;
1246 }
1247 $valueIsCacheable = ( $value !== false && $ttl >= 0 );
1248
1249 // When delete() is called, writes are write-holed by the tombstone,
1250 // so use a special INTERIM key to pass the new value around threads.
1251 if ( ( $isTombstone && $lockTSE > 0 ) && $valueIsCacheable ) {
1252 $tempTTL = max( 1, (int)$lockTSE ); // set() expects seconds
1253 $newAsOf = $this->getCurrentTime();
1254 $wrapped = $this->wrap( $value, $tempTTL, $newAsOf );
1255 // Avoid using set() to avoid pointless mcrouter broadcasting
1256 $this->setInterimValue( $key, $wrapped, $tempTTL );
1257 }
1258
1259 if ( $valueIsCacheable ) {
1260 $setOpts['lockTSE'] = $lockTSE;
1261 $setOpts['staleTTL'] = $staleTTL;
1262 // Use best known "since" timestamp if not provided
1263 $setOpts += [ 'since' => $preCallbackTime ];
1264 // Update the cache; this will fail if the key is tombstoned
1265 $this->set( $key, $value, $ttl, $setOpts );
1266 }
1267
1268 if ( $lockAcquired ) {
1269 // Avoid using delete() to avoid pointless mcrouter broadcasting
1270 $this->cache->changeTTL( self::MUTEX_KEY_PREFIX . $key, (int)$preCallbackTime - 60 );
1271 }
1272
1273 $this->stats->increment( "wanobjectcache.$kClass.miss.compute" );
1274
1275 return $value;
1276 }
1277
1285 protected function getInterimValue( $key, $versioned, $minTime, &$asOf ) {
1286 if ( !$this->useInterimHoldOffCaching ) {
1287 return false; // disabled
1288 }
1289
1290 $wrapped = $this->cache->get( self::INTERIM_KEY_PREFIX . $key );
1291 list( $value ) = $this->unwrap( $wrapped, $this->getCurrentTime() );
1292 if ( $value !== false && $this->isValid( $value, $versioned, $asOf, $minTime ) ) {
1293 $asOf = $wrapped[self::FLD_TIME];
1294
1295 return $value;
1296 }
1297
1298 return false;
1299 }
1300
1306 protected function setInterimValue( $key, $wrapped, $tempTTL ) {
1307 $this->cache->merge(
1308 self::INTERIM_KEY_PREFIX . $key,
1309 function () use ( $wrapped ) {
1310 return $wrapped;
1311 },
1312 $tempTTL,
1313 1
1314 );
1315 }
1316
1383 final public function getMultiWithSetCallback(
1384 ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
1385 ) {
1386 $valueKeys = array_keys( $keyedIds->getArrayCopy() );
1387 $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : [];
1388
1389 // Load required keys into process cache in one go
1390 $this->warmupCache = $this->getRawKeysForWarmup(
1391 $this->getNonProcessCachedKeys( $valueKeys, $opts ),
1392 $checkKeys
1393 );
1394 $this->warmupKeyMisses = 0;
1395
1396 // Wrap $callback to match the getWithSetCallback() format while passing $id to $callback
1397 $id = null; // current entity ID
1398 $func = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf ) use ( $callback, &$id ) {
1399 return $callback( $id, $oldValue, $ttl, $setOpts, $oldAsOf );
1400 };
1401
1402 $values = [];
1403 foreach ( $keyedIds as $key => $id ) { // preserve order
1404 $values[$key] = $this->getWithSetCallback( $key, $ttl, $func, $opts );
1405 }
1406
1407 $this->warmupCache = [];
1408
1409 return $values;
1410 }
1411
1477 final public function getMultiWithUnionSetCallback(
1478 ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
1479 ) {
1480 $idsByValueKey = $keyedIds->getArrayCopy();
1481 $valueKeys = array_keys( $idsByValueKey );
1482 $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : [];
1483 unset( $opts['lockTSE'] ); // incompatible
1484 unset( $opts['busyValue'] ); // incompatible
1485
1486 // Load required keys into process cache in one go
1487 $keysGet = $this->getNonProcessCachedKeys( $valueKeys, $opts );
1488 $this->warmupCache = $this->getRawKeysForWarmup( $keysGet, $checkKeys );
1489 $this->warmupKeyMisses = 0;
1490
1491 // IDs of entities known to be in need of regeneration
1492 $idsRegen = [];
1493
1494 // Find out which keys are missing/deleted/stale
1495 $curTTLs = [];
1496 $asOfs = [];
1497 $curByKey = $this->getMulti( $keysGet, $curTTLs, $checkKeys, $asOfs );
1498 foreach ( $keysGet as $key ) {
1499 if ( !array_key_exists( $key, $curByKey ) || $curTTLs[$key] < 0 ) {
1500 $idsRegen[] = $idsByValueKey[$key];
1501 }
1502 }
1503
1504 // Run the callback to populate the regeneration value map for all required IDs
1505 $newSetOpts = [];
1506 $newTTLsById = array_fill_keys( $idsRegen, $ttl );
1507 $newValsById = $idsRegen ? $callback( $idsRegen, $newTTLsById, $newSetOpts ) : [];
1508
1509 // Wrap $callback to match the getWithSetCallback() format while passing $id to $callback
1510 $id = null; // current entity ID
1511 $func = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf )
1512 use ( $callback, &$id, $newValsById, $newTTLsById, $newSetOpts )
1513 {
1514 if ( array_key_exists( $id, $newValsById ) ) {
1515 // Value was already regerated as expected, so use the value in $newValsById
1516 $newValue = $newValsById[$id];
1517 $ttl = $newTTLsById[$id];
1518 $setOpts = $newSetOpts;
1519 } else {
1520 // Pre-emptive/popularity refresh and version mismatch cases are not detected
1521 // above and thus $newValsById has no entry. Run $callback on this single entity.
1522 $ttls = [ $id => $ttl ];
1523 $newValue = $callback( [ $id ], $ttls, $setOpts )[$id];
1524 $ttl = $ttls[$id];
1525 }
1526
1527 return $newValue;
1528 };
1529
1530 // Run the cache-aside logic using warmupCache instead of persistent cache queries
1531 $values = [];
1532 foreach ( $idsByValueKey as $key => $id ) { // preserve order
1533 $values[$key] = $this->getWithSetCallback( $key, $ttl, $func, $opts );
1534 }
1535
1536 $this->warmupCache = [];
1537
1538 return $values;
1539 }
1540
1553 final public function reap( $key, $purgeTimestamp, &$isStale = false ) {
1554 $minAsOf = $purgeTimestamp + self::HOLDOFF_TTL;
1555 $wrapped = $this->cache->get( self::VALUE_KEY_PREFIX . $key );
1556 if ( is_array( $wrapped ) && $wrapped[self::FLD_TIME] < $minAsOf ) {
1557 $isStale = true;
1558 $this->logger->warning( "Reaping stale value key '$key'." );
1559 $ttlReap = self::HOLDOFF_TTL; // avoids races with tombstone creation
1560 $ok = $this->cache->changeTTL( self::VALUE_KEY_PREFIX . $key, $ttlReap );
1561 if ( !$ok ) {
1562 $this->logger->error( "Could not complete reap of key '$key'." );
1563 }
1564
1565 return $ok;
1566 }
1567
1568 $isStale = false;
1569
1570 return true;
1571 }
1572
1582 final public function reapCheckKey( $key, $purgeTimestamp, &$isStale = false ) {
1583 $purge = $this->parsePurgeValue( $this->cache->get( self::TIME_KEY_PREFIX . $key ) );
1584 if ( $purge && $purge[self::FLD_TIME] < $purgeTimestamp ) {
1585 $isStale = true;
1586 $this->logger->warning( "Reaping stale check key '$key'." );
1587 $ok = $this->cache->changeTTL( self::TIME_KEY_PREFIX . $key, self::TTL_SECOND );
1588 if ( !$ok ) {
1589 $this->logger->error( "Could not complete reap of check key '$key'." );
1590 }
1591
1592 return $ok;
1593 }
1594
1595 $isStale = false;
1596
1597 return false;
1598 }
1599
1607 public function makeKey( $class, $component = null ) {
1608 return call_user_func_array( [ $this->cache, __FUNCTION__ ], func_get_args() );
1609 }
1610
1618 public function makeGlobalKey( $class, $component = null ) {
1619 return call_user_func_array( [ $this->cache, __FUNCTION__ ], func_get_args() );
1620 }
1621
1628 final public function makeMultiKeys( array $entities, callable $keyFunc ) {
1629 $map = [];
1630 foreach ( $entities as $entity ) {
1631 $map[$keyFunc( $entity, $this )] = $entity;
1632 }
1633
1634 return new ArrayIterator( $map );
1635 }
1636
1641 final public function getLastError() {
1642 if ( $this->lastRelayError ) {
1643 // If the cache and the relayer failed, focus on the latter.
1644 // An update not making it to the relayer means it won't show up
1645 // in other DCs (nor will consistent re-hashing see up-to-date values).
1646 // On the other hand, if just the cache update failed, then it should
1647 // eventually be applied by the relayer.
1648 return $this->lastRelayError;
1649 }
1650
1651 $code = $this->cache->getLastError();
1652 switch ( $code ) {
1653 case BagOStuff::ERR_NONE:
1654 return self::ERR_NONE;
1655 case BagOStuff::ERR_NO_RESPONSE:
1656 return self::ERR_NO_RESPONSE;
1657 case BagOStuff::ERR_UNREACHABLE:
1658 return self::ERR_UNREACHABLE;
1659 default:
1660 return self::ERR_UNEXPECTED;
1661 }
1662 }
1663
1667 final public function clearLastError() {
1668 $this->cache->clearLastError();
1669 $this->lastRelayError = self::ERR_NONE;
1670 }
1671
1677 public function clearProcessCache() {
1678 $this->processCaches = [];
1679 }
1680
1701 final public function useInterimHoldOffCaching( $enabled ) {
1702 $this->useInterimHoldOffCaching = $enabled;
1703 }
1704
1710 public function getQoS( $flag ) {
1711 return $this->cache->getQoS( $flag );
1712 }
1713
1777 public function adaptiveTTL( $mtime, $maxTTL, $minTTL = 30, $factor = 0.2 ) {
1778 if ( is_float( $mtime ) || ctype_digit( $mtime ) ) {
1779 $mtime = (int)$mtime; // handle fractional seconds and string integers
1780 }
1781
1782 if ( !is_int( $mtime ) || $mtime <= 0 ) {
1783 return $minTTL; // no last-modified time provided
1784 }
1785
1786 $age = $this->getCurrentTime() - $mtime;
1787
1788 return (int)min( $maxTTL, max( $minTTL, $factor * $age ) );
1789 }
1790
1795 final public function getWarmupKeyMisses() {
1796 return $this->warmupKeyMisses;
1797 }
1798
1809 protected function relayPurge( $key, $ttl, $holdoff ) {
1810 if ( $this->mcrouterAware ) {
1811 // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
1812 // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
1813 $ok = $this->cache->set(
1814 "/*/{$this->cluster}/{$key}",
1815 $this->makePurgeValue( $this->getCurrentTime(), self::HOLDOFF_NONE ),
1816 $ttl
1817 );
1818 } elseif ( $this->purgeRelayer instanceof EventRelayerNull ) {
1819 // This handles the mcrouter and the single-DC case
1820 $ok = $this->cache->set(
1821 $key,
1822 $this->makePurgeValue( $this->getCurrentTime(), self::HOLDOFF_NONE ),
1823 $ttl
1824 );
1825 } else {
1826 $event = $this->cache->modifySimpleRelayEvent( [
1827 'cmd' => 'set',
1828 'key' => $key,
1829 'val' => 'PURGED:$UNIXTIME$:' . (int)$holdoff,
1830 'ttl' => max( $ttl, self::TTL_SECOND ),
1831 'sbt' => true, // substitute $UNIXTIME$ with actual microtime
1832 ] );
1833
1834 $ok = $this->purgeRelayer->notify( $this->purgeChannel, $event );
1835 if ( !$ok ) {
1836 $this->lastRelayError = self::ERR_RELAY;
1837 }
1838 }
1839
1840 return $ok;
1841 }
1842
1849 protected function relayDelete( $key ) {
1850 if ( $this->mcrouterAware ) {
1851 // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
1852 // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
1853 $ok = $this->cache->delete( "/*/{$this->cluster}/{$key}" );
1854 } elseif ( $this->purgeRelayer instanceof EventRelayerNull ) {
1855 // Some other proxy handles broadcasting or there is only one datacenter
1856 $ok = $this->cache->delete( $key );
1857 } else {
1858 $event = $this->cache->modifySimpleRelayEvent( [
1859 'cmd' => 'delete',
1860 'key' => $key,
1861 ] );
1862
1863 $ok = $this->purgeRelayer->notify( $this->purgeChannel, $event );
1864 if ( !$ok ) {
1865 $this->lastRelayError = self::ERR_RELAY;
1866 }
1867 }
1868
1869 return $ok;
1870 }
1871
1885 protected function isAliveOrInGracePeriod( $curTTL, $graceTTL ) {
1886 if ( $curTTL > 0 ) {
1887 return true;
1888 } elseif ( $graceTTL <= 0 ) {
1889 return false;
1890 }
1891
1892 $ageStale = abs( $curTTL ); // seconds of staleness
1893 $curGTTL = ( $graceTTL - $ageStale ); // current grace-time-to-live
1894 if ( $curGTTL <= 0 ) {
1895 return false; // already out of grace period
1896 }
1897
1898 // Chance of using a stale value is the complement of the chance of refreshing it
1899 return !$this->worthRefreshExpiring( $curGTTL, $graceTTL );
1900 }
1901
1915 protected function worthRefreshExpiring( $curTTL, $lowTTL ) {
1916 if ( $lowTTL <= 0 ) {
1917 return false;
1918 } elseif ( $curTTL >= $lowTTL ) {
1919 return false;
1920 } elseif ( $curTTL <= 0 ) {
1921 return false;
1922 }
1923
1924 $chance = ( 1 - $curTTL / $lowTTL );
1925
1926 return mt_rand( 1, 1e9 ) <= 1e9 * $chance;
1927 }
1928
1944 protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) {
1945 if ( $ageNew < 0 || $timeTillRefresh <= 0 ) {
1946 return false;
1947 }
1948
1949 $age = $now - $asOf;
1950 $timeOld = $age - $ageNew;
1951 if ( $timeOld <= 0 ) {
1952 return false;
1953 }
1954
1955 // Lifecycle is: new, ramp-up refresh chance, full refresh chance.
1956 // Note that the "expected # of refreshes" for the ramp-up time range is half of what it
1957 // would be if P(refresh) was at its full value during that time range.
1958 $refreshWindowSec = max( $timeTillRefresh - $ageNew - self::RAMPUP_TTL / 2, 1 );
1959 // P(refresh) * (# hits in $refreshWindowSec) = (expected # of refreshes)
1960 // P(refresh) * ($refreshWindowSec * $popularHitsPerSec) = 1
1961 // P(refresh) = 1/($refreshWindowSec * $popularHitsPerSec)
1962 $chance = 1 / ( self::HIT_RATE_HIGH * $refreshWindowSec );
1963
1964 // Ramp up $chance from 0 to its nominal value over RAMPUP_TTL seconds to avoid stampedes
1965 $chance *= ( $timeOld <= self::RAMPUP_TTL ) ? $timeOld / self::RAMPUP_TTL : 1;
1966
1967 return mt_rand( 1, 1e9 ) <= 1e9 * $chance;
1968 }
1969
1979 protected function isValid( $value, $versioned, $asOf, $minTime ) {
1980 if ( $versioned && !isset( $value[self::VFLD_VERSION] ) ) {
1981 return false;
1982 } elseif ( $minTime > 0 && $asOf < $minTime ) {
1983 return false;
1984 }
1985
1986 return true;
1987 }
1988
1997 protected function wrap( $value, $ttl, $now ) {
1998 return [
1999 self::FLD_VERSION => self::VERSION,
2000 self::FLD_VALUE => $value,
2001 self::FLD_TTL => $ttl,
2002 self::FLD_TIME => $now
2003 ];
2004 }
2005
2013 protected function unwrap( $wrapped, $now ) {
2014 // Check if the value is a tombstone
2015 $purge = $this->parsePurgeValue( $wrapped );
2016 if ( $purge !== false ) {
2017 // Purged values should always have a negative current $ttl
2018 $curTTL = min( $purge[self::FLD_TIME] - $now, self::TINY_NEGATIVE );
2019 return [ false, $curTTL ];
2020 }
2021
2022 if ( !is_array( $wrapped ) // not found
2023 || !isset( $wrapped[self::FLD_VERSION] ) // wrong format
2024 || $wrapped[self::FLD_VERSION] !== self::VERSION // wrong version
2025 ) {
2026 return [ false, null ];
2027 }
2028
2029 $flags = isset( $wrapped[self::FLD_FLAGS] ) ? $wrapped[self::FLD_FLAGS] : 0;
2030 if ( ( $flags & self::FLG_STALE ) == self::FLG_STALE ) {
2031 // Treat as expired, with the cache time as the expiration
2032 $age = $now - $wrapped[self::FLD_TIME];
2033 $curTTL = min( -$age, self::TINY_NEGATIVE );
2034 } elseif ( $wrapped[self::FLD_TTL] > 0 ) {
2035 // Get the approximate time left on the key
2036 $age = $now - $wrapped[self::FLD_TIME];
2037 $curTTL = max( $wrapped[self::FLD_TTL] - $age, 0.0 );
2038 } else {
2039 // Key had no TTL, so the time left is unbounded
2040 $curTTL = INF;
2041 }
2042
2043 return [ $wrapped[self::FLD_VALUE], $curTTL ];
2044 }
2045
2051 protected static function prefixCacheKeys( array $keys, $prefix ) {
2052 $res = [];
2053 foreach ( $keys as $key ) {
2054 $res[] = $prefix . $key;
2055 }
2056
2057 return $res;
2058 }
2059
2064 protected function determineKeyClass( $key ) {
2065 $parts = explode( ':', $key );
2066
2067 return isset( $parts[1] ) ? $parts[1] : $parts[0]; // sanity
2068 }
2069
2075 protected function parsePurgeValue( $value ) {
2076 if ( !is_string( $value ) ) {
2077 return false;
2078 }
2079 $segments = explode( ':', $value, 3 );
2080 if ( !isset( $segments[0] ) || !isset( $segments[1] )
2081 || "{$segments[0]}:" !== self::PURGE_VAL_PREFIX
2082 ) {
2083 return false;
2084 }
2085 if ( !isset( $segments[2] ) ) {
2086 // Back-compat with old purge values without holdoff
2087 $segments[2] = self::HOLDOFF_TTL;
2088 }
2089 return [
2090 self::FLD_TIME => (float)$segments[1],
2091 self::FLD_HOLDOFF => (int)$segments[2],
2092 ];
2093 }
2094
2100 protected function makePurgeValue( $timestamp, $holdoff ) {
2101 return self::PURGE_VAL_PREFIX . (float)$timestamp . ':' . (int)$holdoff;
2102 }
2103
2108 protected function getProcessCache( $group ) {
2109 if ( !isset( $this->processCaches[$group] ) ) {
2110 list( , $n ) = explode( ':', $group );
2111 $this->processCaches[$group] = new HashBagOStuff( [ 'maxKeys' => (int)$n ] );
2112 }
2113
2114 return $this->processCaches[$group];
2115 }
2116
2122 private function getNonProcessCachedKeys( array $keys, array $opts ) {
2123 $keysFound = [];
2124 if ( isset( $opts['pcTTL'] ) && $opts['pcTTL'] > 0 && $this->callbackDepth == 0 ) {
2125 $pcGroup = isset( $opts['pcGroup'] ) ? $opts['pcGroup'] : self::PC_PRIMARY;
2126 $procCache = $this->getProcessCache( $pcGroup );
2127 foreach ( $keys as $key ) {
2128 if ( $procCache->get( $key ) !== false ) {
2129 $keysFound[] = $key;
2130 }
2131 }
2132 }
2133
2134 return array_diff( $keys, $keysFound );
2135 }
2136
2142 private function getRawKeysForWarmup( array $keys, array $checkKeys ) {
2143 if ( !$keys ) {
2144 return [];
2145 }
2146
2147 $keysWarmUp = [];
2148 // Get all the value keys to fetch...
2149 foreach ( $keys as $key ) {
2150 $keysWarmUp[] = self::VALUE_KEY_PREFIX . $key;
2151 }
2152 // Get all the check keys to fetch...
2153 foreach ( $checkKeys as $i => $checkKeyOrKeys ) {
2154 if ( is_int( $i ) ) {
2155 // Single check key that applies to all value keys
2156 $keysWarmUp[] = self::TIME_KEY_PREFIX . $checkKeyOrKeys;
2157 } else {
2158 // List of check keys that apply to value key $i
2159 $keysWarmUp = array_merge(
2160 $keysWarmUp,
2161 self::prefixCacheKeys( $checkKeyOrKeys, self::TIME_KEY_PREFIX )
2162 );
2163 }
2164 }
2165
2166 $warmupCache = $this->cache->getMulti( $keysWarmUp );
2167 $warmupCache += array_fill_keys( $keysWarmUp, false );
2168
2169 return $warmupCache;
2170 }
2171
2176 protected function getCurrentTime() {
2177 return $this->wallClockOverride ?: microtime( true );
2178 }
2179
2184 public function setMockTime( &$time ) {
2185 $this->wallClockOverride =& $time;
2186 $this->cache->setMockTime( $time );
2187 }
2188}
interface is intended to be more or less compatible with the PHP memcached client.
Definition BagOStuff.php:47
A BagOStuff object with no objects in it.
No-op class for publishing messages into a PubSub system.
Base class for reliable event relays.
Simple store for keeping values in an associative array for the current process.
Multi-datacenter aware caching interface.
int $callbackDepth
Callback stack depth for getWithSetCallback()
const TINY_NEGATIVE
Tiny negative float to use when CTL comes up >= 0 due to clock skew.
const HOLDOFF_TTL
Seconds to tombstone keys on delete()
const HOT_TTR
The time length of the "popularity" refresh window for hot keys.
__construct(array $params)
unwrap( $wrapped, $now)
Do not use this method outside WANObjectCache.
worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now)
Check if a key is due for randomized regeneration due to its popularity.
touchCheckKey( $key, $holdoff=self::HOLDOFF_TTL)
Purge a "check" key from all datacenters, invalidating keys that use it.
adaptiveTTL( $mtime, $maxTTL, $minTTL=30, $factor=0.2)
Get a TTL that is higher for objects that have not changed recently.
string $cluster
Cache cluster name for mcrouter use.
const GRACE_TTL_NONE
Idiom for set()/getWithSetCallback() for "no post-expired grace period".
int $warmupKeyMisses
Key fetched.
float null $wallClockOverride
mixed[] $warmupCache
Temporary warm-up cache.
const VERSION
Cache format version number.
const TTL_UNCACHEABLE
Idiom for getWithSetCallback() callbacks to avoid calling set()
const LOW_TTL
Default remaining TTL at which to consider pre-emptive regeneration.
relayPurge( $key, $ttl, $holdoff)
Do the actual async bus purge of a key.
getLastError()
Get the "last error" registered; clearLastError() should be called manually.
BagOStuff $cache
The local datacenter cache.
isValid( $value, $versioned, $asOf, $minTime)
Check whether $value is appropriately versioned and not older than $minTime (if set)
processCheckKeys(array $timeKeys, array $wrappedValues, $now)
doGetWithSetCallback( $key, $ttl, $callback, array $opts, &$asOf=null)
Do the actual I/O for getWithSetCallback() when needed.
const HOLDOFF_NONE
Idiom for delete() for "no hold-off".
getMulti(array $keys, &$curTTLs=[], array $checkKeys=[], array &$asOfs=[])
Fetch the value of several keys from cache.
getCheckKeyTime( $key)
Fetch the value of a timestamp "check" key.
HashBagOStuff[] $processCaches
Map of group PHP instance caches.
relayDelete( $key)
Do the actual async bus delete of a key.
const LOCK_TTL
Seconds to keep lock keys around.
getMultiWithUnionSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch/regenerate multiple cache keys at once.
LoggerInterface $logger
static prefixCacheKeys(array $keys, $prefix)
getMultiWithSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch multiple cache keys at once with regeneration.
const HIT_RATE_HIGH
Hits/second for a refresh to be expected within the "popularity" window.
EventRelayer $purgeRelayer
Bus that handles purge broadcasts.
const INTERIM_KEY_TTL
Seconds to keep interim value keys for tombstoned keys around.
static newEmpty()
Get an instance that wraps EmptyBagOStuff.
worthRefreshExpiring( $curTTL, $lowTTL)
Check if a key is nearing expiration and thus due for randomized regeneration.
getWithSetCallback( $key, $ttl, $callback, array $opts=[])
Method to fetch/regenerate cache keys.
makeMultiKeys(array $entities, callable $keyFunc)
setInterimValue( $key, $wrapped, $tempTTL)
bool $useInterimHoldOffCaching
Whether to use "interim" caching while keys are tombstoned.
const MAX_READ_LAG
Max replication+snapshot lag before applying TTL_LAGGED or disallowing set()
const CHECK_KEY_TTL
Seconds to keep dependency purge keys around.
const MIN_TIMESTAMP_NONE
Idiom for getWithSetCallback() for "no minimum required as-of timestamp".
useInterimHoldOffCaching( $enabled)
Enable or disable the use of brief caching for tombstoned keys.
StatsdDataFactoryInterface $stats
clearProcessCache()
Clear the in-process caches; useful for testing.
getNonProcessCachedKeys(array $keys, array $opts)
string $region
Physical region for mcrouter use.
wrap( $value, $ttl, $now)
Do not use this method outside WANObjectCache.
int $lastRelayError
ERR_* constant for the "last error" registry.
getInterimValue( $key, $versioned, $minTime, &$asOf)
string $purgeChannel
Purge channel name.
callable null $asyncHandler
Function that takes a WAN cache callback and runs it later.
reap( $key, $purgeTimestamp, &$isStale=false)
Set a key to soon expire in the local cluster if it pre-dates $purgeTimestamp.
makePurgeValue( $timestamp, $holdoff)
getRawKeysForWarmup(array $keys, array $checkKeys)
setLogger(LoggerInterface $logger)
reapCheckKey( $key, $purgeTimestamp, &$isStale=false)
Set a "check" key to soon expire in the local cluster if it pre-dates $purgeTimestamp.
makeKey( $class, $component=null)
clearLastError()
Clear the "last error" registry.
const STALE_TTL_NONE
Idiom for set()/getWithSetCallback() for "do not augment the storage medium TTL".
const TSE_NONE
Idiom for getWithSetCallback() callbacks to 'lockTSE' logic.
resetCheckKey( $key)
Delete a "check" key from all datacenters, invalidating keys that use it.
makeGlobalKey( $class, $component=null)
const MAX_COMMIT_DELAY
Max time expected to pass between delete() and DB commit finishing.
const AGE_NEW
Never consider performing "popularity" refreshes until a key reaches this age.
const RAMPUP_TTL
Seconds to ramp up to the "popularity" refresh chance after a key is no longer new.
const TTL_LAGGED
Max TTL to store keys when a data sourced is lagged.
isAliveOrInGracePeriod( $curTTL, $graceTTL)
Check if a key is fresh or in the grace window and thus due for randomized reuse.
getMultiCheckKeyTime(array $keys)
Fetch the values of each timestamp "check" key.
$mcrouterAware
@bar bool Whether to use mcrouter key prefixing for routing
$res
Definition database.txt:21
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
namespace being checked & $result
Definition hooks.txt:2323
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1795
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition hooks.txt:181
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition hooks.txt:865
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
Generic base class for storage interfaces.
you have access to all of the normal MediaWiki so you can get a DB use the cache
$cache
Definition mcc.php:33
$params