MediaWiki  master
WANObjectCache.php
Go to the documentation of this file.
1 <?php
22 use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
23 use Psr\Log\LoggerAwareInterface;
24 use Psr\Log\LoggerInterface;
25 use Psr\Log\NullLogger;
28 
120 class WANObjectCache implements
124  LoggerAwareInterface
125 {
127  protected $cache;
129  protected $processCaches = [];
131  protected $logger;
133  protected $stats;
135  protected $asyncHandler;
136 
138  protected $mcrouterAware;
140  protected $region;
142  protected $cluster;
144  protected $useInterimHoldOffCaching = true;
146  protected $epoch;
148  protected $secret;
150  protected $coalesceKeys;
152  protected $coalesceScheme;
153 
155  private $keyHighQps;
158 
160  private $callbackDepth = 0;
162  private $warmupCache = [];
164  private $warmupKeyMisses = 0;
165 
168 
170  private const MAX_COMMIT_DELAY = 3;
172  private const MAX_READ_LAG = 7;
174  public const HOLDOFF_TTL = self::MAX_COMMIT_DELAY + self::MAX_READ_LAG + 1;
175 
177  private const LOW_TTL = 30;
179  public const TTL_LAGGED = 30;
180 
182  private const HOT_TTR = 900;
184  private const AGE_NEW = 60;
185 
187  private const TSE_NONE = -1;
188 
190  public const STALE_TTL_NONE = 0;
192  public const GRACE_TTL_NONE = 0;
194  public const HOLDOFF_TTL_NONE = 0;
196  public const HOLDOFF_NONE = self::HOLDOFF_TTL_NONE;
197 
199  public const MIN_TIMESTAMP_NONE = 0.0;
200 
202  private const PC_PRIMARY = 'primary:1000';
203 
205  public const PASS_BY_REF = -1;
206 
208  private const SCHEME_HASH_TAG = 1;
210  private const SCHEME_HASH_STOP = 2;
211 
213  private static $CHECK_KEY_TTL = self::TTL_YEAR;
215  private static $INTERIM_KEY_TTL = 1;
216 
218  private static $LOCK_TTL = 10;
220  private static $COOLOFF_TTL = 1;
222  private static $RAMPUP_TTL = 30;
223 
225  private static $TINY_NEGATIVE = -0.000001;
227  private static $TINY_POSTIVE = 0.000001;
228 
230  private static $RECENT_SET_LOW_MS = 50;
232  private static $RECENT_SET_HIGH_MS = 100;
233 
235  private static $GENERATION_SLOW_SEC = 3;
236 
238  private static $PURGE_TIME = 0;
240  private static $PURGE_HOLDOFF = 1;
241 
243  private static $VERSION = 1;
244 
246  public const KEY_VERSION = 'version';
248  public const KEY_AS_OF = 'asOf';
250  public const KEY_TTL = 'ttl';
252  public const KEY_CUR_TTL = 'curTTL';
254  public const KEY_TOMB_AS_OF = 'tombAsOf';
256  public const KEY_CHECK_AS_OF = 'lastCKPurge';
257 
259  private static $FLD_FORMAT_VERSION = 0;
261  private static $FLD_VALUE = 1;
263  private static $FLD_TTL = 2;
265  private static $FLD_TIME = 3;
267  private static $FLD_FLAGS = 4;
269  private static $FLD_VALUE_VERSION = 5;
271  private static $FLD_GENERATION_TIME = 6;
272 
274  private const TYPE_VALUE = 'v';
276  private const TYPE_TIMESTAMP = 't';
278  private const TYPE_MUTEX = 'm';
280  private const TYPE_INTERIM = 'i';
282  private const TYPE_COOLOFF = 'c';
283 
285  private const PURGE_VAL_PREFIX = 'PURGED:';
286 
323  public function __construct( array $params ) {
324  $this->cache = $params['cache'];
325  $this->region = $params['region'] ?? 'main';
326  $this->cluster = $params['cluster'] ?? 'wan-main';
327  $this->mcrouterAware = !empty( $params['mcrouterAware'] );
328  $this->epoch = $params['epoch'] ?? 0;
329  $this->secret = $params['secret'] ?? (string)$this->epoch;
330  $this->coalesceKeys = $params['coalesceKeys'] ?? false;
331  if ( !empty( $params['mcrouterAware'] ) ) {
332  // https://github.com/facebook/mcrouter/wiki/Key-syntax
333  $this->coalesceScheme = self::SCHEME_HASH_STOP;
334  } else {
335  // https://redis.io/topics/cluster-spec
336  // https://github.com/twitter/twemproxy/blob/v0.4.1/notes/recommendation.md#hash-tags
337  // https://github.com/Netflix/dynomite/blob/v0.7.0/notes/recommendation.md#hash-tags
338  $this->coalesceScheme = self::SCHEME_HASH_TAG;
339  }
340 
341  $this->keyHighQps = $params['keyHighQps'] ?? 100;
342  $this->keyHighUplinkBps = $params['keyHighUplinkBps'] ?? ( 1e9 / 8 / 100 );
343 
344  $this->setLogger( $params['logger'] ?? new NullLogger() );
345  $this->stats = $params['stats'] ?? new NullStatsdDataFactory();
346  $this->asyncHandler = $params['asyncHandler'] ?? null;
347 
348  $this->cache->registerWrapperInfoForStats(
349  'WANCache',
350  'wanobjectcache',
351  [ __CLASS__, 'getCollectionFromKey' ]
352  );
353  }
354 
358  public function setLogger( LoggerInterface $logger ) {
359  $this->logger = $logger;
360  }
361 
367  public static function newEmpty() {
368  return new static( [ 'cache' => new EmptyBagOStuff() ] );
369  }
370 
423  final public function get(
424  $key, &$curTTL = null, array $checkKeys = [], &$info = null
425  ) {
426  $curTTLs = self::PASS_BY_REF;
427  $infoByKey = self::PASS_BY_REF;
428  $values = $this->getMulti( [ $key ], $curTTLs, $checkKeys, $infoByKey );
430  '@phan-var array[] $infoByKey';
431 
432  $curTTL = $curTTLs[$key] ?? null;
433 
434  $info = ( $info === self::PASS_BY_REF )
435  // Key metadata array
436  ? $infoByKey[$key]
437  // Only the "as of" timestamp metadata (b/c)
438  : $infoByKey[$key][self::KEY_AS_OF];
439 
440  return array_key_exists( $key, $values ) ? $values[$key] : false;
441  }
442 
465  final public function getMulti(
466  array $keys,
467  &$curTTLs = [],
468  array $checkKeys = [],
469  &$info = null
470  ) {
471  $result = [];
472  $curTTLs = [];
473  $infoByKey = [];
474 
475  // Order-corresponding list of value keys for the provided base keys
476  $valueKeys = $this->makeSisterKeys( $keys, self::TYPE_VALUE );
477 
478  $fullKeysNeeded = $valueKeys;
479  $checkKeysForAll = [];
480  $checkKeysByKey = [];
481  foreach ( $checkKeys as $i => $checkKeyOrKeyGroup ) {
482  // Note: avoid array_merge() inside loop in case there are many keys
483  if ( is_int( $i ) ) {
484  // Single check key that applies to all value keys
485  $fullKey = $this->makeSisterKey( $checkKeyOrKeyGroup, self::TYPE_TIMESTAMP );
486  $fullKeysNeeded[] = $fullKey;
487  $checkKeysForAll[] = $fullKey;
488  } else {
489  // List of check keys that apply to a specific value key
490  foreach ( (array)$checkKeyOrKeyGroup as $checkKey ) {
491  $fullKey = $this->makeSisterKey( $checkKey, self::TYPE_TIMESTAMP );
492  $fullKeysNeeded[] = $fullKey;
493  $checkKeysByKey[$i][] = $fullKey;
494  }
495  }
496  }
497 
498  if ( $this->warmupCache ) {
499  // Get the raw values of the keys from the warmup cache
500  $wrappedValues = $this->warmupCache;
501  $fullKeysMissing = array_diff( $fullKeysNeeded, array_keys( $wrappedValues ) );
502  if ( $fullKeysMissing ) { // sanity
503  $this->warmupKeyMisses += count( $fullKeysMissing );
504  $wrappedValues += $this->cache->getMulti( $fullKeysMissing );
505  }
506  } else {
507  // Fetch the raw values of the keys from the backend
508  $wrappedValues = $this->cache->getMulti( $fullKeysNeeded );
509  }
510 
511  // Time used to compare/init "check" keys (derived after getMulti() to be pessimistic)
512  $now = $this->getCurrentTime();
513 
514  // Collect timestamps from all "check" keys
515  $purgeValuesForAll = $this->processCheckKeys( $checkKeysForAll, $wrappedValues, $now );
516  $purgeValuesByKey = [];
517  foreach ( $checkKeysByKey as $cacheKey => $checks ) {
518  $purgeValuesByKey[$cacheKey] = $this->processCheckKeys( $checks, $wrappedValues, $now );
519  }
520 
521  // Get the main cache value for each key and validate them
522  reset( $keys );
523  foreach ( $valueKeys as $i => $vKey ) {
524  // Get the corresponding base key for this value key
525  $key = current( $keys );
526  next( $keys );
527 
528  list( $value, $keyInfo ) = $this->unwrap(
529  array_key_exists( $vKey, $wrappedValues ) ? $wrappedValues[$vKey] : false,
530  $now
531  );
532  // Force dependent keys to be seen as stale for a while after purging
533  // to reduce race conditions involving stale data getting cached
534  $purgeValues = $purgeValuesForAll;
535  if ( isset( $purgeValuesByKey[$key] ) ) {
536  $purgeValues = array_merge( $purgeValues, $purgeValuesByKey[$key] );
537  }
538 
539  $lastCKPurge = null; // timestamp of the highest check key
540  foreach ( $purgeValues as $purge ) {
541  $lastCKPurge = max( $purge[self::$PURGE_TIME], $lastCKPurge );
542  $safeTimestamp = $purge[self::$PURGE_TIME] + $purge[self::$PURGE_HOLDOFF];
543  if ( $value !== false && $safeTimestamp >= $keyInfo[self::KEY_AS_OF] ) {
544  // How long ago this value was invalidated by *this* check key
545  $ago = min( $purge[self::$PURGE_TIME] - $now, self::$TINY_NEGATIVE );
546  // How long ago this value was invalidated by *any* known check key
547  $keyInfo[self::KEY_CUR_TTL] = min( $keyInfo[self::KEY_CUR_TTL], $ago );
548  }
549  }
550  $keyInfo[self::KEY_CHECK_AS_OF] = $lastCKPurge;
551 
552  if ( $value !== false ) {
553  $result[$key] = $value;
554  }
555  if ( $keyInfo[self::KEY_CUR_TTL] !== null ) {
556  $curTTLs[$key] = $keyInfo[self::KEY_CUR_TTL];
557  }
558 
559  $infoByKey[$key] = ( $info === self::PASS_BY_REF )
560  ? $keyInfo
561  : $keyInfo[self::KEY_AS_OF]; // b/c
562  }
563 
564  $info = $infoByKey;
565 
566  return $result;
567  }
568 
576  private function processCheckKeys( array $timeKeys, array $wrappedValues, $now ) {
577  $purgeValues = [];
578  foreach ( $timeKeys as $timeKey ) {
579  $purge = isset( $wrappedValues[$timeKey] )
580  ? $this->parsePurgeValue( $wrappedValues[$timeKey] )
581  : false;
582  if ( $purge === false ) {
583  // Key is not set or malformed; regenerate
584  $newVal = $this->makePurgeValue( $now, self::HOLDOFF_TTL );
585  $this->cache->add( $timeKey, $newVal, self::$CHECK_KEY_TTL );
586  $purge = $this->parsePurgeValue( $newVal );
587  }
588  $purgeValues[] = $purge;
589  }
590 
591  return $purgeValues;
592  }
593 
670  final public function set( $key, $value, $ttl = self::TTL_INDEFINITE, array $opts = [] ) {
671  $now = $this->getCurrentTime();
672  $lag = $opts['lag'] ?? 0;
673  $age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
674  $pending = $opts['pending'] ?? false;
675  $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
676  $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
677  $creating = $opts['creating'] ?? false;
678  $version = $opts['version'] ?? null;
679  $walltime = $opts['walltime'] ?? null;
680 
681  if ( $ttl < 0 ) {
682  return true; // not cacheable
683  }
684 
685  // Do not cache potentially uncommitted data as it might get rolled back
686  if ( $pending ) {
687  $this->logger->info(
688  'Rejected set() for {cachekey} due to pending writes.',
689  [ 'cachekey' => $key ]
690  );
691 
692  return true; // no-op the write for being unsafe
693  }
694 
695  // Check if there is a risk of caching (stale) data that predates the last delete()
696  // tombstone due to the tombstone having expired. If so, then the behavior should depend
697  // on whether the problem is specific to this regeneration attempt or systemically affects
698  // attempts to regenerate this key. For systemic cases, the cache writes should set a low
699  // TTL so that the value at least remains cacheable. For non-systemic cases, the cache
700  // write can simply be rejected.
701  if ( $age > self::MAX_READ_LAG ) {
702  // Case A: high snapshot lag
703  if ( $walltime === null ) {
704  // Case A0: high snapshot lag without regeneration wall time info.
705  // Probably systemic; use a low TTL to avoid stampedes/uncacheability.
706  $mitigated = 'snapshot lag';
707  $mitigationTTL = self::TTL_SECOND;
708  } elseif ( ( $age - $walltime ) > self::MAX_READ_LAG ) {
709  // Case A1: value regeneration during an already long-running transaction.
710  // Probably non-systemic; rely on a less problematic regeneration attempt.
711  $mitigated = 'snapshot lag (late regeneration)';
712  $mitigationTTL = self::TTL_UNCACHEABLE;
713  } else {
714  // Case A2: value regeneration takes a long time.
715  // Probably systemic; use a low TTL to avoid stampedes/uncacheability.
716  $mitigated = 'snapshot lag (high regeneration time)';
717  $mitigationTTL = self::TTL_SECOND;
718  }
719  } elseif ( $lag === false || $lag > self::MAX_READ_LAG ) {
720  // Case B: high replication lag without high snapshot lag
721  // Probably systemic; use a low TTL to avoid stampedes/uncacheability
722  $mitigated = 'replication lag';
723  $mitigationTTL = self::TTL_LAGGED;
724  } elseif ( ( $lag + $age ) > self::MAX_READ_LAG ) {
725  // Case C: medium length request with medium replication lag
726  // Probably non-systemic; rely on a less problematic regeneration attempt
727  $mitigated = 'read lag';
728  $mitigationTTL = self::TTL_UNCACHEABLE;
729  } else {
730  // New value generated with recent enough data
731  $mitigated = null;
732  $mitigationTTL = null;
733  }
734 
735  if ( $mitigationTTL === self::TTL_UNCACHEABLE ) {
736  $this->logger->warning(
737  "Rejected set() for {cachekey} due to $mitigated.",
738  [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age, 'walltime' => $walltime ]
739  );
740 
741  return true; // no-op the write for being unsafe
742  }
743 
744  // TTL to use in staleness checks (does not effect persistence layer TTL)
745  $logicalTTL = null;
746 
747  if ( $mitigationTTL !== null ) {
748  // New value generated from data that is old enough to be risky
749  if ( $lockTSE >= 0 ) {
750  // Value will have the normal expiry but will be seen as stale sooner
751  $logicalTTL = min( $ttl ?: INF, $mitigationTTL );
752  } else {
753  // Value expires sooner (leaving enough TTL for preemptive refresh)
754  $ttl = min( $ttl ?: INF, max( $mitigationTTL, self::LOW_TTL ) );
755  }
756 
757  $this->logger->warning(
758  "Lowered set() TTL for {cachekey} due to $mitigated.",
759  [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age, 'walltime' => $walltime ]
760  );
761  }
762 
763  // Wrap that value with time/TTL/version metadata
764  $wrapped = $this->wrap( $value, $logicalTTL ?: $ttl, $version, $now, $walltime );
765  $storeTTL = $ttl + $staleTTL;
766 
767  if ( $creating ) {
768  $ok = $this->cache->add(
769  $this->makeSisterKey( $key, self::TYPE_VALUE ),
770  $wrapped,
771  $storeTTL
772  );
773  } else {
774  $ok = $this->cache->merge(
775  $this->makeSisterKey( $key, self::TYPE_VALUE ),
776  function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
777  // A string value means that it is a tombstone; do nothing in that case
778  return ( is_string( $cWrapped ) ) ? false : $wrapped;
779  },
780  $storeTTL,
781  1 // 1 attempt
782  );
783  }
784 
785  return $ok;
786  }
787 
849  final public function delete( $key, $ttl = self::HOLDOFF_TTL ) {
850  if ( $ttl <= 0 ) {
851  // Publish the purge to all datacenters
852  $ok = $this->relayDelete( $this->makeSisterKey( $key, self::TYPE_VALUE ) );
853  } else {
854  // Publish the purge to all datacenters
855  $ok = $this->relayPurge(
856  $this->makeSisterKey( $key, self::TYPE_VALUE ),
857  $ttl,
858  self::HOLDOFF_TTL_NONE
859  );
860  }
861 
862  $kClass = $this->determineKeyClassForStats( $key );
863  $this->stats->increment( "wanobjectcache.$kClass.delete." . ( $ok ? 'ok' : 'error' ) );
864 
865  return $ok;
866  }
867 
887  final public function getCheckKeyTime( $key ) {
888  return $this->getMultiCheckKeyTime( [ $key ] )[$key];
889  }
890 
952  final public function getMultiCheckKeyTime( array $keys ) {
953  $rawKeys = [];
954  foreach ( $keys as $key ) {
955  $rawKeys[$key] = $this->makeSisterKey( $key, self::TYPE_TIMESTAMP );
956  }
957 
958  $rawValues = $this->cache->getMulti( $rawKeys );
959  $rawValues += array_fill_keys( $rawKeys, false );
960 
961  $times = [];
962  foreach ( $rawKeys as $key => $rawKey ) {
963  $purge = $this->parsePurgeValue( $rawValues[$rawKey] );
964  if ( $purge !== false ) {
965  $time = $purge[self::$PURGE_TIME];
966  } else {
967  // Casting assures identical floats for the next getCheckKeyTime() calls
968  $now = (string)$this->getCurrentTime();
969  $this->cache->add(
970  $rawKey,
971  $this->makePurgeValue( $now, self::HOLDOFF_TTL ),
973  );
974  $time = (float)$now;
975  }
976 
977  $times[$key] = $time;
978  }
979 
980  return $times;
981  }
982 
1017  final public function touchCheckKey( $key, $holdoff = self::HOLDOFF_TTL ) {
1018  // Publish the purge to all datacenters
1019  $ok = $this->relayPurge(
1020  $this->makeSisterKey( $key, self::TYPE_TIMESTAMP ),
1021  self::$CHECK_KEY_TTL,
1022  $holdoff
1023  );
1024 
1025  $kClass = $this->determineKeyClassForStats( $key );
1026  $this->stats->increment( "wanobjectcache.$kClass.ck_touch." . ( $ok ? 'ok' : 'error' ) );
1027 
1028  return $ok;
1029  }
1030 
1058  final public function resetCheckKey( $key ) {
1059  // Publish the purge to all datacenters
1060  $ok = $this->relayDelete( $this->makeSisterKey( $key, self::TYPE_TIMESTAMP ) );
1061 
1062  $kClass = $this->determineKeyClassForStats( $key );
1063  $this->stats->increment( "wanobjectcache.$kClass.ck_reset." . ( $ok ? 'ok' : 'error' ) );
1064 
1065  return $ok;
1066  }
1067 
1372  final public function getWithSetCallback(
1373  $key, $ttl, $callback, array $opts = [], array $cbParams = []
1374  ) {
1375  $version = $opts['version'] ?? null;
1376  $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
1377  $pCache = ( $pcTTL >= 0 )
1378  ? $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY )
1379  : null;
1380 
1381  // Use the process cache if requested as long as no outer cache callback is running.
1382  // Nested callback process cache use is not lag-safe with regard to HOLDOFF_TTL since
1383  // process cached values are more lagged than persistent ones as they are not purged.
1384  if ( $pCache && $this->callbackDepth == 0 ) {
1385  $cached = $pCache->get( $this->getProcessCacheKey( $key, $version ), $pcTTL, false );
1386  if ( $cached !== false ) {
1387  $this->logger->debug( "getWithSetCallback($key): process cache hit" );
1388  return $cached;
1389  }
1390  }
1391 
1392  $res = $this->fetchOrRegenerate( $key, $ttl, $callback, $opts, $cbParams );
1393  list( $value, $valueVersion, $curAsOf ) = $res;
1394  if ( $valueVersion !== $version ) {
1395  // Current value has a different version; use the variant key for this version.
1396  // Regenerate the variant value if it is not newer than the main value at $key
1397  // so that purges to the main key propagate to the variant value.
1398  $this->logger->debug( "getWithSetCallback($key): using variant key" );
1399  list( $value ) = $this->fetchOrRegenerate(
1400  $this->makeGlobalKey( 'WANCache-key-variant', md5( $key ), $version ),
1401  $ttl,
1402  $callback,
1403  [ 'version' => null, 'minAsOf' => $curAsOf ] + $opts,
1404  $cbParams
1405  );
1406  }
1407 
1408  // Update the process cache if enabled
1409  if ( $pCache && $value !== false ) {
1410  $pCache->set( $this->getProcessCacheKey( $key, $version ), $value );
1411  }
1412 
1413  return $value;
1414  }
1415 
1432  private function fetchOrRegenerate( $key, $ttl, $callback, array $opts, array $cbParams ) {
1433  $checkKeys = $opts['checkKeys'] ?? [];
1434  $graceTTL = $opts['graceTTL'] ?? self::GRACE_TTL_NONE;
1435  $minAsOf = $opts['minAsOf'] ?? self::MIN_TIMESTAMP_NONE;
1436  $hotTTR = $opts['hotTTR'] ?? self::HOT_TTR;
1437  $lowTTL = $opts['lowTTL'] ?? min( self::LOW_TTL, $ttl );
1438  $ageNew = $opts['ageNew'] ?? self::AGE_NEW;
1439  $touchedCb = $opts['touchedCallback'] ?? null;
1440  $initialTime = $this->getCurrentTime();
1441 
1442  $kClass = $this->determineKeyClassForStats( $key );
1443 
1444  // Get the current key value and its metadata
1445  $curTTL = self::PASS_BY_REF;
1446  $curInfo = self::PASS_BY_REF;
1447  $curValue = $this->get( $key, $curTTL, $checkKeys, $curInfo );
1449  '@phan-var array $curInfo';
1450  // Apply any $touchedCb invalidation timestamp to get the "last purge timestamp"
1451  list( $curTTL, $LPT ) = $this->resolveCTL( $curValue, $curTTL, $curInfo, $touchedCb );
1452  // Use the cached value if it exists and is not due for synchronous regeneration
1453  if (
1454  $this->isValid( $curValue, $curInfo[self::KEY_AS_OF], $minAsOf ) &&
1455  $this->isAliveOrInGracePeriod( $curTTL, $graceTTL )
1456  ) {
1457  $preemptiveRefresh = (
1458  $this->worthRefreshExpiring( $curTTL, $curInfo[self::KEY_TTL], $lowTTL ) ||
1459  $this->worthRefreshPopular( $curInfo['asOf'], $ageNew, $hotTTR, $initialTime )
1460  );
1461  if ( !$preemptiveRefresh ) {
1462  $this->stats->timing(
1463  "wanobjectcache.$kClass.hit.good",
1464  1e3 * ( $this->getCurrentTime() - $initialTime )
1465  );
1466 
1467  return [ $curValue, $curInfo[self::KEY_VERSION], $curInfo[self::KEY_AS_OF] ];
1468  } elseif ( $this->scheduleAsyncRefresh( $key, $ttl, $callback, $opts, $cbParams ) ) {
1469  $this->logger->debug( "fetchOrRegenerate($key): hit with async refresh" );
1470  $this->stats->timing(
1471  "wanobjectcache.$kClass.hit.refresh",
1472  1e3 * ( $this->getCurrentTime() - $initialTime )
1473  );
1474 
1475  return [ $curValue, $curInfo[self::KEY_VERSION], $curInfo[self::KEY_AS_OF] ];
1476  } else {
1477  $this->logger->debug( "fetchOrRegenerate($key): hit with sync refresh" );
1478  }
1479  }
1480 
1481  // Determine if there is stale or volatile cached value that is still usable
1482  $isKeyTombstoned = ( $curInfo[self::KEY_TOMB_AS_OF] !== null );
1483  if ( $isKeyTombstoned ) {
1484  // Key is write-holed; use the (volatile) interim key as an alternative
1485  list( $possValue, $possInfo ) = $this->getInterimValue( $key, $minAsOf );
1486  // Update the "last purge time" since the $touchedCb timestamp depends on $value
1487  $LPT = $this->resolveTouched( $possValue, $LPT, $touchedCb );
1488  } else {
1489  $possValue = $curValue;
1490  $possInfo = $curInfo;
1491  }
1492 
1493  // Avoid overhead from callback runs, regeneration locks, and cache sets during
1494  // hold-off periods for the key by reusing very recently generated cached values
1495  if (
1496  $this->isValid( $possValue, $possInfo[self::KEY_AS_OF], $minAsOf, $LPT ) &&
1497  $this->isVolatileValueAgeNegligible( $initialTime - $possInfo[self::KEY_AS_OF] )
1498  ) {
1499  $this->logger->debug( "fetchOrRegenerate($key): volatile hit" );
1500  $this->stats->timing(
1501  "wanobjectcache.$kClass.hit.volatile",
1502  1e3 * ( $this->getCurrentTime() - $initialTime )
1503  );
1504 
1505  return [ $possValue, $possInfo[self::KEY_VERSION], $curInfo[self::KEY_AS_OF] ];
1506  }
1507 
1508  $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
1509  $busyValue = $opts['busyValue'] ?? null;
1510  $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
1511  $version = $opts['version'] ?? null;
1512 
1513  // Determine whether one thread per datacenter should handle regeneration at a time
1514  $useRegenerationLock =
1515  // Note that since tombstones no-op set(), $lockTSE and $curTTL cannot be used to
1516  // deduce the key hotness because |$curTTL| will always keep increasing until the
1517  // tombstone expires or is overwritten by a new tombstone. Also, even if $lockTSE
1518  // is not set, constant regeneration of a key for the tombstone lifetime might be
1519  // very expensive. Assume tombstoned keys are possibly hot in order to reduce
1520  // the risk of high regeneration load after the delete() method is called.
1521  $isKeyTombstoned ||
1522  // Assume a key is hot if requested soon ($lockTSE seconds) after invalidation.
1523  // This avoids stampedes when timestamps from $checkKeys/$touchedCb bump.
1524  ( $curTTL !== null && $curTTL <= 0 && abs( $curTTL ) <= $lockTSE ) ||
1525  // Assume a key is hot if there is no value and a busy fallback is given.
1526  // This avoids stampedes on eviction or preemptive regeneration taking too long.
1527  ( $busyValue !== null && $possValue === false );
1528 
1529  // If a regeneration lock is required, threads that do not get the lock will try to use
1530  // the stale value, the interim value, or the $busyValue placeholder, in that order. If
1531  // none of those are set then all threads will bypass the lock and regenerate the value.
1532  $hasLock = $useRegenerationLock && $this->claimStampedeLock( $key );
1533  if ( $useRegenerationLock && !$hasLock ) {
1534  if ( $this->isValid( $possValue, $possInfo[self::KEY_AS_OF], $minAsOf ) ) {
1535  $this->logger->debug( "fetchOrRegenerate($key): returning stale value" );
1536  $this->stats->timing(
1537  "wanobjectcache.$kClass.hit.stale",
1538  1e3 * ( $this->getCurrentTime() - $initialTime )
1539  );
1540 
1541  return [ $possValue, $possInfo[self::KEY_VERSION], $curInfo[self::KEY_AS_OF] ];
1542  } elseif ( $busyValue !== null ) {
1543  $miss = is_infinite( $minAsOf ) ? 'renew' : 'miss';
1544  $this->logger->debug( "fetchOrRegenerate($key): busy $miss" );
1545  $this->stats->timing(
1546  "wanobjectcache.$kClass.$miss.busy",
1547  1e3 * ( $this->getCurrentTime() - $initialTime )
1548  );
1549  $placeholderValue = $this->resolveBusyValue( $busyValue );
1550 
1551  return [ $placeholderValue, $version, $curInfo[self::KEY_AS_OF] ];
1552  }
1553  }
1554 
1555  // Generate the new value given any prior value with a matching version
1556  $setOpts = [];
1557  $preCallbackTime = $this->getCurrentTime();
1559  try {
1560  $value = $callback(
1561  ( $curInfo[self::KEY_VERSION] === $version ) ? $curValue : false,
1562  $ttl,
1563  $setOpts,
1564  ( $curInfo[self::KEY_VERSION] === $version ) ? $curInfo[self::KEY_AS_OF] : null,
1565  $cbParams
1566  );
1567  } finally {
1569  }
1570  $postCallbackTime = $this->getCurrentTime();
1571 
1572  // How long it took to fetch, validate, and generate the value
1573  $elapsed = max( $postCallbackTime - $initialTime, 0.0 );
1574 
1575  // Attempt to save the newly generated value if applicable
1576  if (
1577  // Callback yielded a cacheable value
1578  ( $value !== false && $ttl >= 0 ) &&
1579  // Current thread was not raced out of a regeneration lock or key is tombstoned
1580  ( !$useRegenerationLock || $hasLock || $isKeyTombstoned ) &&
1581  // Key does not appear to be undergoing a set() stampede
1582  $this->checkAndSetCooloff( $key, $kClass, $value, $elapsed, $hasLock )
1583  ) {
1584  // How long it took to generate the value
1585  $walltime = max( $postCallbackTime - $preCallbackTime, 0.0 );
1586  $this->stats->timing( "wanobjectcache.$kClass.regen_walltime", 1e3 * $walltime );
1587  // If the key is write-holed then use the (volatile) interim key as an alternative
1588  if ( $isKeyTombstoned ) {
1589  $this->setInterimValue( $key, $value, $lockTSE, $version, $walltime );
1590  } else {
1591  $finalSetOpts = [
1592  // @phan-suppress-next-line PhanUselessBinaryAddRight
1593  'since' => $setOpts['since'] ?? $preCallbackTime,
1594  'version' => $version,
1595  'staleTTL' => $staleTTL,
1596  'lockTSE' => $lockTSE, // informs lag vs performance trade-offs
1597  'creating' => ( $curValue === false ), // optimization
1598  'walltime' => $walltime
1599  ] + $setOpts;
1600  $this->set( $key, $value, $ttl, $finalSetOpts );
1601  }
1602  }
1603 
1604  $this->yieldStampedeLock( $key, $hasLock );
1605 
1606  $miss = is_infinite( $minAsOf ) ? 'renew' : 'miss';
1607  $this->logger->debug( "fetchOrRegenerate($key): $miss, new value computed" );
1608  $this->stats->timing(
1609  "wanobjectcache.$kClass.$miss.compute",
1610  1e3 * ( $this->getCurrentTime() - $initialTime )
1611  );
1612 
1613  return [ $value, $version, $curInfo[self::KEY_AS_OF] ];
1614  }
1615 
1620  private function claimStampedeLock( $key ) {
1621  // Note that locking is not bypassed due to I/O errors; this avoids stampedes
1622  return $this->cache->add(
1623  $this->makeSisterKey( $key, self::TYPE_MUTEX ),
1624  1,
1625  self::$LOCK_TTL
1626  );
1627  }
1628 
1633  private function yieldStampedeLock( $key, $hasLock ) {
1634  if ( $hasLock ) {
1635  // The backend might be a mcrouter proxy set to broadcast DELETE to *all* the local
1636  // datacenter cache servers via OperationSelectorRoute (for increased consistency).
1637  // Since that would be excessive for these locks, use TOUCH to expire the key.
1638  $this->cache->changeTTL(
1639  $this->makeSisterKey( $key, self::TYPE_MUTEX ),
1640  $this->getCurrentTime() - 60
1641  );
1642  }
1643  }
1644 
1652  private function makeSisterKeys( array $baseKeys, $type ) {
1653  $keys = [];
1654  foreach ( $baseKeys as $baseKey ) {
1655  $keys[] = $this->makeSisterKey( $baseKey, $type );
1656  }
1657 
1658  return $keys;
1659  }
1660 
1668  private function makeSisterKey( $baseKey, $typeChar ) {
1669  if ( $this->coalesceKeys === 'non-global' ) {
1670  $useColocationScheme = ( strncmp( $baseKey, "global:", 7 ) !== 0 );
1671  } else {
1672  $useColocationScheme = ( $this->coalesceKeys === true );
1673  }
1674 
1675  if ( !$useColocationScheme ) {
1676  // Old key style: "WANCache:<character>:<base key>"
1677  $fullKey = 'WANCache:' . $typeChar . ':' . $baseKey;
1678  } elseif ( $this->coalesceScheme === self::SCHEME_HASH_STOP ) {
1679  // Key style: "WANCache:<base key>|#|<character>"
1680  $fullKey = 'WANCache:' . $baseKey . '|#|' . $typeChar;
1681  } else {
1682  // Key style: "WANCache:{<base key>}:<character>"
1683  $fullKey = 'WANCache:{' . $baseKey . '}:' . $typeChar;
1684  }
1685 
1686  return $fullKey;
1687  }
1688 
1695  public static function getCollectionFromKey( string $sisterKey ) {
1696  if ( substr( $sisterKey, -4 ) === '|#|v' ) {
1697  // Key style: "WANCache:<base key>|#|<character>"
1698  $collection = substr( $sisterKey, 9, strcspn( $sisterKey, ':|', 9 ) );
1699  } elseif ( substr( $sisterKey, -3 ) === '}:v' ) {
1700  // Key style: "WANCache:{<base key>}:<character>"
1701  $collection = substr( $sisterKey, 10, strcspn( $sisterKey, ':}', 10 ) );
1702  } elseif ( substr( $sisterKey, 9, 2 ) === 'v:' ) {
1703  // Old key style: "WANCache:<character>:<base key>"
1704  $collection = substr( $sisterKey, 11, strcspn( $sisterKey, ':', 11 ) );
1705  } else {
1706  $collection = 'internal';
1707  }
1708 
1709  return $collection;
1710  }
1711 
1716  private function isVolatileValueAgeNegligible( $age ) {
1717  return ( $age < mt_rand( self::$RECENT_SET_LOW_MS, self::$RECENT_SET_HIGH_MS ) / 1e3 );
1718  }
1719 
1741  private function checkAndSetCooloff( $key, $kClass, $value, $elapsed, $hasLock ) {
1742  $valueKey = $this->makeSisterKey( $key, self::TYPE_VALUE );
1743  list( $estimatedSize ) = $this->cache->setNewPreparedValues( [ $valueKey => $value ] );
1744 
1745  if ( !$hasLock ) {
1746  // Suppose that this cache key is very popular (KEY_HIGH_QPS reads/second).
1747  // After eviction, there will be cache misses until it gets regenerated and saved.
1748  // If the time window when the key is missing lasts less than one second, then the
1749  // number of misses will not reach KEY_HIGH_QPS. This window largely corresponds to
1750  // the key regeneration time. Estimate the count/rate of cache misses, e.g.:
1751  // - 100 QPS, 20ms regeneration => ~2 misses (< 1s)
1752  // - 100 QPS, 100ms regeneration => ~10 misses (< 1s)
1753  // - 100 QPS, 3000ms regeneration => ~300 misses (100/s for 3s)
1754  $missesPerSecForHighQPS = ( min( $elapsed, 1 ) * $this->keyHighQps );
1755 
1756  // Determine whether there is enough I/O stampede risk to justify throttling set().
1757  // Estimate unthrottled set() overhead, as bps, from miss count/rate and value size,
1758  // comparing it to the per-key uplink bps limit (KEY_HIGH_UPLINK_BPS), e.g.:
1759  // - 2 misses (< 1s), 10KB value, 1250000 bps limit => 160000 bits (low risk)
1760  // - 2 misses (< 1s), 100KB value, 1250000 bps limit => 1600000 bits (high risk)
1761  // - 10 misses (< 1s), 10KB value, 1250000 bps limit => 800000 bits (low risk)
1762  // - 10 misses (< 1s), 100KB value, 1250000 bps limit => 8000000 bits (high risk)
1763  // - 300 misses (100/s), 1KB value, 1250000 bps limit => 800000 bps (low risk)
1764  // - 300 misses (100/s), 10KB value, 1250000 bps limit => 8000000 bps (high risk)
1765  // - 300 misses (100/s), 100KB value, 1250000 bps limit => 80000000 bps (high risk)
1766  if ( ( $missesPerSecForHighQPS * $estimatedSize ) >= $this->keyHighUplinkBps ) {
1767  $this->cache->clearLastError();
1768  if (
1769  !$this->cache->add(
1770  $this->makeSisterKey( $key, self::TYPE_COOLOFF ),
1771  1,
1772  self::$COOLOFF_TTL
1773  ) &&
1774  // Don't treat failures due to I/O errors as the key being in cooloff
1775  $this->cache->getLastError() === BagOStuff::ERR_NONE
1776  ) {
1777  $this->stats->increment( "wanobjectcache.$kClass.cooloff_bounce" );
1778 
1779  return false;
1780  }
1781  }
1782  }
1783 
1784  // Corresponding metrics for cache writes that actually get sent over the write
1785  $this->stats->timing( "wanobjectcache.$kClass.regen_set_delay", 1e3 * $elapsed );
1786  $this->stats->updateCount( "wanobjectcache.$kClass.regen_set_bytes", $estimatedSize );
1787 
1788  return true;
1789  }
1790 
1799  private function resolveCTL( $value, $curTTL, $curInfo, $touchedCallback ) {
1800  if ( $touchedCallback === null || $value === false ) {
1801  return [
1802  $curTTL,
1803  max( $curInfo[self::KEY_TOMB_AS_OF], $curInfo[self::KEY_CHECK_AS_OF] )
1804  ];
1805  }
1806 
1807  $touched = $touchedCallback( $value );
1808  if ( $touched !== null && $touched >= $curInfo[self::KEY_AS_OF] ) {
1809  $curTTL = min( $curTTL, self::$TINY_NEGATIVE, $curInfo[self::KEY_AS_OF] - $touched );
1810  }
1811 
1812  return [
1813  $curTTL,
1814  max(
1815  $curInfo[self::KEY_TOMB_AS_OF],
1816  $curInfo[self::KEY_CHECK_AS_OF],
1817  $touched
1818  )
1819  ];
1820  }
1821 
1829  private function resolveTouched( $value, $lastPurge, $touchedCallback ) {
1830  return ( $touchedCallback === null || $value === false )
1831  ? $lastPurge // nothing to derive the "touched timestamp" from
1832  : max( $touchedCallback( $value ), $lastPurge );
1833  }
1834 
1840  private function getInterimValue( $key, $minAsOf ) {
1841  $now = $this->getCurrentTime();
1842 
1843  if ( $this->useInterimHoldOffCaching ) {
1844  $wrapped = $this->cache->get(
1845  $this->makeSisterKey( $key, self::TYPE_INTERIM )
1846  );
1847 
1848  list( $value, $keyInfo ) = $this->unwrap( $wrapped, $now );
1849  if ( $this->isValid( $value, $keyInfo[self::KEY_AS_OF], $minAsOf ) ) {
1850  return [ $value, $keyInfo ];
1851  }
1852  }
1853 
1854  return $this->unwrap( false, $now );
1855  }
1856 
1864  private function setInterimValue( $key, $value, $ttl, $version, $walltime ) {
1865  $ttl = max( self::$INTERIM_KEY_TTL, (int)$ttl );
1866 
1867  $wrapped = $this->wrap( $value, $ttl, $version, $this->getCurrentTime(), $walltime );
1868  $this->cache->merge(
1869  $this->makeSisterKey( $key, self::TYPE_INTERIM ),
1870  function () use ( $wrapped ) {
1871  return $wrapped;
1872  },
1873  $ttl,
1874  1
1875  );
1876  }
1877 
1882  private function resolveBusyValue( $busyValue ) {
1883  return ( $busyValue instanceof Closure ) ? $busyValue() : $busyValue;
1884  }
1885 
1950  final public function getMultiWithSetCallback(
1951  ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
1952  ) {
1953  // Load required keys into process cache in one go
1954  $this->warmupCache = $this->getRawKeysForWarmup(
1955  $this->getNonProcessCachedMultiKeys( $keyedIds, $opts ),
1956  $opts['checkKeys'] ?? []
1957  );
1958  $this->warmupKeyMisses = 0;
1959 
1960  // The required callback signature includes $id as the first argument for convenience
1961  // to distinguish different items. To reuse the code in getWithSetCallback(), wrap the
1962  // callback with a proxy callback that has the standard getWithSetCallback() signature.
1963  // This is defined only once per batch to avoid closure creation overhead.
1964  $proxyCb = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf, $params ) use ( $callback ) {
1965  return $callback( $params['id'], $oldValue, $ttl, $setOpts, $oldAsOf );
1966  };
1967 
1968  $values = [];
1969  foreach ( $keyedIds as $key => $id ) { // preserve order
1970  $values[$key] = $this->getWithSetCallback(
1971  $key,
1972  $ttl,
1973  $proxyCb,
1974  $opts,
1975  [ 'id' => $id ]
1976  );
1977  }
1978 
1979  $this->warmupCache = [];
1980 
1981  return $values;
1982  }
1983 
2049  final public function getMultiWithUnionSetCallback(
2050  ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
2051  ) {
2052  $checkKeys = $opts['checkKeys'] ?? [];
2053  unset( $opts['lockTSE'] ); // incompatible
2054  unset( $opts['busyValue'] ); // incompatible
2055 
2056  // Load required keys into process cache in one go
2057  $keysByIdGet = $this->getNonProcessCachedMultiKeys( $keyedIds, $opts );
2058  $this->warmupCache = $this->getRawKeysForWarmup( $keysByIdGet, $checkKeys );
2059  $this->warmupKeyMisses = 0;
2060 
2061  // IDs of entities known to be in need of regeneration
2062  $idsRegen = [];
2063 
2064  // Find out which keys are missing/deleted/stale
2065  $curTTLs = [];
2066  $asOfs = [];
2067  $curByKey = $this->getMulti( $keysByIdGet, $curTTLs, $checkKeys, $asOfs );
2068  foreach ( $keysByIdGet as $id => $key ) {
2069  if ( !array_key_exists( $key, $curByKey ) || $curTTLs[$key] < 0 ) {
2070  $idsRegen[] = $id;
2071  }
2072  }
2073 
2074  // Run the callback to populate the regeneration value map for all required IDs
2075  $newSetOpts = [];
2076  $newTTLsById = array_fill_keys( $idsRegen, $ttl );
2077  $newValsById = $idsRegen ? $callback( $idsRegen, $newTTLsById, $newSetOpts ) : [];
2078 
2079  // The required callback signature includes $id as the first argument for convenience
2080  // to distinguish different items. To reuse the code in getWithSetCallback(), wrap the
2081  // callback with a proxy callback that has the standard getWithSetCallback() signature.
2082  // This is defined only once per batch to avoid closure creation overhead.
2083  $proxyCb = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf, $params )
2084  use ( $callback, $newValsById, $newTTLsById, $newSetOpts )
2085  {
2086  $id = $params['id'];
2087 
2088  if ( array_key_exists( $id, $newValsById ) ) {
2089  // Value was already regerated as expected, so use the value in $newValsById
2090  $newValue = $newValsById[$id];
2091  $ttl = $newTTLsById[$id];
2092  $setOpts = $newSetOpts;
2093  } else {
2094  // Pre-emptive/popularity refresh and version mismatch cases are not detected
2095  // above and thus $newValsById has no entry. Run $callback on this single entity.
2096  $ttls = [ $id => $ttl ];
2097  $newValue = $callback( [ $id ], $ttls, $setOpts )[$id];
2098  $ttl = $ttls[$id];
2099  }
2100 
2101  return $newValue;
2102  };
2103 
2104  // Run the cache-aside logic using warmupCache instead of persistent cache queries
2105  $values = [];
2106  foreach ( $keyedIds as $key => $id ) { // preserve order
2107  $values[$key] = $this->getWithSetCallback(
2108  $key,
2109  $ttl,
2110  $proxyCb,
2111  $opts,
2112  [ 'id' => $id ]
2113  );
2114  }
2115 
2116  $this->warmupCache = [];
2117 
2118  return $values;
2119  }
2120 
2133  final public function reap( $key, $purgeTimestamp, &$isStale = false ) {
2134  $minAsOf = $purgeTimestamp + self::HOLDOFF_TTL;
2135  $wrapped = $this->cache->get( $this->makeSisterKey( $key, self::TYPE_VALUE ) );
2136  if ( is_array( $wrapped ) && $wrapped[self::$FLD_TIME] < $minAsOf ) {
2137  $isStale = true;
2138  $this->logger->warning( "Reaping stale value key '$key'." );
2139  $ttlReap = self::HOLDOFF_TTL; // avoids races with tombstone creation
2140  $ok = $this->cache->changeTTL(
2141  $this->makeSisterKey( $key, self::TYPE_VALUE ),
2142  $ttlReap
2143  );
2144  if ( !$ok ) {
2145  $this->logger->error( "Could not complete reap of key '$key'." );
2146  }
2147 
2148  return $ok;
2149  }
2150 
2151  $isStale = false;
2152 
2153  return true;
2154  }
2155 
2165  final public function reapCheckKey( $key, $purgeTimestamp, &$isStale = false ) {
2166  $purge = $this->parsePurgeValue(
2167  $this->cache->get( $this->makeSisterKey( $key, self::TYPE_TIMESTAMP ) )
2168  );
2169  if ( $purge && $purge[self::$PURGE_TIME] < $purgeTimestamp ) {
2170  $isStale = true;
2171  $this->logger->warning( "Reaping stale check key '$key'." );
2172  $ok = $this->cache->changeTTL(
2173  $this->makeSisterKey( $key, self::TYPE_TIMESTAMP ),
2174  self::TTL_SECOND
2175  );
2176  if ( !$ok ) {
2177  $this->logger->error( "Could not complete reap of check key '$key'." );
2178  }
2179 
2180  return $ok;
2181  }
2182 
2183  $isStale = false;
2184 
2185  return false;
2186  }
2187 
2198  public function makeGlobalKey( $collection, ...$components ) {
2199  return $this->cache->makeGlobalKey( ...func_get_args() );
2200  }
2201 
2212  public function makeKey( $collection, ...$components ) {
2213  return $this->cache->makeKey( ...func_get_args() );
2214  }
2215 
2223  public function hash256( $component ) {
2224  return hash_hmac( 'sha256', $component, $this->secret );
2225  }
2226 
2277  final public function makeMultiKeys( array $ids, $keyCallback ) {
2278  $idByKey = [];
2279  foreach ( $ids as $id ) {
2280  // Discourage triggering of automatic makeKey() hashing in some backends
2281  if ( strlen( $id ) > 64 ) {
2282  $this->logger->warning( __METHOD__ . ": long ID '$id'; use hash256()" );
2283  }
2284  $key = $keyCallback( $id, $this );
2285  // Edge case: ignore key collisions due to duplicate $ids like "42" and 42
2286  if ( !isset( $idByKey[$key] ) ) {
2287  $idByKey[$key] = $id;
2288  } elseif ( (string)$id !== (string)$idByKey[$key] ) {
2289  throw new UnexpectedValueException(
2290  "Cache key collision; IDs ('$id','{$idByKey[$key]}') map to '$key'"
2291  );
2292  }
2293  }
2294 
2295  return new ArrayIterator( $idByKey );
2296  }
2297 
2333  final public function multiRemap( array $ids, array $res ) {
2334  if ( count( $ids ) !== count( $res ) ) {
2335  // If makeMultiKeys() is called on a list of non-unique IDs, then the resulting
2336  // ArrayIterator will have less entries due to "first appearance" de-duplication
2337  $ids = array_keys( array_flip( $ids ) );
2338  if ( count( $ids ) !== count( $res ) ) {
2339  throw new UnexpectedValueException( "Multi-key result does not match ID list" );
2340  }
2341  }
2342 
2343  return array_combine( $ids, $res );
2344  }
2345 
2350  final public function getLastError() {
2351  $code = $this->cache->getLastError();
2352  switch ( $code ) {
2353  case BagOStuff::ERR_NONE:
2354  return self::ERR_NONE;
2355  case BagOStuff::ERR_NO_RESPONSE:
2356  return self::ERR_NO_RESPONSE;
2357  case BagOStuff::ERR_UNREACHABLE:
2358  return self::ERR_UNREACHABLE;
2359  default:
2360  return self::ERR_UNEXPECTED;
2361  }
2362  }
2363 
2367  final public function clearLastError() {
2368  $this->cache->clearLastError();
2369  }
2370 
2376  public function clearProcessCache() {
2377  $this->processCaches = [];
2378  }
2379 
2400  final public function useInterimHoldOffCaching( $enabled ) {
2401  $this->useInterimHoldOffCaching = $enabled;
2402  }
2403 
2409  public function getQoS( $flag ) {
2410  return $this->cache->getQoS( $flag );
2411  }
2412 
2476  public function adaptiveTTL( $mtime, $maxTTL, $minTTL = 30, $factor = 0.2 ) {
2477  if ( is_float( $mtime ) || ctype_digit( $mtime ) ) {
2478  $mtime = (int)$mtime; // handle fractional seconds and string integers
2479  }
2480 
2481  if ( !is_int( $mtime ) || $mtime <= 0 ) {
2482  return $minTTL; // no last-modified time provided
2483  }
2484 
2485  $age = $this->getCurrentTime() - $mtime;
2486 
2487  return (int)min( $maxTTL, max( $minTTL, $factor * $age ) );
2488  }
2489 
2494  final public function getWarmupKeyMisses() {
2495  return $this->warmupKeyMisses;
2496  }
2497 
2508  protected function relayPurge( $key, $ttl, $holdoff ) {
2509  if ( $this->mcrouterAware ) {
2510  // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
2511  // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
2512  $ok = $this->cache->set(
2513  "/*/{$this->cluster}/{$key}",
2514  $this->makePurgeValue( $this->getCurrentTime(), $holdoff ),
2515  $ttl
2516  );
2517  } else {
2518  // Some other proxy handles broadcasting or there is only one datacenter
2519  $ok = $this->cache->set(
2520  $key,
2521  $this->makePurgeValue( $this->getCurrentTime(), $holdoff ),
2522  $ttl
2523  );
2524  }
2525 
2526  return $ok;
2527  }
2528 
2535  protected function relayDelete( $key ) {
2536  if ( $this->mcrouterAware ) {
2537  // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
2538  // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
2539  $ok = $this->cache->delete( "/*/{$this->cluster}/{$key}" );
2540  } else {
2541  // Some other proxy handles broadcasting or there is only one datacenter
2542  $ok = $this->cache->delete( $key );
2543  }
2544 
2545  return $ok;
2546  }
2547 
2559  private function scheduleAsyncRefresh( $key, $ttl, $callback, array $opts, array $cbParams ) {
2560  if ( !$this->asyncHandler ) {
2561  return false;
2562  }
2563  // Update the cache value later, such during post-send of an HTTP request. This forces
2564  // cache regeneration by setting "minAsOf" to infinity, meaning that no existing value
2565  // is considered valid. Furthermore, note that preemptive regeneration is not applicable
2566  // to invalid values, so there is no risk of infinite preemptive regeneration loops.
2567  $func = $this->asyncHandler;
2568  $func( function () use ( $key, $ttl, $callback, $opts, $cbParams ) {
2569  $opts['minAsOf'] = INF;
2570  $this->fetchOrRegenerate( $key, $ttl, $callback, $opts, $cbParams );
2571  } );
2572 
2573  return true;
2574  }
2575 
2590  private function isAliveOrInGracePeriod( $curTTL, $graceTTL ) {
2591  if ( $curTTL > 0 ) {
2592  return true;
2593  } elseif ( $graceTTL <= 0 ) {
2594  return false;
2595  }
2596 
2597  $ageStale = abs( $curTTL ); // seconds of staleness
2598  $curGraceTTL = ( $graceTTL - $ageStale ); // current grace-time-to-live
2599  if ( $curGraceTTL <= 0 ) {
2600  return false; // already out of grace period
2601  }
2602 
2603  // Chance of using a stale value is the complement of the chance of refreshing it
2604  return !$this->worthRefreshExpiring( $curGraceTTL, $graceTTL, $graceTTL );
2605  }
2606 
2624  protected function worthRefreshExpiring( $curTTL, $logicalTTL, $lowTTL ) {
2625  if ( $lowTTL <= 0 ) {
2626  return false;
2627  }
2628 
2629  // T264787: avoid having keys start off with a high chance of being refreshed;
2630  // the point where refreshing becomes possible cannot precede the key lifetime.
2631  $effectiveLowTTL = min( $lowTTL, $logicalTTL ?: INF );
2632 
2633  if ( $curTTL >= $effectiveLowTTL || $curTTL <= 0 ) {
2634  return false;
2635  }
2636 
2637  $chance = ( 1 - $curTTL / $effectiveLowTTL );
2638 
2639  // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
2640  $decision = ( mt_rand( 1, 1e9 ) <= 1e9 * $chance );
2641 
2642  $this->logger->debug(
2643  "worthRefreshExpiring($curTTL, $logicalTTL, $lowTTL): " .
2644  "p = $chance; refresh = " . ( $decision ? 'Y' : 'N' )
2645  );
2646 
2647  return $decision;
2648  }
2649 
2665  protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) {
2666  if ( $ageNew < 0 || $timeTillRefresh <= 0 ) {
2667  return false;
2668  }
2669 
2670  $age = $now - $asOf;
2671  $timeOld = $age - $ageNew;
2672  if ( $timeOld <= 0 ) {
2673  return false;
2674  }
2675 
2676  $popularHitsPerSec = 1;
2677  // Lifecycle is: new, ramp-up refresh chance, full refresh chance.
2678  // Note that the "expected # of refreshes" for the ramp-up time range is half
2679  // of what it would be if P(refresh) was at its full value during that time range.
2680  $refreshWindowSec = max( $timeTillRefresh - $ageNew - self::$RAMPUP_TTL / 2, 1 );
2681  // P(refresh) * (# hits in $refreshWindowSec) = (expected # of refreshes)
2682  // P(refresh) * ($refreshWindowSec * $popularHitsPerSec) = 1 (by definition)
2683  // P(refresh) = 1/($refreshWindowSec * $popularHitsPerSec)
2684  $chance = 1 / ( $popularHitsPerSec * $refreshWindowSec );
2685  // Ramp up $chance from 0 to its nominal value over RAMPUP_TTL seconds to avoid stampedes
2686  $chance *= ( $timeOld <= self::$RAMPUP_TTL ) ? $timeOld / self::$RAMPUP_TTL : 1;
2687 
2688  // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
2689  $decision = ( mt_rand( 1, 1e9 ) <= 1e9 * $chance );
2690 
2691  $this->logger->debug(
2692  "worthRefreshPopular($asOf, $ageNew, $timeTillRefresh, $now): " .
2693  "p = $chance; refresh = " . ( $decision ? 'Y' : 'N' )
2694  );
2695 
2696  return $decision;
2697  }
2698 
2708  protected function isValid( $value, $asOf, $minAsOf, $purgeTime = null ) {
2709  // Avoid reading any key not generated after the latest delete() or touch
2710  $safeMinAsOf = max( $minAsOf, $purgeTime + self::$TINY_POSTIVE );
2711 
2712  if ( $value === false ) {
2713  return false;
2714  } elseif ( $safeMinAsOf > 0 && $asOf < $minAsOf ) {
2715  return false;
2716  }
2717 
2718  return true;
2719  }
2720 
2729  private function wrap( $value, $ttl, $version, $now, $walltime ) {
2730  // Returns keys in ascending integer order for PHP7 array packing:
2731  // https://nikic.github.io/2014/12/22/PHPs-new-hashtable-implementation.html
2732  $wrapped = [
2733  self::$FLD_FORMAT_VERSION => self::$VERSION,
2734  self::$FLD_VALUE => $value,
2735  self::$FLD_TTL => $ttl,
2736  self::$FLD_TIME => $now
2737  ];
2738  if ( $version !== null ) {
2739  $wrapped[self::$FLD_VALUE_VERSION] = $version;
2740  }
2741  if ( $walltime >= self::$GENERATION_SLOW_SEC ) {
2742  $wrapped[self::$FLD_GENERATION_TIME] = $walltime;
2743  }
2744 
2745  return $wrapped;
2746  }
2747 
2760  private function unwrap( $wrapped, $now ) {
2761  $value = false;
2762  $info = $this->newKeyInfoPlaceholder();
2763 
2764  if ( is_array( $wrapped ) ) {
2765  // Entry expected to be a cached value; validate it
2766  if (
2767  ( $wrapped[self::$FLD_FORMAT_VERSION] ?? null ) === self::$VERSION &&
2768  $wrapped[self::$FLD_TIME] >= $this->epoch
2769  ) {
2770  if ( $wrapped[self::$FLD_TTL] > 0 ) {
2771  // Get the approximate time left on the key
2772  $age = $now - $wrapped[self::$FLD_TIME];
2773  $curTTL = max( $wrapped[self::$FLD_TTL] - $age, 0.0 );
2774  } else {
2775  // Key had no TTL, so the time left is unbounded
2776  $curTTL = INF;
2777  }
2778  $value = $wrapped[self::$FLD_VALUE];
2779  $info[self::KEY_VERSION] = $wrapped[self::$FLD_VALUE_VERSION] ?? null;
2780  $info[self::KEY_AS_OF] = $wrapped[self::$FLD_TIME];
2781  $info[self::KEY_CUR_TTL] = $curTTL;
2782  $info[self::KEY_TTL] = $wrapped[self::$FLD_TTL];
2783  }
2784  } else {
2785  // Entry expected to be a tombstone; parse it
2786  $purge = $this->parsePurgeValue( $wrapped );
2787  if ( $purge !== false ) {
2788  // Tombstoned keys should always have a negative current $ttl
2789  $info[self::KEY_CUR_TTL] =
2790  min( $purge[self::$PURGE_TIME] - $now, self::$TINY_NEGATIVE );
2791  $info[self::KEY_TOMB_AS_OF] = $purge[self::$PURGE_TIME];
2792  }
2793  }
2794 
2795  return [ $value, $info ];
2796  }
2797 
2801  private function newKeyInfoPlaceholder() {
2802  return [
2803  self::KEY_VERSION => null,
2804  self::KEY_AS_OF => null,
2805  self::KEY_TTL => null,
2806  self::KEY_CUR_TTL => null,
2807  self::KEY_TOMB_AS_OF => null
2808  ];
2809  }
2810 
2815  private function determineKeyClassForStats( $key ) {
2816  $parts = explode( ':', $key, 3 );
2817  // Sanity fallback in case the key was not made by makeKey.
2818  // Replace dots because they are special in StatsD (T232907)
2819  return strtr( $parts[1] ?? $parts[0], '.', '_' );
2820  }
2821 
2827  private function parsePurgeValue( $value ) {
2828  if ( !is_string( $value ) ) {
2829  return false;
2830  }
2831 
2832  $segments = explode( ':', $value, 3 );
2833  if (
2834  !isset( $segments[0] ) ||
2835  !isset( $segments[1] ) ||
2836  "{$segments[0]}:" !== self::PURGE_VAL_PREFIX
2837  ) {
2838  return false;
2839  }
2840 
2841  if ( !isset( $segments[2] ) ) {
2842  // Back-compat with old purge values without holdoff
2843  $segments[2] = self::HOLDOFF_TTL;
2844  }
2845 
2846  if ( $segments[1] < $this->epoch ) {
2847  // Values this old are ignored
2848  return false;
2849  }
2850 
2851  return [
2852  self::$PURGE_TIME => (float)$segments[1],
2853  self::$PURGE_HOLDOFF => (int)$segments[2],
2854  ];
2855  }
2856 
2862  private function makePurgeValue( $timestamp, $holdoff ) {
2863  return self::PURGE_VAL_PREFIX . (float)$timestamp . ':' . (int)$holdoff;
2864  }
2865 
2870  private function getProcessCache( $group ) {
2871  if ( !isset( $this->processCaches[$group] ) ) {
2872  list( , $size ) = explode( ':', $group );
2873  $this->processCaches[$group] = new MapCacheLRU( (int)$size );
2874  if ( $this->wallClockOverride !== null ) {
2875  $this->processCaches[$group]->setMockTime( $this->wallClockOverride );
2876  }
2877  }
2878 
2879  return $this->processCaches[$group];
2880  }
2881 
2887  private function getProcessCacheKey( $key, $version ) {
2888  return $key . ' ' . (int)$version;
2889  }
2890 
2896  private function getNonProcessCachedMultiKeys( ArrayIterator $keys, array $opts ) {
2897  $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
2898 
2899  $keysMissing = [];
2900  if ( $pcTTL > 0 && $this->callbackDepth == 0 ) {
2901  $version = $opts['version'] ?? null;
2902  $pCache = $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY );
2903  foreach ( $keys as $key => $id ) {
2904  if ( !$pCache->has( $this->getProcessCacheKey( $key, $version ), $pcTTL ) ) {
2905  $keysMissing[$id] = $key;
2906  }
2907  }
2908  }
2909 
2910  return $keysMissing;
2911  }
2912 
2918  private function getRawKeysForWarmup( array $keys, array $checkKeys ) {
2919  if ( !$keys ) {
2920  return [];
2921  }
2922 
2923  // Get all the value keys to fetch...
2924  $keysWarmup = $this->makeSisterKeys( $keys, self::TYPE_VALUE );
2925  // Get all the check keys to fetch...
2926  foreach ( $checkKeys as $i => $checkKeyOrKeyGroup ) {
2927  // Note: avoid array_merge() inside loop in case there are many keys
2928  if ( is_int( $i ) ) {
2929  // Single check key that applies to all value keys
2930  $keysWarmup[] = $this->makeSisterKey( $checkKeyOrKeyGroup, self::TYPE_TIMESTAMP );
2931  } else {
2932  // List of check keys that apply to a specific value key
2933  foreach ( (array)$checkKeyOrKeyGroup as $checkKey ) {
2934  $keysWarmup[] = $this->makeSisterKey( $checkKey, self::TYPE_TIMESTAMP );
2935  }
2936  }
2937  }
2938 
2939  $warmupCache = $this->cache->getMulti( $keysWarmup );
2940  $warmupCache += array_fill_keys( $keysWarmup, false );
2941 
2942  return $warmupCache;
2943  }
2944 
2949  protected function getCurrentTime() {
2950  if ( $this->wallClockOverride ) {
2951  return $this->wallClockOverride;
2952  }
2953 
2954  $clockTime = (float)time(); // call this first
2955  // microtime() uses an initial gettimeofday() call added to usage clocks.
2956  // This can severely drift from time() and the microtime() value of other threads
2957  // due to undercounting of the amount of time elapsed. Instead of seeing the current
2958  // time as being in the past, use the value of time(). This avoids setting cache values
2959  // that will immediately be seen as expired and possibly cause stampedes.
2960  return max( microtime( true ), $clockTime );
2961  }
2962 
2967  public function setMockTime( &$time ) {
2968  $this->wallClockOverride =& $time;
2969  $this->cache->setMockTime( $time );
2970  foreach ( $this->processCaches as $pCache ) {
2971  $pCache->setMockTime( $time );
2972  }
2973  }
2974 }
WANObjectCache\$RAMPUP_TTL
static int $RAMPUP_TTL
Seconds to ramp up the chance of regeneration due to expected time-till-refresh.
Definition: WANObjectCache.php:222
WANObjectCache\getWithSetCallback
getWithSetCallback( $key, $ttl, $callback, array $opts=[], array $cbParams=[])
Method to fetch/regenerate a cache key.
Definition: WANObjectCache.php:1372
WANObjectCache\getCollectionFromKey
static getCollectionFromKey(string $sisterKey)
Definition: WANObjectCache.php:1695
WANObjectCache\getQoS
getQoS( $flag)
Definition: WANObjectCache.php:2409
WANObjectCache\$VERSION
static int $VERSION
Cache format version number.
Definition: WANObjectCache.php:243
WANObjectCache\hash256
hash256( $component)
Hash a possibly long string into a suitable component for makeKey()/makeGlobalKey()
Definition: WANObjectCache.php:2223
WANObjectCache\determineKeyClassForStats
determineKeyClassForStats( $key)
Definition: WANObjectCache.php:2815
WANObjectCache\relayDelete
relayDelete( $key)
Do the actual async bus delete of a key.
Definition: WANObjectCache.php:2535
WANObjectCache\setMockTime
setMockTime(&$time)
Definition: WANObjectCache.php:2967
WANObjectCache\makePurgeValue
makePurgeValue( $timestamp, $holdoff)
Definition: WANObjectCache.php:2862
WANObjectCache\$INTERIM_KEY_TTL
static int $INTERIM_KEY_TTL
Seconds to keep interim value keys for tombstoned keys around.
Definition: WANObjectCache.php:215
WANObjectCache\$warmupCache
mixed[] $warmupCache
Temporary warm-up cache.
Definition: WANObjectCache.php:162
EmptyBagOStuff
A BagOStuff object with no objects in it.
Definition: EmptyBagOStuff.php:29
WANObjectCache\$cache
BagOStuff $cache
The local datacenter cache.
Definition: WANObjectCache.php:127
WANObjectCache\fetchOrRegenerate
fetchOrRegenerate( $key, $ttl, $callback, array $opts, array $cbParams)
Do the actual I/O for getWithSetCallback() when needed.
Definition: WANObjectCache.php:1432
WANObjectCache\isValid
isValid( $value, $asOf, $minAsOf, $purgeTime=null)
Check if $value is not false, versioned (if needed), and not older than $minTime (if set)
Definition: WANObjectCache.php:2708
true
return true
Definition: router.php:90
Wikimedia\LightweightObjectStore\ExpirationAwareness
Generic interface providing Time-To-Live constants for expirable object storage.
Definition: ExpirationAwareness.php:32
WANObjectCache\$FLD_GENERATION_TIME
static int $FLD_GENERATION_TIME
Key to how long it took to generate the value.
Definition: WANObjectCache.php:271
NullStatsdDataFactory
Definition: NullStatsdDataFactory.php:10
WANObjectCache\getMultiWithSetCallback
getMultiWithSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch multiple cache keys at once with regeneration.
Definition: WANObjectCache.php:1950
WANObjectCache\$TINY_NEGATIVE
static float $TINY_NEGATIVE
Tiny negative float to use when CTL comes up >= 0 due to clock skew.
Definition: WANObjectCache.php:225
BagOStuff
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:72
WANObjectCache\getMultiWithUnionSetCallback
getMultiWithUnionSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch/regenerate multiple cache keys at once.
Definition: WANObjectCache.php:2049
WANObjectCache\resolveTouched
resolveTouched( $value, $lastPurge, $touchedCallback)
Definition: WANObjectCache.php:1829
WANObjectCache\getMulti
getMulti(array $keys, &$curTTLs=[], array $checkKeys=[], &$info=null)
Fetch the value of several keys from cache.
Definition: WANObjectCache.php:465
WANObjectCache\worthRefreshExpiring
worthRefreshExpiring( $curTTL, $logicalTTL, $lowTTL)
Check if a key is nearing expiration and thus due for randomized regeneration.
Definition: WANObjectCache.php:2624
$res
$res
Definition: testCompression.php:57
WANObjectCache\reapCheckKey
reapCheckKey( $key, $purgeTimestamp, &$isStale=false)
Set a "check" key to soon expire in the local cluster if it pre-dates $purgeTimestamp.
Definition: WANObjectCache.php:2165
WANObjectCache\getCheckKeyTime
getCheckKeyTime( $key)
Fetch the value of a timestamp "check" key.
Definition: WANObjectCache.php:887
WANObjectCache\resolveBusyValue
resolveBusyValue( $busyValue)
Definition: WANObjectCache.php:1882
WANObjectCache\$logger
LoggerInterface $logger
Definition: WANObjectCache.php:131
WANObjectCache\$FLD_VALUE
static int $FLD_VALUE
Key to the cached value.
Definition: WANObjectCache.php:261
WANObjectCache\$warmupKeyMisses
int $warmupKeyMisses
Key fetched.
Definition: WANObjectCache.php:164
WANObjectCache\newEmpty
static newEmpty()
Get an instance that wraps EmptyBagOStuff.
Definition: WANObjectCache.php:367
WANObjectCache\$RECENT_SET_LOW_MS
static int $RECENT_SET_LOW_MS
Min millisecond set() backoff during hold-off (far less than INTERIM_KEY_TTL)
Definition: WANObjectCache.php:230
WANObjectCache\clearProcessCache
clearProcessCache()
Clear the in-process caches; useful for testing.
Definition: WANObjectCache.php:2376
WANObjectCache\getInterimValue
getInterimValue( $key, $minAsOf)
Definition: WANObjectCache.php:1840
WANObjectCache\$secret
string $secret
Stable secret used for hasing long strings into key components.
Definition: WANObjectCache.php:148
WANObjectCache\clearLastError
clearLastError()
Clear the "last error" registry.
Definition: WANObjectCache.php:2367
WANObjectCache\getCurrentTime
getCurrentTime()
Definition: WANObjectCache.php:2949
WANObjectCache\$FLD_FORMAT_VERSION
static int $FLD_FORMAT_VERSION
Key to WAN cache version number.
Definition: WANObjectCache.php:259
WANObjectCache\unwrap
unwrap( $wrapped, $now)
Definition: WANObjectCache.php:2760
WANObjectCache\getLastError
getLastError()
Get the "last error" registered; clearLastError() should be called manually.
Definition: WANObjectCache.php:2350
WANObjectCache\makeSisterKeys
makeSisterKeys(array $baseKeys, $type)
Get cache keys that should be collocated with their corresponding base keys.
Definition: WANObjectCache.php:1652
WANObjectCache\makeMultiKeys
makeMultiKeys(array $ids, $keyCallback)
Get an iterator of (cache key => entity ID) for a list of entity IDs.
Definition: WANObjectCache.php:2277
WANObjectCache\__construct
__construct(array $params)
Definition: WANObjectCache.php:323
WANObjectCache\useInterimHoldOffCaching
useInterimHoldOffCaching( $enabled)
Enable or disable the use of brief caching for tombstoned keys.
Definition: WANObjectCache.php:2400
WANObjectCache\reap
reap( $key, $purgeTimestamp, &$isStale=false)
Set a key to soon expire in the local cluster if it pre-dates $purgeTimestamp.
Definition: WANObjectCache.php:2133
WANObjectCache\$keyHighQps
int $keyHighQps
Reads/second assumed during a hypothetical cache write stampede for a key.
Definition: WANObjectCache.php:155
WANObjectCache\touchCheckKey
touchCheckKey( $key, $holdoff=self::HOLDOFF_TTL)
Purge a "check" key from all datacenters, invalidating keys that use it.
Definition: WANObjectCache.php:1017
MapCacheLRU
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:37
WANObjectCache\$mcrouterAware
bool $mcrouterAware
Whether to use mcrouter key prefixing for routing.
Definition: WANObjectCache.php:138
WANObjectCache\wrap
wrap( $value, $ttl, $version, $now, $walltime)
Definition: WANObjectCache.php:2729
WANObjectCache\adaptiveTTL
adaptiveTTL( $mtime, $maxTTL, $minTTL=30, $factor=0.2)
Get a TTL that is higher for objects that have not changed recently.
Definition: WANObjectCache.php:2476
WANObjectCache\getNonProcessCachedMultiKeys
getNonProcessCachedMultiKeys(ArrayIterator $keys, array $opts)
Definition: WANObjectCache.php:2896
WANObjectCache\$epoch
float $epoch
Unix timestamp of the oldest possible valid values.
Definition: WANObjectCache.php:146
WANObjectCache\relayPurge
relayPurge( $key, $ttl, $holdoff)
Do the actual async bus purge of a key.
Definition: WANObjectCache.php:2508
WANObjectCache\$wallClockOverride
float null $wallClockOverride
Definition: WANObjectCache.php:167
WANObjectCache\$CHECK_KEY_TTL
static int $CHECK_KEY_TTL
Seconds to keep dependency purge keys around.
Definition: WANObjectCache.php:213
Wikimedia\LightweightObjectStore\StorageAwareness
Generic interface providing error code and quality-of-service constants for object stores.
Definition: StorageAwareness.php:32
WANObjectCache\makeGlobalKey
makeGlobalKey( $collection,... $components)
Make a cache key for the global keyspace and given components.
Definition: WANObjectCache.php:2198
WANObjectCache\isVolatileValueAgeNegligible
isVolatileValueAgeNegligible( $age)
Definition: WANObjectCache.php:1716
WANObjectCache\getMultiCheckKeyTime
getMultiCheckKeyTime(array $keys)
Fetch the values of each timestamp "check" key.
Definition: WANObjectCache.php:952
WANObjectCache\$COOLOFF_TTL
static int $COOLOFF_TTL
Seconds to no-op key set() calls to avoid large blob I/O stampedes.
Definition: WANObjectCache.php:220
WANObjectCache\worthRefreshPopular
worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now)
Check if a key is due for randomized regeneration due to its popularity.
Definition: WANObjectCache.php:2665
WANObjectCache\$LOCK_TTL
static int $LOCK_TTL
Seconds to keep lock keys around.
Definition: WANObjectCache.php:218
WANObjectCache\isAliveOrInGracePeriod
isAliveOrInGracePeriod( $curTTL, $graceTTL)
Check if a key is fresh or in the grace window and thus due for randomized reuse.
Definition: WANObjectCache.php:2590
WANObjectCache\$useInterimHoldOffCaching
bool $useInterimHoldOffCaching
Whether to use "interim" caching while keys are tombstoned.
Definition: WANObjectCache.php:144
WANObjectCache\$cluster
string $cluster
Cache cluster name for mcrouter use.
Definition: WANObjectCache.php:142
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:125
WANObjectCache\getProcessCache
getProcessCache( $group)
Definition: WANObjectCache.php:2870
WANObjectCache\multiRemap
multiRemap(array $ids, array $res)
Get an (ID => value) map from (i) a non-unique list of entity IDs, and (ii) the list of corresponding...
Definition: WANObjectCache.php:2333
WANObjectCache\getRawKeysForWarmup
getRawKeysForWarmup(array $keys, array $checkKeys)
Definition: WANObjectCache.php:2918
WANObjectCache\$keyHighUplinkBps
float $keyHighUplinkBps
Max tolerable bytes/second to spend on a cache write stampede for a key.
Definition: WANObjectCache.php:157
WANObjectCache\checkAndSetCooloff
checkAndSetCooloff( $key, $kClass, $value, $elapsed, $hasLock)
Check whether set() is rate-limited to avoid concurrent I/O spikes.
Definition: WANObjectCache.php:1741
WANObjectCache\$FLD_FLAGS
static int $FLD_FLAGS
@noinspection PhpUnusedPrivateFieldInspection
Definition: WANObjectCache.php:267
WANObjectCache\resetCheckKey
resetCheckKey( $key)
Delete a "check" key from all datacenters, invalidating keys that use it.
Definition: WANObjectCache.php:1058
WANObjectCache\newKeyInfoPlaceholder
newKeyInfoPlaceholder()
Definition: WANObjectCache.php:2801
WANObjectCache\claimStampedeLock
claimStampedeLock( $key)
Definition: WANObjectCache.php:1620
WANObjectCache\parsePurgeValue
parsePurgeValue( $value)
Definition: WANObjectCache.php:2827
WANObjectCache\$RECENT_SET_HIGH_MS
static int $RECENT_SET_HIGH_MS
Max millisecond set() backoff during hold-off (far less than INTERIM_KEY_TTL)
Definition: WANObjectCache.php:232
WANObjectCache\getProcessCacheKey
getProcessCacheKey( $key, $version)
Definition: WANObjectCache.php:2887
WANObjectCache\yieldStampedeLock
yieldStampedeLock( $key, $hasLock)
Definition: WANObjectCache.php:1633
WANObjectCache\$TINY_POSTIVE
static float $TINY_POSTIVE
Tiny positive float to use when using "minTime" to assert an inequality.
Definition: WANObjectCache.php:227
WANObjectCache\$asyncHandler
callable null $asyncHandler
Function that takes a WAN cache callback and runs it later.
Definition: WANObjectCache.php:135
WANObjectCache\getWarmupKeyMisses
getWarmupKeyMisses()
Definition: WANObjectCache.php:2494
$keys
$keys
Definition: testCompression.php:72
WANObjectCache\$GENERATION_SLOW_SEC
static int $GENERATION_SLOW_SEC
Consider value generation slow if it takes more than this many seconds.
Definition: WANObjectCache.php:235
WANObjectCache\makeSisterKey
makeSisterKey( $baseKey, $typeChar)
Get a cache key that should be collocated with a base key.
Definition: WANObjectCache.php:1668
WANObjectCache\resolveCTL
resolveCTL( $value, $curTTL, $curInfo, $touchedCallback)
Definition: WANObjectCache.php:1799
WANObjectCache\$PURGE_HOLDOFF
static int $PURGE_HOLDOFF
Key to the tombstone entry hold-off TTL.
Definition: WANObjectCache.php:240
WANObjectCache\$callbackDepth
int $callbackDepth
Callback stack depth for getWithSetCallback()
Definition: WANObjectCache.php:160
WANObjectCache\$coalesceScheme
int $coalesceScheme
Scheme to use for key coalescing (Hash Tags or Hash Stops)
Definition: WANObjectCache.php:152
IStoreKeyEncoder
Generic interface for object stores with key encoding methods.
Definition: IStoreKeyEncoder.php:9
WANObjectCache\setLogger
setLogger(LoggerInterface $logger)
Definition: WANObjectCache.php:358
WANObjectCache\$FLD_TTL
static int $FLD_TTL
Key to the original TTL.
Definition: WANObjectCache.php:263
WANObjectCache\makeKey
makeKey( $collection,... $components)
Make a cache key using the "global" keyspace for the given components.
Definition: WANObjectCache.php:2212
WANObjectCache\$coalesceKeys
string bool $coalesceKeys
Whether "sister" keys should be coalesced to the same cache server.
Definition: WANObjectCache.php:150
WANObjectCache\$FLD_VALUE_VERSION
static int $FLD_VALUE_VERSION
Key to collection cache version number.
Definition: WANObjectCache.php:269
WANObjectCache\$FLD_TIME
static int $FLD_TIME
Key to the cache timestamp.
Definition: WANObjectCache.php:265
WANObjectCache\$PURGE_TIME
static int $PURGE_TIME
Key to the tombstone entry timestamp.
Definition: WANObjectCache.php:238
WANObjectCache\$stats
StatsdDataFactoryInterface $stats
Definition: WANObjectCache.php:133
WANObjectCache\setInterimValue
setInterimValue( $key, $value, $ttl, $version, $walltime)
Definition: WANObjectCache.php:1864
WANObjectCache\$processCaches
MapCacheLRU[] $processCaches
Map of group PHP instance caches.
Definition: WANObjectCache.php:129
WANObjectCache\scheduleAsyncRefresh
scheduleAsyncRefresh( $key, $ttl, $callback, array $opts, array $cbParams)
Schedule a deferred cache regeneration if possible.
Definition: WANObjectCache.php:2559
WANObjectCache\processCheckKeys
processCheckKeys(array $timeKeys, array $wrappedValues, $now)
Definition: WANObjectCache.php:576
$type
$type
Definition: testCompression.php:52
WANObjectCache\$region
string $region
Physical region for mcrouter use.
Definition: WANObjectCache.php:140