Movatterモバイル変換


[0]ホーム

URL:


MediaWiki master
WANObjectCache.php
Go to the documentation of this file.
1<?php
21namespaceWikimedia\ObjectCache;
22
23use ArrayIterator;
24use Closure;
25use Exception;
26use Psr\Log\LoggerAwareInterface;
27use Psr\Log\LoggerInterface;
28use Psr\Log\NullLogger;
29use RuntimeException;
30use UnexpectedValueException;
31useWikimedia\LightweightObjectStore\ExpirationAwareness;
32useWikimedia\MapCacheLRU\MapCacheLRU;
33useWikimedia\Stats\StatsFactory;
34useWikimedia\Telemetry\NoopTracer;
35useWikimedia\Telemetry\SpanInterface;
36useWikimedia\Telemetry\TracerInterface;
37
163classWANObjectCacheimplements
164ExpirationAwareness,
165IStoreKeyEncoder,
166 LoggerAwareInterface
167{
169protected$cache;
171protected$processCaches = [];
173protected$logger;
175protected$stats;
177protected$asyncHandler;
178
186protected$broadcastRoute;
188protected$useInterimHoldOffCaching =true;
190protected$epoch;
192protected$coalesceScheme;
193
195private $tracer;
196
198private $missLog;
199
201private $callbackDepth = 0;
203private $warmupCache = [];
205private $warmupKeyMisses = 0;
206
208private $wallClockOverride;
209
211privateconst MAX_COMMIT_DELAY = 3;
213privateconst MAX_READ_LAG = 7;
215publicconstHOLDOFF_TTL = self::MAX_COMMIT_DELAY + self::MAX_READ_LAG + 1;
216
218privateconst LOW_TTL = 60;
220publicconstTTL_LAGGED = 30;
221
223privateconst HOT_TTR = 900;
225privateconst AGE_NEW = 60;
226
228privateconst TSE_NONE = -1;
229
231publicconstSTALE_TTL_NONE = 0;
233publicconstGRACE_TTL_NONE = 0;
235publicconstHOLDOFF_TTL_NONE = 0;
236
238publicconst MIN_TIMESTAMP_NONE = 0.0;
239
241privateconst PC_PRIMARY ='primary:1000';
242
244publicconstPASS_BY_REF = [];
245
247privateconst SCHEME_HASH_TAG = 1;
249privateconst SCHEME_HASH_STOP = 2;
250
252privateconst CHECK_KEY_TTL = self::TTL_YEAR;
254privateconst INTERIM_KEY_TTL = 2;
255
257privateconst LOCK_TTL = 10;
259privateconst RAMPUP_TTL = 30;
260
262privateconst TINY_NEGATIVE = -0.000001;
264privateconst TINY_POSITIVE = 0.000001;
265
267privateconst RECENT_SET_LOW_MS = 50;
269privateconst RECENT_SET_HIGH_MS = 100;
270
272privateconst GENERATION_HIGH_SEC = 0.2;
273
275privateconst PURGE_TIME = 0;
277privateconst PURGE_HOLDOFF = 1;
278
280privateconst VERSION = 1;
281
283publicconstKEY_VERSION ='version';
285publicconstKEY_AS_OF ='asOf';
287publicconstKEY_TTL ='ttl';
289publicconstKEY_CUR_TTL ='curTTL';
291publicconstKEY_TOMB_AS_OF ='tombAsOf';
293publicconstKEY_CHECK_AS_OF ='lastCKPurge';
294
296privateconst RES_VALUE = 0;
298privateconst RES_VERSION = 1;
300privateconst RES_AS_OF = 2;
302privateconst RES_TTL = 3;
304privateconst RES_TOMB_AS_OF = 4;
306privateconst RES_CHECK_AS_OF = 5;
308privateconst RES_TOUCH_AS_OF = 6;
310privateconst RES_CUR_TTL = 7;
311
313privateconst FLD_FORMAT_VERSION = 0;
315privateconst FLD_VALUE = 1;
317privateconst FLD_TTL = 2;
319privateconst FLD_TIME = 3;
321privateconst FLD_FLAGS = 4;
323privateconst FLD_VALUE_VERSION = 5;
324privateconst FLD_GENERATION_TIME = 6;
325
327privateconst TYPE_VALUE ='v';
329privateconst TYPE_TIMESTAMP ='t';
331privateconst TYPE_MUTEX ='m';
333privateconst TYPE_INTERIM ='i';
334
336privateconst PURGE_VAL_PREFIX ='PURGED';
337
365publicfunction__construct( array $params ) {
366 $this->cache = $params['cache'];
367 $this->broadcastRoute = $params['broadcastRoutingPrefix'] ??null;
368 $this->epoch = $params['epoch'] ?? 0;
369if ( ( $params['coalesceScheme'] ??'' ) ==='hash_tag' ) {
370// https://redis.io/topics/cluster-spec
371// https://github.com/twitter/twemproxy/blob/v0.4.1/notes/recommendation.md#hash-tags
372// https://github.com/Netflix/dynomite/blob/v0.7.0/notes/recommendation.md#hash-tags
373 $this->coalesceScheme = self::SCHEME_HASH_TAG;
374 }else {
375// https://github.com/facebook/mcrouter/wiki/Key-syntax
376 $this->coalesceScheme = self::SCHEME_HASH_STOP;
377 }
378
379 $this->setLogger( $params['logger'] ??new NullLogger() );
380 $this->tracer = $params['tracer'] ??newNoopTracer();
381 $this->stats = $params['stats'] ?? StatsFactory::newNull();
382
383 $this->asyncHandler = $params['asyncHandler'] ??null;
384 $this->missLog = array_fill( 0, 10, ['', 0.0 ] );
385 }
386
387publicfunctionsetLogger( LoggerInterface$logger ): void {
388 $this->logger =$logger;
389 }
390
396publicstaticfunctionnewEmpty() {
397returnnewstatic( ['cache' =>newEmptyBagOStuff() ] );
398 }
399
455finalpublicfunctionget( $key, &$curTTL =null, array $checkKeys = [], &$info = [] ) {
456// Note that an undeclared variable passed as $info starts as null (not the default).
457// Also, if no $info parameter is provided, then it doesn't matter how it changes here.
458 $legacyInfo = ( $info !== self::PASS_BY_REF );
459
461 $span = $this->startOperationSpan( __FUNCTION__, $key, $checkKeys );
462
463 $now = $this->getCurrentTime();
464 $res = $this->fetchKeys( [ $key ], $checkKeys, $now )[$key];
465
466 $curTTL = $res[self::RES_CUR_TTL];
467 $info = $legacyInfo
468 ? $res[self::RES_AS_OF]
469 : [
470 self::KEY_VERSION => $res[self::RES_VERSION],
471 self::KEY_AS_OF => $res[self::RES_AS_OF],
472 self::KEY_TTL => $res[self::RES_TTL],
473 self::KEY_CUR_TTL => $res[self::RES_CUR_TTL],
474 self::KEY_TOMB_AS_OF => $res[self::RES_TOMB_AS_OF],
475 self::KEY_CHECK_AS_OF => $res[self::RES_CHECK_AS_OF]
476 ];
477
478if ( $curTTL ===null || $curTTL <= 0 ) {
479// Log the timestamp in case a corresponding set() call does not provide "walltime"
480 unset( $this->missLog[array_key_first( $this->missLog )] );
481 $this->missLog[] = [ $key, $this->getCurrentTime() ];
482 }
483
484return $res[self::RES_VALUE];
485 }
486
511finalpublicfunctiongetMulti(
512 array $keys,
513 &$curTTLs = [],
514 array $checkKeys = [],
515 &$info = []
516 ) {
517// Note that an undeclared variable passed as $info starts as null (not the default).
518// Also, if no $info parameter is provided, then it doesn't matter how it changes here.
519 $legacyInfo = ( $info !== self::PASS_BY_REF );
520
522 $span = $this->startOperationSpan( __FUNCTION__, $keys, $checkKeys );
523
524 $curTTLs = [];
525 $info = [];
526 $valuesByKey = [];
527
528 $now = $this->getCurrentTime();
529 $resByKey = $this->fetchKeys( $keys, $checkKeys, $now );
530foreach ( $resByKey as $key => $res ) {
531if ( $res[self::RES_VALUE] !==false ) {
532 $valuesByKey[$key] = $res[self::RES_VALUE];
533 }
534
535if ( $res[self::RES_CUR_TTL] !==null ) {
536 $curTTLs[$key] = $res[self::RES_CUR_TTL];
537 }
538 $info[$key] = $legacyInfo
539 ? $res[self::RES_AS_OF]
540 : [
541 self::KEY_VERSION => $res[self::RES_VERSION],
542 self::KEY_AS_OF => $res[self::RES_AS_OF],
543 self::KEY_TTL => $res[self::RES_TTL],
544 self::KEY_CUR_TTL => $res[self::RES_CUR_TTL],
545 self::KEY_TOMB_AS_OF => $res[self::RES_TOMB_AS_OF],
546 self::KEY_CHECK_AS_OF => $res[self::RES_CHECK_AS_OF]
547 ];
548 }
549
550return $valuesByKey;
551 }
552
568protectedfunctionfetchKeys( array $keys, array $checkKeys,float $now, $touchedCb =null ) {
569 $resByKey = [];
570
571// List of all sister keys that need to be fetched from cache
572 $allSisterKeys = [];
573// Order-corresponding value sister key list for the base key list ($keys)
574 $valueSisterKeys = [];
575// List of "check" sister keys to compare all value sister keys against
576 $checkSisterKeysForAll = [];
577// Map of (base key => additional "check" sister key(s) to compare against)
578 $checkSisterKeysByKey = [];
579
580foreach ( $keys as $key ) {
581 $sisterKey = $this->makeSisterKey( $key, self::TYPE_VALUE );
582 $allSisterKeys[] = $sisterKey;
583 $valueSisterKeys[] = $sisterKey;
584 }
585
586foreach ( $checkKeys as $i => $checkKeyOrKeyGroup ) {
587// Note: avoid array_merge() inside loop in case there are many keys
588if ( is_int( $i ) ) {
589// Single "check" key that applies to all base keys
590 $sisterKey = $this->makeSisterKey( $checkKeyOrKeyGroup, self::TYPE_TIMESTAMP );
591 $allSisterKeys[] = $sisterKey;
592 $checkSisterKeysForAll[] = $sisterKey;
593 }else {
594// List of "check" keys that apply to a specific base key
595foreach ( (array)$checkKeyOrKeyGroup as $checkKey ) {
596 $sisterKey = $this->makeSisterKey( $checkKey, self::TYPE_TIMESTAMP );
597 $allSisterKeys[] = $sisterKey;
598 $checkSisterKeysByKey[$i][] = $sisterKey;
599 }
600 }
601 }
602
603if ( $this->warmupCache ) {
604// Get the wrapped values of the sister keys from the warmup cache
605 $wrappedBySisterKey = $this->warmupCache;
606 $sisterKeysMissing = array_diff( $allSisterKeys, array_keys( $wrappedBySisterKey ) );
607if ( $sisterKeysMissing ) {
608 $this->warmupKeyMisses += count( $sisterKeysMissing );
609 $wrappedBySisterKey += $this->cache->getMulti( $sisterKeysMissing );
610 }
611 }else {
612// Fetch the wrapped values of the sister keys from the backend
613 $wrappedBySisterKey = $this->cache->getMulti( $allSisterKeys );
614 }
615
616// List of "check" sister key purge timestamps to compare all value sister keys against
617 $ckPurgesForAll = $this->processCheckKeys(
618 $checkSisterKeysForAll,
619 $wrappedBySisterKey,
620 $now
621 );
622// Map of (base key => extra "check" sister key purge timestamp(s) to compare against)
623 $ckPurgesByKey = [];
624foreach ( $checkSisterKeysByKey as $keyWithCheckKeys => $checkKeysForKey ) {
625 $ckPurgesByKey[$keyWithCheckKeys] = $this->processCheckKeys(
626 $checkKeysForKey,
627 $wrappedBySisterKey,
628 $now
629 );
630 }
631
632// Unwrap and validate any value found for each base key (under the value sister key)
633foreach (
634 array_map(null, $valueSisterKeys, $keys )
635 as [ $valueSisterKey, $key ]
636 ) {
637if ( array_key_exists( $valueSisterKey, $wrappedBySisterKey ) ) {
638// Key exists as either a live value or tombstone value
639 $wrapped = $wrappedBySisterKey[$valueSisterKey];
640 }else {
641// Key does not exist
642 $wrapped =false;
643 }
644
645 $res = $this->unwrap( $wrapped, $now );
646 $value = $res[self::RES_VALUE];
647
648foreach ( array_merge( $ckPurgesForAll, $ckPurgesByKey[$key] ?? [] ) as $ckPurge ) {
649 $res[self::RES_CHECK_AS_OF] = max(
650 $ckPurge[self::PURGE_TIME],
651 $res[self::RES_CHECK_AS_OF]
652 );
653// Timestamp marking the end of the hold-off period for this purge
654 $holdoffDeadline = $ckPurge[self::PURGE_TIME] + $ckPurge[self::PURGE_HOLDOFF];
655// Check if the value was generated during the hold-off period
656if ( $value !==false && $holdoffDeadline >= $res[self::RES_AS_OF] ) {
657// How long ago this value was purged by *this* "check" key
658 $ago = min( $ckPurge[self::PURGE_TIME] - $now, self::TINY_NEGATIVE );
659// How long ago this value was purged by *any* known "check" key
660 $res[self::RES_CUR_TTL] = min( $res[self::RES_CUR_TTL], $ago );
661 }
662 }
663
664if ( $touchedCb !==null && $value !==false ) {
665 $touched = $touchedCb( $value );
666if ( $touched !==null && $touched >= $res[self::RES_AS_OF] ) {
667 $res[self::RES_CUR_TTL] = min(
668 $res[self::RES_CUR_TTL],
669 $res[self::RES_AS_OF] - $touched,
670 self::TINY_NEGATIVE
671 );
672 }
673 }else {
674 $touched =null;
675 }
676
677 $res[self::RES_TOUCH_AS_OF] = max( $res[self::RES_TOUCH_AS_OF], $touched );
678
679 $resByKey[$key] = $res;
680 }
681
682return $resByKey;
683 }
684
691privatefunction processCheckKeys(
692 array $checkSisterKeys,
693 array $wrappedBySisterKey,
694float $now
695 ) {
696 $purges = [];
697
698foreach ( $checkSisterKeys as $timeKey ) {
699 $purge = isset( $wrappedBySisterKey[$timeKey] )
700 ? $this->parsePurgeValue( $wrappedBySisterKey[$timeKey] )
701 : null;
702
703if ( $purge ===null ) {
704// No holdoff when lazy creating a check key, use cache right away (T344191)
705 $wrapped = $this->makeCheckPurgeValue( $now, self::HOLDOFF_TTL_NONE, $purge );
706 $this->cache->add(
707 $timeKey,
708 $wrapped,
709 self::CHECK_KEY_TTL,
710 $this->cache::WRITE_BACKGROUND
711 );
712 }
713
714 $purges[] = $purge;
715 }
716
717return $purges;
718 }
719
803finalpublicfunctionset( $key, $value, $ttl = self::TTL_INDEFINITE, array $opts = [] ) {
805 $span = $this->startOperationSpan( __FUNCTION__, $key );
806
807 $keygroup = $this->determineKeyGroupForStats( $key );
808
809 $ok = $this->setMainValue(
810 $key,
811 $value,
812 $ttl,
813 $opts['version'] ??null,
814 $opts['walltime'] ??null,
815 $opts['lag'] ?? 0,
816 $opts['since'] ??null,
817 $opts['pending'] ??false,
818 $opts['lockTSE'] ?? self::TSE_NONE,
819 $opts['staleTTL'] ?? self::STALE_TTL_NONE,
820 $opts['segmentable'] ??false,
821 $opts['creating'] ??false
822 );
823
824 $this->stats->getCounter('wanobjectcache_set_total' )
825 ->setLabel('keygroup', $keygroup )
826 ->setLabel('result', ( $ok ?'ok' :'error' ) )
827 ->copyToStatsdAt("wanobjectcache.$keygroup.set." . ( $ok ?'ok' :'error' ) )
828 ->increment();
829
830return $ok;
831 }
832
848privatefunction setMainValue(
849 $key,
850 $value,
851 $ttl,
852 ?int $version,
853 ?float $walltime,
854 $dataReplicaLag,
855 $dataReadSince,
856bool $dataPendingCommit,
857int $lockTSE,
858int $staleTTL,
859bool $segmentable,
860bool $creating
861 ) {
862if ( $ttl < 0 ) {
863// not cacheable
864returntrue;
865 }
866
867 $now = $this->getCurrentTime();
868 $ttl = (int)$ttl;
869 $walltime ??= $this->timeSinceLoggedMiss( $key, $now );
870 $dataSnapshotLag = ( $dataReadSince !== null ) ? max( 0, $now - $dataReadSince ) : 0;
871 $dataCombinedLag = $dataReplicaLag + $dataSnapshotLag;
872
873// Forbid caching data that only exists within an uncommitted transaction. Also, lower
874// the TTL when the data has a "since" time so far in the past that a delete() tombstone,
875// made after that time, could have already expired (the key is no longer write-holed).
876// The mitigation TTL depends on whether this data lag is assumed to systemically effect
877// regeneration attempts in the near future. The TTL also reflects regeneration wall time.
878if ( $dataPendingCommit ) {
879// Case A: data comes from an uncommitted write transaction
880 $mitigated ='pending writes';
881// Data might never be committed; rely on a less problematic regeneration attempt
882 $mitigationTTL = self::TTL_UNCACHEABLE;
883 } elseif ( $dataSnapshotLag > self::MAX_READ_LAG ) {
884// Case B: high snapshot lag
885 $pregenSnapshotLag = ( $walltime !== null ) ? ( $dataSnapshotLag - $walltime ) : 0;
886if ( ( $pregenSnapshotLag + self::GENERATION_HIGH_SEC ) > self::MAX_READ_LAG ) {
887// Case B1: generation started when transaction duration was already long
888 $mitigated ='snapshot lag (late generation)';
889// Probably non-systemic; rely on a less problematic regeneration attempt
890 $mitigationTTL = self::TTL_UNCACHEABLE;
891 }else {
892// Case B2: slow generation made transaction duration long
893 $mitigated ='snapshot lag (high generation time)';
894// Probably systemic; use a low TTL to avoid stampedes/uncacheability
895 $mitigationTTL = self::TTL_LAGGED;
896 }
897 } elseif ( $dataReplicaLag ===false || $dataReplicaLag > self::MAX_READ_LAG ) {
898// Case C: low/medium snapshot lag with high replication lag
899 $mitigated ='replication lag';
900// Probably systemic; use a low TTL to avoid stampedes/uncacheability
901 $mitigationTTL = self::TTL_LAGGED;
902 } elseif ( $dataCombinedLag > self::MAX_READ_LAG ) {
903 $pregenCombinedLag = ( $walltime !== null ) ? ( $dataCombinedLag - $walltime ) : 0;
904// Case D: medium snapshot lag with medium replication lag
905if ( ( $pregenCombinedLag + self::GENERATION_HIGH_SEC ) > self::MAX_READ_LAG ) {
906// Case D1: generation started when read lag was too high
907 $mitigated ='read lag (late generation)';
908// Probably non-systemic; rely on a less problematic regeneration attempt
909 $mitigationTTL = self::TTL_UNCACHEABLE;
910 }else {
911// Case D2: slow generation made read lag too high
912 $mitigated ='read lag (high generation time)';
913// Probably systemic; use a low TTL to avoid stampedes/uncacheability
914 $mitigationTTL = self::TTL_LAGGED;
915 }
916 }else {
917// Case E: new value generated with recent data
918 $mitigated =null;
919// Nothing to mitigate
920 $mitigationTTL =null;
921 }
922
923if ( $mitigationTTL === self::TTL_UNCACHEABLE ) {
924 $this->logger->warning(
925"Rejected set() for {cachekey} due to $mitigated.",
926 [
927'cachekey' => $key,
928'lag' => $dataReplicaLag,
929'age' => $dataSnapshotLag,
930'walltime' => $walltime
931 ]
932 );
933
934// no-op the write for being unsafe
935returntrue;
936 }
937
938// TTL to use in staleness checks (does not effect persistence layer TTL)
939 $logicalTTL =null;
940
941if ( $mitigationTTL !==null ) {
942// New value was generated from data that is old enough to be risky
943if ( $lockTSE >= 0 ) {
944// Persist the value as long as normal, but make it count as stale sooner
945 $logicalTTL = min( $ttl ?: INF, $mitigationTTL );
946 }else {
947// Persist the value for a shorter duration
948 $ttl = min( $ttl ?: INF, $mitigationTTL );
949 }
950
951 $this->logger->warning(
952"Lowered set() TTL for {cachekey} due to $mitigated.",
953 [
954'cachekey' => $key,
955'lag' => $dataReplicaLag,
956'age' => $dataSnapshotLag,
957'walltime' => $walltime
958 ]
959 );
960 }
961
962// Wrap that value with time/TTL/version metadata
963 $wrapped = $this->wrap( $value, $logicalTTL ?: $ttl, $version, $now );
964 $storeTTL = $ttl + $staleTTL;
965
966 $flags = $this->cache::WRITE_BACKGROUND;
967if ( $segmentable ) {
968 $flags |= $this->cache::WRITE_ALLOW_SEGMENTS;
969 }
970
971if ( $creating ) {
972 $ok = $this->cache->add(
973 $this->makeSisterKey( $key, self::TYPE_VALUE ),
974 $wrapped,
975 $storeTTL,
976 $flags
977 );
978 }else {
979 $ok = $this->cache->merge(
980 $this->makeSisterKey( $key, self::TYPE_VALUE ),
981staticfunction ( $cache, $key, $cWrapped ) use ( $wrapped ) {
982// A string value means that it is a tombstone; do nothing in that case
983return ( is_string( $cWrapped ) ) ?false : $wrapped;
984 },
985 $storeTTL,
986 $this->cache::MAX_CONFLICTS_ONE,
987 $flags
988 );
989 }
990
991return $ok;
992 }
993
1056finalpublicfunctiondelete( $key, $ttl = self::HOLDOFF_TTL ) {
1058 $span = $this->startOperationSpan( __FUNCTION__, $key );
1059
1060// Purge values must be stored under the value key so that WANObjectCache::set()
1061// can atomically merge values without accidentally undoing a recent purge and thus
1062// violating the holdoff TTL restriction.
1063 $valueSisterKey = $this->makeSisterKey( $key, self::TYPE_VALUE );
1064
1065if ( $ttl <= 0 ) {
1066// A client or cache cleanup script is requesting a cache purge, so there is no
1067// volatility period due to replica DB lag. Any recent change to an entity cached
1068// in this key should have triggered an appropriate purge event.
1069 $ok = $this->cache->delete( $this->getRouteKey( $valueSisterKey ), $this->cache::WRITE_BACKGROUND );
1070 }else {
1071// A cacheable entity recently changed, so there might be a volatility period due
1072// to replica DB lag. Clients usually expect their actions to be reflected in any
1073// of their subsequent web request. This is attainable if (a) purge relay lag is
1074// lower than the time it takes for subsequent request by the client to arrive,
1075// and, (b) DB replica queries have "read-your-writes" consistency due to DB lag
1076// mitigation systems.
1077 $now = $this->getCurrentTime();
1078// Set the key to the purge value in all datacenters
1079 $purge = self::PURGE_VAL_PREFIX .':' . (int)$now;
1080 $ok = $this->cache->set(
1081 $this->getRouteKey( $valueSisterKey ),
1082 $purge,
1083 $ttl,
1084 $this->cache::WRITE_BACKGROUND
1085 );
1086 }
1087
1088 $keygroup = $this->determineKeyGroupForStats( $key );
1089
1090 $this->stats->getCounter('wanobjectcache_delete_total' )
1091 ->setLabel('keygroup', $keygroup )
1092 ->setLabel('result', ( $ok ?'ok' :'error' ) )
1093 ->copyToStatsdAt("wanobjectcache.$keygroup.delete." . ( $ok ?'ok' :'error' ) )
1094 ->increment();
1095
1096return $ok;
1097 }
1098
1118finalpublicfunctiongetCheckKeyTime( $key ) {
1120 $span = $this->startOperationSpan( __FUNCTION__, $key );
1121
1122return $this->getMultiCheckKeyTime( [ $key ] )[$key];
1123 }
1124
1186finalpublicfunctiongetMultiCheckKeyTime( array $keys ) {
1188 $span = $this->startOperationSpan( __FUNCTION__, $keys );
1189
1190 $checkSisterKeysByKey = [];
1191foreach ( $keys as $key ) {
1192 $checkSisterKeysByKey[$key] = $this->makeSisterKey( $key, self::TYPE_TIMESTAMP );
1193 }
1194
1195 $wrappedBySisterKey = $this->cache->getMulti( $checkSisterKeysByKey );
1196 $wrappedBySisterKey += array_fill_keys( $checkSisterKeysByKey,false );
1197
1198 $now = $this->getCurrentTime();
1199 $times = [];
1200foreach ( $checkSisterKeysByKey as $key => $checkSisterKey ) {
1201 $purge = $this->parsePurgeValue( $wrappedBySisterKey[$checkSisterKey] );
1202if ( $purge ===null ) {
1203 $wrapped = $this->makeCheckPurgeValue( $now, self::HOLDOFF_TTL_NONE, $purge );
1204 $this->cache->add(
1205 $checkSisterKey,
1206 $wrapped,
1207 self::CHECK_KEY_TTL,
1208 $this->cache::WRITE_BACKGROUND
1209 );
1210 }
1211
1212 $times[$key] = $purge[self::PURGE_TIME];
1213 }
1214
1215return $times;
1216 }
1217
1251publicfunctiontouchCheckKey( $key, $holdoff = self::HOLDOFF_TTL ) {
1253 $span = $this->startOperationSpan( __FUNCTION__, $key );
1254
1255 $checkSisterKey = $this->makeSisterKey( $key, self::TYPE_TIMESTAMP );
1256
1257 $now = $this->getCurrentTime();
1258 $purge = $this->makeCheckPurgeValue( $now, $holdoff );
1259 $ok = $this->cache->set(
1260 $this->getRouteKey( $checkSisterKey ),
1261 $purge,
1262 self::CHECK_KEY_TTL,
1263 $this->cache::WRITE_BACKGROUND
1264 );
1265
1266 $keygroup = $this->determineKeyGroupForStats( $key );
1267
1268 $this->stats->getCounter('wanobjectcache_check_total' )
1269 ->setLabel('keygroup', $keygroup )
1270 ->setLabel('result', ( $ok ?'ok' :'error' ) )
1271 ->copyToStatsdAt("wanobjectcache.$keygroup.ck_touch." . ( $ok ?'ok' :'error' ) )
1272 ->increment();
1273
1274return $ok;
1275 }
1276
1304publicfunctionresetCheckKey( $key ) {
1306 $span = $this->startOperationSpan( __FUNCTION__, $key );
1307
1308 $checkSisterKey = $this->makeSisterKey( $key, self::TYPE_TIMESTAMP );
1309 $ok = $this->cache->delete( $this->getRouteKey( $checkSisterKey ), $this->cache::WRITE_BACKGROUND );
1310
1311 $keygroup = $this->determineKeyGroupForStats( $key );
1312
1313 $this->stats->getCounter('wanobjectcache_reset_total' )
1314 ->setLabel('keygroup', $keygroup )
1315 ->setLabel('result', ( $ok ?'ok' :'error' ) )
1316 ->copyToStatsdAt("wanobjectcache.$keygroup.ck_reset." . ( $ok ?'ok' :'error' ) )
1317 ->increment();
1318
1319return $ok;
1320 }
1321
1623finalpublicfunctiongetWithSetCallback(
1624 $key, $ttl, $callback, array $opts = [], array $cbParams = []
1625 ) {
1627 $span = $this->startOperationSpan( __FUNCTION__, $key );
1628
1629 $version = $opts['version'] ??null;
1630 $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
1631 $pCache = ( $pcTTL >= 0 )
1632 ? $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY )
1633 :null;
1634
1635// Use the process cache if requested as long as no outer cache callback is running.
1636// Nested callback process cache use is not lag-safe with regard to HOLDOFF_TTL since
1637// process cached values are more lagged than persistent ones as they are not purged.
1638if ( $pCache && $this->callbackDepth == 0 ) {
1639 $cached = $pCache->get( $key, $pcTTL,false );
1640if ( $cached !==false ) {
1641 $this->logger->debug("getWithSetCallback($key): process cache hit" );
1642return $cached;
1643 }
1644 }
1645
1646 [ $value, $valueVersion, $curAsOf ] = $this->fetchOrRegenerate( $key, $ttl, $callback, $opts, $cbParams );
1647if ( $valueVersion !== $version ) {
1648// Current value has a different version; use the variant key for this version.
1649// Regenerate the variant value if it is not newer than the main value at $key
1650// so that purges to the main key propagate to the variant value.
1651 $this->logger->debug("getWithSetCallback($key): using variant key" );
1652 [ $value ] = $this->fetchOrRegenerate(
1653 $this->makeGlobalKey('WANCache-key-variant', md5( $key ), (string)$version ),
1654 $ttl,
1655 $callback,
1656 ['version' =>null,'minAsOf' => $curAsOf ] + $opts,
1657 $cbParams
1658 );
1659 }
1660
1661// Update the process cache if enabled
1662if ( $pCache && $value !==false ) {
1663 $pCache->set( $key, $value );
1664 }
1665
1666return $value;
1667 }
1668
1685privatefunction fetchOrRegenerate( $key, $ttl, $callback, array $opts, array $cbParams ) {
1686 $checkKeys = $opts['checkKeys'] ?? [];
1687 $graceTTL = $opts['graceTTL'] ?? self::GRACE_TTL_NONE;
1688 $minAsOf = $opts['minAsOf'] ?? self::MIN_TIMESTAMP_NONE;
1689 $hotTTR = $opts['hotTTR'] ?? self::HOT_TTR;
1690 $lowTTL = $opts['lowTTL'] ?? min( self::LOW_TTL, $ttl );
1691 $ageNew = $opts['ageNew'] ?? self::AGE_NEW;
1692 $touchedCb = $opts['touchedCallback'] ??null;
1693 $startTime = $this->getCurrentTime();
1694
1695 $keygroup = $this->determineKeyGroupForStats( $key );
1696
1697// Get the current key value and its metadata
1698 $curState = $this->fetchKeys( [ $key ], $checkKeys, $startTime, $touchedCb )[$key];
1699 $curValue = $curState[self::RES_VALUE];
1700
1701// Use the cached value if it exists and is not due for synchronous regeneration
1702if ( $this->isAcceptablyFreshValue( $curState, $graceTTL, $minAsOf ) ) {
1703if ( !$this->isLotteryRefreshDue( $curState, $lowTTL, $ageNew, $hotTTR, $startTime ) ) {
1704 $this->stats->getTiming('wanobjectcache_getwithset_seconds' )
1705 ->setLabel('keygroup', $keygroup )
1706 ->setLabel('result','hit' )
1707 ->setLabel('reason','good' )
1708 ->copyToStatsdAt("wanobjectcache.$keygroup.hit.good" )
1709 ->observe( 1e3 * ( $this->getCurrentTime() - $startTime ) );
1710
1711return [ $curValue, $curState[self::RES_VERSION], $curState[self::RES_AS_OF] ];
1712 } elseif ( $this->scheduleAsyncRefresh( $key, $ttl, $callback, $opts, $cbParams ) ) {
1713 $this->logger->debug("fetchOrRegenerate($key): hit with async refresh" );
1714
1715 $this->stats->getTiming('wanobjectcache_getwithset_seconds' )
1716 ->setLabel('keygroup', $keygroup )
1717 ->setLabel('result','hit' )
1718 ->setLabel('reason','refresh' )
1719 ->copyToStatsdAt("wanobjectcache.$keygroup.hit.refresh" )
1720 ->observe( 1e3 * ( $this->getCurrentTime() - $startTime ) );
1721
1722return [ $curValue, $curState[self::RES_VERSION], $curState[self::RES_AS_OF] ];
1723 }else {
1724 $this->logger->debug("fetchOrRegenerate($key): hit with sync refresh" );
1725 }
1726 }
1727
1728 $isKeyTombstoned = ( $curState[self::RES_TOMB_AS_OF] !== null );
1729// Use the interim key as a temporary alternative if the key is tombstoned
1730if ( $isKeyTombstoned ) {
1731 $volState = $this->getInterimValue( $key, $minAsOf, $startTime, $touchedCb );
1732 $volValue = $volState[self::RES_VALUE];
1733 }else {
1734 $volState = $curState;
1735 $volValue = $curValue;
1736 }
1737
1738// During the volatile "hold-off" period that follows a purge of the key, the value
1739// will be regenerated many times if frequently accessed. This is done to mitigate
1740// the effects of backend replication lag as soon as possible. However, throttle the
1741// overhead of locking and regeneration by reusing values recently written to cache
1742// tens of milliseconds ago. Verify the "as of" time against the last purge event.
1743 $lastPurgeTime = max(
1744// RES_TOUCH_AS_OF depends on the value (possibly from the interim key)
1745 $volState[self::RES_TOUCH_AS_OF],
1746 $curState[self::RES_TOMB_AS_OF],
1747 $curState[self::RES_CHECK_AS_OF]
1748 );
1749 $safeMinAsOf = max( $minAsOf, $lastPurgeTime + self::TINY_POSITIVE );
1750
1751if ( $volState[self::RES_VALUE] ===false || $volState[self::RES_AS_OF] < $safeMinAsOf ) {
1752 $isExtremelyNewValue =false;
1753 }else {
1754 $age = $startTime - $volState[self::RES_AS_OF];
1755 $isExtremelyNewValue = ( $age < mt_rand( self::RECENT_SET_LOW_MS, self::RECENT_SET_HIGH_MS ) / 1e3 );
1756 }
1757if ( $isExtremelyNewValue ) {
1758 $this->logger->debug("fetchOrRegenerate($key): volatile hit" );
1759
1760 $this->stats->getTiming('wanobjectcache_getwithset_seconds' )
1761 ->setLabel('keygroup', $keygroup )
1762 ->setLabel('result','hit' )
1763 ->setLabel('reason','volatile' )
1764 ->copyToStatsdAt("wanobjectcache.$keygroup.hit.volatile" )
1765 ->observe( 1e3 * ( $this->getCurrentTime() - $startTime ) );
1766
1767return [ $volValue, $volState[self::RES_VERSION], $curState[self::RES_AS_OF] ];
1768 }
1769
1770 $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
1771 $busyValue = $opts['busyValue'] ??null;
1772 $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
1773 $segmentable = $opts['segmentable'] ??false;
1774 $version = $opts['version'] ??null;
1775
1776// Determine whether one thread per datacenter should handle regeneration at a time
1777 $useRegenerationLock =
1778// Note that since tombstones no-op set(), $lockTSE and $curTTL cannot be used to
1779// deduce the key hotness because |$curTTL| will always keep increasing until the
1780// tombstone expires or is overwritten by a new tombstone. Also, even if $lockTSE
1781// is not set, constant regeneration of a key for the tombstone lifetime might be
1782// very expensive. Assume tombstoned keys are possibly hot in order to reduce
1783// the risk of high regeneration load after the delete() method is called.
1784 $isKeyTombstoned ||
1785// Assume a key is hot if requested soon ($lockTSE seconds) after purge.
1786// This avoids stampedes when timestamps from $checkKeys/$touchedCb bump.
1787 (
1788 $curState[self::RES_CUR_TTL] !==null &&
1789 $curState[self::RES_CUR_TTL] <= 0 &&
1790 abs( $curState[self::RES_CUR_TTL] ) <= $lockTSE
1791 ) ||
1792// Assume a key is hot if there is no value and a busy fallback is given.
1793// This avoids stampedes on eviction or preemptive regeneration taking too long.
1794 ( $busyValue !==null && $volValue ===false );
1795
1796// If a regeneration lock is required, threads that do not get the lock will try to use
1797// the stale value, the interim value, or the $busyValue placeholder, in that order. If
1798// none of those are set then all threads will bypass the lock and regenerate the value.
1799 $mutexKey = $this->makeSisterKey( $key, self::TYPE_MUTEX );
1800// Note that locking is not bypassed due to I/O errors; this avoids stampedes
1801 $hasLock = $useRegenerationLock && $this->cache->add( $mutexKey, 1, self::LOCK_TTL );
1802if ( $useRegenerationLock && !$hasLock ) {
1803// Determine if there is stale or volatile cached value that is still usable
1804// @phan-suppress-next-line PhanTypeMismatchArgumentNullable False positive
1805if ( $this->isValid( $volValue, $volState[self::RES_AS_OF], $minAsOf ) ) {
1806 $this->logger->debug("fetchOrRegenerate($key): returning stale value" );
1807
1808 $this->stats->getTiming('wanobjectcache_getwithset_seconds' )
1809 ->setLabel('keygroup', $keygroup )
1810 ->setLabel('result','hit' )
1811 ->setLabel('reason','stale' )
1812 ->copyToStatsdAt("wanobjectcache.$keygroup.hit.stale" )
1813 ->observe( 1e3 * ( $this->getCurrentTime() - $startTime ) );
1814
1815return [ $volValue, $volState[self::RES_VERSION], $curState[self::RES_AS_OF] ];
1816 } elseif ( $busyValue !==null ) {
1817 $miss = is_infinite( $minAsOf ) ?'renew' :'miss';
1818 $this->logger->debug("fetchOrRegenerate($key): busy $miss" );
1819
1820 $this->stats->getTiming('wanobjectcache_getwithset_seconds' )
1821 ->setLabel('keygroup', $keygroup )
1822 ->setLabel('result', $miss )
1823 ->setLabel('reason','busy' )
1824 ->copyToStatsdAt("wanobjectcache.$keygroup.$miss.busy" )
1825 ->observe( 1e3 * ( $this->getCurrentTime() - $startTime ) );
1826
1827 $placeholderValue = ( $busyValue instanceof Closure ) ? $busyValue() : $busyValue;
1828
1829return [ $placeholderValue, $version, $curState[self::RES_AS_OF] ];
1830 }
1831 }
1832
1833// Generate the new value given any prior value with a matching version
1834 $setOpts = [];
1835 $preCallbackTime = $this->getCurrentTime();
1836 ++$this->callbackDepth;
1837// https://github.com/phan/phan/issues/4419
1839 $value =null;
1840try {
1841 $value = $callback(
1842 ( $curState[self::RES_VERSION] === $version ) ? $curValue : false,
1843 $ttl,
1844 $setOpts,
1845 ( $curState[self::RES_VERSION] === $version ) ? $curState[self::RES_AS_OF] : null,
1846 $cbParams
1847 );
1848 }finally {
1849 --$this->callbackDepth;
1850 }
1851 $postCallbackTime = $this->getCurrentTime();
1852
1853// How long it took to generate the value
1854 $walltime = max( $postCallbackTime - $preCallbackTime, 0.0 );
1855
1856 $this->stats->getTiming('wanobjectcache_regen_seconds' )
1857 ->setLabel('keygroup', $keygroup )
1858 ->copyToStatsdAt("wanobjectcache.$keygroup.regen_walltime" )
1859 ->observe( 1e3 * $walltime );
1860
1861// Attempt to save the newly generated value if applicable
1862if (
1863// Callback yielded a cacheable value
1864 ( $value !==false && $ttl >= 0 ) &&
1865// Current thread was not raced out of a regeneration lock or key is tombstoned
1866 ( !$useRegenerationLock || $hasLock || $isKeyTombstoned )
1867 ) {
1868// If the key is write-holed then use the (volatile) interim key as an alternative
1869if ( $isKeyTombstoned ) {
1870 $this->setInterimValue(
1871 $key,
1872 $value,
1873 $lockTSE,
1874 $version,
1875 $segmentable
1876 );
1877 }else {
1878 $this->setMainValue(
1879 $key,
1880 $value,
1881 $ttl,
1882 $version,
1883 $walltime,
1884// @phan-suppress-next-line PhanCoalescingAlwaysNull
1885 $setOpts['lag'] ?? 0,
1886// @phan-suppress-next-line PhanCoalescingAlwaysNull
1887 $setOpts['since'] ?? $preCallbackTime,
1888// @phan-suppress-next-line PhanCoalescingAlwaysNull
1889 $setOpts['pending'] ??false,
1890 $lockTSE,
1891 $staleTTL,
1892 $segmentable,
1893 ( $curValue ===false )
1894 );
1895 }
1896 }
1897
1898if ( $hasLock ) {
1899 $this->cache->delete( $mutexKey, $this->cache::WRITE_BACKGROUND );
1900 }
1901
1902 $miss = is_infinite( $minAsOf ) ?'renew' :'miss';
1903 $this->logger->debug("fetchOrRegenerate($key): $miss, new value computed" );
1904
1905 $this->stats->getTiming('wanobjectcache_getwithset_seconds' )
1906 ->setLabel('keygroup', $keygroup )
1907 ->setLabel('result', $miss )
1908 ->setLabel('reason','compute' )
1909 ->copyToStatsdAt("wanobjectcache.$keygroup.$miss.compute" )
1910 ->observe( 1e3 * ( $this->getCurrentTime() - $startTime ) );
1911
1912return [ $value, $version, $curState[self::RES_AS_OF] ];
1913 }
1914
1924privatefunction makeSisterKey(string $baseKey,string $typeChar ) {
1925if ( $this->coalesceScheme === self::SCHEME_HASH_STOP ) {
1926// Key style: "WANCache:<base key>|#|<character>"
1927 $sisterKey ='WANCache:' . $baseKey .'|#|' . $typeChar;
1928 }else {
1929// Key style: "WANCache:{<base key>}:<character>"
1930 $sisterKey ='WANCache:{' . $baseKey .'}:' . $typeChar;
1931 }
1932return $sisterKey;
1933 }
1934
1944privatefunction getInterimValue( $key, $minAsOf, $now, $touchedCb ) {
1945if ( $this->useInterimHoldOffCaching ) {
1946 $interimSisterKey = $this->makeSisterKey( $key, self::TYPE_INTERIM );
1947 $wrapped = $this->cache->get( $interimSisterKey );
1948 $res = $this->unwrap( $wrapped, $now );
1949if ( $res[self::RES_VALUE] !==false && $res[self::RES_AS_OF] >= $minAsOf ) {
1950if ( $touchedCb !==null ) {
1951// Update "last purge time" since the $touchedCb timestamp depends on $value
1952// Get the new "touched timestamp", accounting for callback-checked dependencies
1953 $res[self::RES_TOUCH_AS_OF] = max(
1954 $touchedCb( $res[self::RES_VALUE] ),
1955 $res[self::RES_TOUCH_AS_OF]
1956 );
1957 }
1958
1959return $res;
1960 }
1961 }
1962
1963return $this->unwrap(false, $now );
1964 }
1965
1974privatefunction setInterimValue(
1975 $key,
1976 $value,
1977 $ttl,
1978 ?int $version,
1979bool $segmentable
1980 ) {
1981 $now = $this->getCurrentTime();
1982 $ttl = max( self::INTERIM_KEY_TTL, (int)$ttl );
1983
1984// Wrap that value with time/TTL/version metadata
1985 $wrapped = $this->wrap( $value, $ttl, $version, $now );
1986
1987 $flags = $this->cache::WRITE_BACKGROUND;
1988if ( $segmentable ) {
1989 $flags |= $this->cache::WRITE_ALLOW_SEGMENTS;
1990 }
1991
1992return $this->cache->set(
1993 $this->makeSisterKey( $key, self::TYPE_INTERIM ),
1994 $wrapped,
1995 $ttl,
1996 $flags
1997 );
1998 }
1999
2065finalpublicfunctiongetMultiWithSetCallback(
2066 ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
2067 ) {
2068 $span = $this->startOperationSpan( __FUNCTION__,'' );
2069if ( $span->getContext()->isSampled() ) {
2070 $span->setAttributes( [
2071'org.wikimedia.wancache.multi_count' => $keyedIds->count(),
2072'org.wikimedia.wancache.ttl' => $ttl,
2073 ] );
2074 }
2075// Batch load required keys into the in-process warmup cache
2076 $this->warmupCache = $this->fetchWrappedValuesForWarmupCache(
2077 $this->getNonProcessCachedMultiKeys( $keyedIds, $opts ),
2078 $opts['checkKeys'] ?? []
2079 );
2080 $this->warmupKeyMisses = 0;
2081
2082// The required callback signature includes $id as the first argument for convenience
2083// to distinguish different items. To reuse the code in getWithSetCallback(), wrap the
2084// callback with a proxy callback that has the standard getWithSetCallback() signature.
2085// This is defined only once per batch to avoid closure creation overhead.
2086 $proxyCb =staticfunction ( $oldValue, &$ttl, &$setOpts, $oldAsOf, $params )
2087 use ( $callback )
2088 {
2089return $callback( $params['id'], $oldValue, $ttl, $setOpts, $oldAsOf );
2090 };
2091
2092// Get the order-preserved result map using the warm-up cache
2093 $values = [];
2094foreach ( $keyedIds as $key => $id ) {
2095 $values[$key] = $this->getWithSetCallback(
2096 $key,
2097 $ttl,
2098 $proxyCb,
2099 $opts,
2100 ['id' => $id ]
2101 );
2102 }
2103
2104 $this->warmupCache = [];
2105
2106return $values;
2107 }
2108
2176 ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
2177 ) {
2178 $span = $this->startOperationSpan( __FUNCTION__,'' );
2179if ( $span->getContext()->isSampled() ) {
2180 $span->setAttributes( [
2181'org.wikimedia.wancache.multi_count' => $keyedIds->count(),
2182'org.wikimedia.wancache.ttl' => $ttl,
2183 ] );
2184 }
2185 $checkKeys = $opts['checkKeys'] ?? [];// TODO: ???
2186 $minAsOf = $opts['minAsOf'] ?? self::MIN_TIMESTAMP_NONE;
2187
2188// unset incompatible keys
2189 unset( $opts['lockTSE'] );
2190 unset( $opts['busyValue'] );
2191
2192// Batch load required keys into the in-process warmup cache
2193 $keysByIdGet = $this->getNonProcessCachedMultiKeys( $keyedIds, $opts );
2194 $this->warmupCache = $this->fetchWrappedValuesForWarmupCache( $keysByIdGet, $checkKeys );
2195 $this->warmupKeyMisses = 0;
2196
2197// IDs of entities known to be in need of generation
2198 $idsRegen = [];
2199
2200// Find out which keys are missing/deleted/stale
2201 $now = $this->getCurrentTime();
2202 $resByKey = $this->fetchKeys( $keysByIdGet, $checkKeys, $now );
2203foreach ( $keysByIdGet as $id => $key ) {
2204 $res = $resByKey[$key];
2205if (
2206 $res[self::RES_VALUE] ===false ||
2207 $res[self::RES_CUR_TTL] < 0 ||
2208 $res[self::RES_AS_OF] < $minAsOf
2209 ) {
2210 $idsRegen[] = $id;
2211 }
2212 }
2213
2214// Run the callback to populate the generation value map for all required IDs
2215 $newSetOpts = [];
2216 $newTTLsById = array_fill_keys( $idsRegen, $ttl );
2217 $newValsById = $idsRegen ? $callback( $idsRegen, $newTTLsById, $newSetOpts ) : [];
2218
2219 $method = __METHOD__;
2220// The required callback signature includes $id as the first argument for convenience
2221// to distinguish different items. To reuse the code in getWithSetCallback(), wrap the
2222// callback with a proxy callback that has the standard getWithSetCallback() signature.
2223// This is defined only once per batch to avoid closure creation overhead.
2224 $proxyCb =function ( $oldValue, &$ttl, &$setOpts, $oldAsOf, $params )
2225 use ( $callback, $newValsById, $newTTLsById, $newSetOpts, $method )
2226 {
2227 $id = $params['id'];
2228
2229if ( array_key_exists( $id, $newValsById ) ) {
2230// Value was already regenerated as expected, so use the value in $newValsById
2231 $newValue = $newValsById[$id];
2232 $ttl = $newTTLsById[$id];
2233 $setOpts = $newSetOpts;
2234 }else {
2235// Pre-emptive/popularity refresh and version mismatch cases are not detected
2236// above and thus $newValsById has no entry. Run $callback on this single entity.
2237 $ttls = [ $id => $ttl ];
2238 $result = $callback( [ $id ], $ttls, $setOpts );
2239if ( !isset( $result[$id] ) ) {
2240// T303092
2241 $this->logger->warning(
2242 $method .' failed due to {id} not set in result {result}', [
2243'id' => $id,
2244'result' => json_encode( $result )
2245 ] );
2246 }
2247 $newValue = $result[$id];
2248 $ttl = $ttls[$id];
2249 }
2250
2251return $newValue;
2252 };
2253
2254// Get the order-preserved result map using the warm-up cache
2255 $values = [];
2256foreach ( $keyedIds as $key => $id ) {
2257 $values[$key] = $this->getWithSetCallback(
2258 $key,
2259 $ttl,
2260 $proxyCb,
2261 $opts,
2262 ['id' => $id ]
2263 );
2264 }
2265
2266 $this->warmupCache = [];
2267
2268return $values;
2269 }
2270
2278publicfunctionmakeGlobalKey( $keygroup, ...$components ) {
2279return $this->cache->makeGlobalKey( $keygroup, ...$components );
2280 }
2281
2289publicfunctionmakeKey( $keygroup, ...$components ) {
2290return $this->cache->makeKey( $keygroup, ...$components );
2291 }
2292
2334finalpublicfunctionmakeMultiKeys( array $ids, $keyCallback ) {
2335 $idByKey = [];
2336foreach ( $ids as $id ) {
2337 $key = $keyCallback( $id, $this );
2338// Edge case: ignore key collisions due to duplicate $ids like "42" and 42
2339if ( !isset( $idByKey[$key] ) ) {
2340 $idByKey[$key] = $id;
2341 } elseif ( (string)$id !== (string)$idByKey[$key] ) {
2342thrownew UnexpectedValueException(
2343"Cache key collision; IDs ('$id','{$idByKey[$key]}') map to '$key'"
2344 );
2345 }
2346 }
2347
2348returnnew ArrayIterator( $idByKey );
2349 }
2350
2386finalpublicfunctionmultiRemap( array $ids, array $res ) {
2387if ( count( $ids ) !== count( $res ) ) {
2388// If makeMultiKeys() is called on a list of non-unique IDs, then the resulting
2389// ArrayIterator will have less entries due to "first appearance" de-duplication
2390 $ids = array_keys( array_fill_keys( $ids,true ) );
2391if ( count( $ids ) !== count( $res ) ) {
2392thrownew UnexpectedValueException("Multi-key result does not match ID list" );
2393 }
2394 }
2395
2396return array_combine( $ids, $res );
2397 }
2398
2405publicfunctionwatchErrors() {
2406return $this->cache->watchErrors();
2407 }
2408
2426finalpublicfunctiongetLastError( $watchPoint = 0 ) {
2427 $code = $this->cache->getLastError( $watchPoint );
2428switch ( $code ) {
2435default:
2437 }
2438 }
2439
2445publicfunctionclearProcessCache() {
2446 $this->processCaches = [];
2447 }
2448
2469finalpublicfunctionuseInterimHoldOffCaching( $enabled ) {
2470 $this->useInterimHoldOffCaching = $enabled;
2471 }
2472
2478publicfunctiongetQoS( $flag ) {
2479return $this->cache->getQoS( $flag );
2480 }
2481
2545publicfunctionadaptiveTTL( $mtime, $maxTTL, $minTTL = 30, $factor = 0.2 ) {
2546// handle fractional seconds and string integers
2547 $mtime = (int)$mtime;
2548if ( $mtime <= 0 ) {
2549// no last-modified time provided
2550return $minTTL;
2551 }
2552
2553 $age = (int)$this->getCurrentTime() - $mtime;
2554
2555return (int)min( $maxTTL, max( $minTTL, $factor * $age ) );
2556 }
2557
2563finalpublicfunctiongetWarmupKeyMisses() {
2564// Number of misses in $this->warmupCache during the last call to certain methods
2565return $this->warmupKeyMisses;
2566 }
2567
2572protectedfunctiongetRouteKey(string $sisterKey ) {
2573if ( $this->broadcastRoute !==null ) {
2574if ( $sisterKey[0] ==='/' ) {
2575thrownew RuntimeException("Sister key '$sisterKey' already contains a route." );
2576 }
2577return $this->broadcastRoute . $sisterKey;
2578 }
2579return $sisterKey;
2580 }
2581
2593privatefunction scheduleAsyncRefresh( $key, $ttl, $callback, array $opts, array $cbParams ) {
2594if ( !$this->asyncHandler ) {
2595returnfalse;
2596 }
2597// Update the cache value later, such during post-send of an HTTP request. This forces
2598// cache regeneration by setting "minAsOf" to infinity, meaning that no existing value
2599// is considered valid. Furthermore, note that preemptive regeneration is not applicable
2600// to invalid values, so there is no risk of infinite preemptive regeneration loops.
2601 $func = $this->asyncHandler;
2602 $func(function () use ( $key, $ttl, $callback, $opts, $cbParams ) {
2603 $opts['minAsOf'] = INF;
2604try {
2605 $this->fetchOrRegenerate( $key, $ttl, $callback, $opts, $cbParams );
2606 }catch ( Exception $e ) {
2607// Log some context for easier debugging
2608 $this->logger->error('Async refresh failed for {key}', [
2609'key' => $key,
2610'ttl' => $ttl,
2611'exception' => $e
2612 ] );
2613throw $e;
2614 }
2615 } );
2616
2617returntrue;
2618 }
2619
2628privatefunction isAcceptablyFreshValue( $res, $graceTTL, $minAsOf ) {
2629if ( !$this->isValid( $res[self::RES_VALUE], $res[self::RES_AS_OF], $minAsOf ) ) {
2630// Value does not exists or is too old
2631returnfalse;
2632 }
2633
2634 $curTTL = $res[self::RES_CUR_TTL];
2635if ( $curTTL > 0 ) {
2636// Value is definitely still fresh
2637returntrue;
2638 }
2639
2640// Remaining seconds during which this stale value can be used
2641 $curGraceTTL = $graceTTL + $curTTL;
2642
2643return ( $curGraceTTL > 0 )
2644// Chance of using the value decreases as $curTTL goes from 0 to -$graceTTL
2645 ? !$this->worthRefreshExpiring( $curGraceTTL, $graceTTL, $graceTTL )
2646// Value is too stale to fall in the grace period
2647 : false;
2648 }
2649
2660protectedfunctionisLotteryRefreshDue( $res, $lowTTL, $ageNew, $hotTTR, $now ) {
2661 $curTTL = $res[self::RES_CUR_TTL];
2662 $logicalTTL = $res[self::RES_TTL];
2663 $asOf = $res[self::RES_AS_OF];
2664
2665return (
2666 $this->worthRefreshExpiring( $curTTL, $logicalTTL, $lowTTL ) ||
2667 $this->worthRefreshPopular( $asOf, $ageNew, $hotTTR, $now )
2668 );
2669 }
2670
2686protectedfunctionworthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) {
2687if ( $ageNew < 0 || $timeTillRefresh <= 0 ) {
2688returnfalse;
2689 }
2690
2691 $age = $now - $asOf;
2692 $timeOld = $age - $ageNew;
2693if ( $timeOld <= 0 ) {
2694returnfalse;
2695 }
2696
2697 $popularHitsPerSec = 1;
2698// Lifecycle is: new, ramp-up refresh chance, full refresh chance.
2699// Note that the "expected # of refreshes" for the ramp-up time range is half
2700// of what it would be if P(refresh) was at its full value during that time range.
2701 $refreshWindowSec = max( $timeTillRefresh - $ageNew - self::RAMPUP_TTL / 2, 1 );
2702// P(refresh) * (# hits in $refreshWindowSec) = (expected # of refreshes)
2703// P(refresh) * ($refreshWindowSec * $popularHitsPerSec) = 1 (by definition)
2704// P(refresh) = 1/($refreshWindowSec * $popularHitsPerSec)
2705 $chance = 1 / ( $popularHitsPerSec * $refreshWindowSec );
2706// Ramp up $chance from 0 to its nominal value over RAMPUP_TTL seconds to avoid stampedes
2707 $chance *= ( $timeOld <= self::RAMPUP_TTL ) ? $timeOld / self::RAMPUP_TTL : 1;
2708
2709return ( mt_rand( 1, 1_000_000_000 ) <= 1_000_000_000 * $chance );
2710 }
2711
2730protectedfunctionworthRefreshExpiring( $curTTL, $logicalTTL, $lowTTL ) {
2731if ( $lowTTL <= 0 ) {
2732returnfalse;
2733 }
2734// T264787: avoid having keys start off with a high chance of being refreshed;
2735// the point where refreshing becomes possible cannot precede the key lifetime.
2736 $effectiveLowTTL = min( $lowTTL, $logicalTTL ?: INF );
2737
2738// How long the value was in the "low TTL" phase
2739 $timeOld = $effectiveLowTTL - $curTTL;
2740if ( $timeOld <= 0 || $timeOld >= $effectiveLowTTL ) {
2741returnfalse;
2742 }
2743
2744// Ratio of the low TTL phase that has elapsed (r)
2745 $ttrRatio = $timeOld / $effectiveLowTTL;
2746// Use p(r) as the monotonically increasing "chance of refresh" function,
2747// having p(0)=0 and p(1)=1. The value expires at the nominal expiry.
2748 $chance = $ttrRatio ** 4;
2749
2750return ( mt_rand( 1, 1_000_000_000 ) <= 1_000_000_000 * $chance );
2751 }
2752
2761protectedfunctionisValid( $value, $asOf, $minAsOf ) {
2762return ( $value !==false && $asOf >= $minAsOf );
2763 }
2764
2772privatefunction wrap( $value, $ttl, $version, $now ) {
2773// Returns keys in ascending integer order for PHP7 array packing:
2774// https://nikic.github.io/2014/12/22/PHPs-new-hashtable-implementation.html
2775 $wrapped = [
2776 self::FLD_FORMAT_VERSION => self::VERSION,
2777 self::FLD_VALUE => $value,
2778 self::FLD_TTL => $ttl,
2779 self::FLD_TIME => $now
2780 ];
2781if ( $version !==null ) {
2782 $wrapped[self::FLD_VALUE_VERSION] = $version;
2783 }
2784
2785return $wrapped;
2786 }
2787
2802privatefunction unwrap( $wrapped, $now ) {
2803// https://nikic.github.io/2014/12/22/PHPs-new-hashtable-implementation.html
2804 $res = [
2805// Attributes that only depend on the fetched key value
2806 self::RES_VALUE =>false,
2807 self::RES_VERSION =>null,
2808 self::RES_AS_OF =>null,
2809 self::RES_TTL =>null,
2810 self::RES_TOMB_AS_OF =>null,
2811// Attributes that depend on caller-specific "check" keys or "touched callbacks"
2812 self::RES_CHECK_AS_OF =>null,
2813 self::RES_TOUCH_AS_OF =>null,
2814 self::RES_CUR_TTL => null
2815 ];
2816
2817if ( is_array( $wrapped ) ) {
2818// Entry expected to be a cached value; validate it
2819if (
2820 ( $wrapped[self::FLD_FORMAT_VERSION] ??null ) === self::VERSION &&
2821 $wrapped[self::FLD_TIME] >= $this->epoch
2822 ) {
2823if ( $wrapped[self::FLD_TTL] > 0 ) {
2824// Get the approximate time left on the key
2825 $age = $now - $wrapped[self::FLD_TIME];
2826 $curTTL = max( $wrapped[self::FLD_TTL] - $age, 0.0 );
2827 }else {
2828// Key had no TTL, so the time left is unbounded
2829 $curTTL = INF;
2830 }
2831 $res[self::RES_VALUE] = $wrapped[self::FLD_VALUE];
2832 $res[self::RES_VERSION] = $wrapped[self::FLD_VALUE_VERSION] ??null;
2833 $res[self::RES_AS_OF] = $wrapped[self::FLD_TIME];
2834 $res[self::RES_CUR_TTL] = $curTTL;
2835 $res[self::RES_TTL] = $wrapped[self::FLD_TTL];
2836 }
2837 }else {
2838// Entry expected to be a tombstone; parse it
2839 $purge = $this->parsePurgeValue( $wrapped );
2840if ( $purge !==null ) {
2841// Tombstoned keys should always have a negative "current TTL"
2842 $curTTL = min( $purge[self::PURGE_TIME] - $now, self::TINY_NEGATIVE );
2843 $res[self::RES_CUR_TTL] = $curTTL;
2844 $res[self::RES_TOMB_AS_OF] = $purge[self::PURGE_TIME];
2845 }
2846 }
2847
2848return $res;
2849 }
2850
2856privatefunction determineKeyGroupForStats( $key ) {
2857 $parts = explode(':', $key, 3 );
2858// Fallback in case the key was not made by makeKey.
2859// Replace dots because they are special in StatsD (T232907)
2860return strtr( $parts[1] ?? $parts[0],'.','_' );
2861 }
2862
2871privatefunction parsePurgeValue( $value ) {
2872if ( !is_string( $value ) ) {
2873returnnull;
2874 }
2875
2876 $segments = explode(':', $value, 3 );
2877 $prefix = $segments[0];
2878if ( $prefix !== self::PURGE_VAL_PREFIX ) {
2879// Not a purge value
2880returnnull;
2881 }
2882
2883 $timestamp = (float)$segments[1];
2884// makeTombstonePurgeValue() doesn't store hold-off TTLs
2885 $holdoff = isset( $segments[2] ) ? (int)$segments[2] : self::HOLDOFF_TTL;
2886
2887if ( $timestamp < $this->epoch ) {
2888// Purge value is too old
2889returnnull;
2890 }
2891
2892return [ self::PURGE_TIME => $timestamp, self::PURGE_HOLDOFF => $holdoff ];
2893 }
2894
2901privatefunction makeCheckPurgeValue(float $timestamp,int $holdoff, ?array &$purge =null ) {
2902 $normalizedTime = (int)$timestamp;
2903// Purge array that matches what parsePurgeValue() would have returned
2904 $purge = [ self::PURGE_TIME => (float)$normalizedTime, self::PURGE_HOLDOFF => $holdoff ];
2905
2906return self::PURGE_VAL_PREFIX .":$normalizedTime:$holdoff";
2907 }
2908
2913privatefunction getProcessCache( $group ) {
2914if ( !isset( $this->processCaches[$group] ) ) {
2915 [ , $size ] = explode(':', $group );
2916 $this->processCaches[$group] =new MapCacheLRU( (int)$size );
2917if ( $this->wallClockOverride !==null ) {
2918 $this->processCaches[$group]->setMockTime( $this->wallClockOverride );
2919 }
2920 }
2921
2922return $this->processCaches[$group];
2923 }
2924
2930privatefunction getNonProcessCachedMultiKeys( ArrayIterator $keys, array $opts ) {
2931 $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
2932
2933 $keysMissing = [];
2934if ( $pcTTL > 0 && $this->callbackDepth == 0 ) {
2935 $pCache = $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY );
2936foreach ( $keys as $key => $id ) {
2937if ( !$pCache->has( $key, $pcTTL ) ) {
2938 $keysMissing[$id] = $key;
2939 }
2940 }
2941 }
2942
2943return $keysMissing;
2944 }
2945
2952privatefunction fetchWrappedValuesForWarmupCache( array $keys, array $checkKeys ) {
2953if ( !$keys ) {
2954return [];
2955 }
2956
2957// Get all the value keys to fetch...
2958 $sisterKeys = [];
2959foreach ( $keys as $baseKey ) {
2960 $sisterKeys[] = $this->makeSisterKey( $baseKey, self::TYPE_VALUE );
2961 }
2962// Get all the "check" keys to fetch...
2963foreach ( $checkKeys as $i => $checkKeyOrKeyGroup ) {
2964// Note: avoid array_merge() inside loop in case there are many keys
2965if ( is_int( $i ) ) {
2966// Single "check" key that applies to all value keys
2967 $sisterKeys[] = $this->makeSisterKey( $checkKeyOrKeyGroup, self::TYPE_TIMESTAMP );
2968 }else {
2969// List of "check" keys that apply to a specific value key
2970foreach ( (array)$checkKeyOrKeyGroup as $checkKey ) {
2971 $sisterKeys[] = $this->makeSisterKey( $checkKey, self::TYPE_TIMESTAMP );
2972 }
2973 }
2974 }
2975
2976 $wrappedBySisterKey = $this->cache->getMulti( $sisterKeys );
2977 $wrappedBySisterKey += array_fill_keys( $sisterKeys,false );
2978
2979return $wrappedBySisterKey;
2980 }
2981
2987privatefunction timeSinceLoggedMiss( $key, $now ) {
2988// phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.Found
2989for ( end( $this->missLog ); $miss = current( $this->missLog ); prev( $this->missLog ) ) {
2990if ( $miss[0] === $key ) {
2991return ( $now - $miss[1] );
2992 }
2993 }
2994
2995returnnull;
2996 }
2997
3002protectedfunctiongetCurrentTime() {
3003return $this->wallClockOverride ?: microtime(true );
3004 }
3005
3010publicfunctionsetMockTime( &$time ) {
3011 $this->wallClockOverride =& $time;
3012 $this->cache->setMockTime( $time );
3013foreach ( $this->processCaches as $pCache ) {
3014 $pCache->setMockTime( $time );
3015 }
3016 }
3017
3029privatefunction startOperationSpan( $opName, $keys, $checkKeys = [] ) {
3030 $span = $this->tracer->createSpan("WANObjectCache::$opName" )
3031 ->setSpanKind(SpanInterface::SPAN_KIND_CLIENT )
3032 ->start();
3033
3034if ( !$span->getContext()->isSampled() ) {
3035return $span;
3036 }
3037
3038 $keys = is_array( $keys ) ? implode(' ', $keys ) : $keys;
3039
3040if ( count( $checkKeys ) > 0 ) {
3041 $checkKeys = array_map(
3042static fn ( $checkKeyOrKeyGroup ) =>
3043 is_array( $checkKeyOrKeyGroup )
3044 ? implode(' ', $checkKeyOrKeyGroup )
3045 : $checkKeyOrKeyGroup,
3046 $checkKeys );
3047
3048 $checkKeys = implode(' ', $checkKeys );
3049 $span->setAttributes( ['org.wikimedia.wancache.check_keys' => $checkKeys ] );
3050 }
3051
3052 $span->setAttributes( ['org.wikimedia.wancache.keys' => $keys ] );
3053
3054 $span->activate();
3055return $span;
3056 }
3057}
3058
3060class_alias( WANObjectCache::class,'WANObjectCache' );
Wikimedia\MapCacheLRU\MapCacheLRU
Store key-value entries in a size-limited in-memory LRU cache.
DefinitionMapCacheLRU.php:38
Wikimedia\ObjectCache\BagOStuff
Abstract class for any ephemeral data store.
DefinitionBagOStuff.php:87
Wikimedia\ObjectCache\BagOStuff\ERR_NO_RESPONSE
const ERR_NO_RESPONSE
Storage operation failed to yield a complete response.
DefinitionBagOStuff.php:114
Wikimedia\ObjectCache\BagOStuff\ERR_NONE
const ERR_NONE
Storage operation succeeded, or no operation was performed.
DefinitionBagOStuff.php:112
Wikimedia\ObjectCache\BagOStuff\ERR_UNEXPECTED
const ERR_UNEXPECTED
Storage operation failed due to usage limitations or an I/O error.
DefinitionBagOStuff.php:118
Wikimedia\ObjectCache\BagOStuff\ERR_UNREACHABLE
const ERR_UNREACHABLE
Storage operation could not establish a connection.
DefinitionBagOStuff.php:116
Wikimedia\ObjectCache\EmptyBagOStuff
No-op implementation that stores nothing.
DefinitionEmptyBagOStuff.php:31
Wikimedia\ObjectCache\WANObjectCache
Multi-datacenter aware caching interface.
DefinitionWANObjectCache.php:167
Wikimedia\ObjectCache\WANObjectCache\makeMultiKeys
makeMultiKeys(array $ids, $keyCallback)
Get an iterator of (cache key => entity ID) for a list of entity IDs.
DefinitionWANObjectCache.php:2334
Wikimedia\ObjectCache\WANObjectCache\adaptiveTTL
adaptiveTTL( $mtime, $maxTTL, $minTTL=30, $factor=0.2)
Get a TTL that is higher for objects that have not changed recently.
DefinitionWANObjectCache.php:2545
Wikimedia\ObjectCache\WANObjectCache\$stats
StatsFactory $stats
DefinitionWANObjectCache.php:175
Wikimedia\ObjectCache\WANObjectCache\watchErrors
watchErrors()
Get a "watch point" token that can be used to get the "last error" to occur after now.
DefinitionWANObjectCache.php:2405
Wikimedia\ObjectCache\WANObjectCache\$useInterimHoldOffCaching
bool $useInterimHoldOffCaching
Whether to use "interim" caching while keys are tombstoned.
DefinitionWANObjectCache.php:188
Wikimedia\ObjectCache\WANObjectCache\newEmpty
static newEmpty()
Get an instance that wraps EmptyBagOStuff.
DefinitionWANObjectCache.php:396
Wikimedia\ObjectCache\WANObjectCache\getCurrentTime
getCurrentTime()
DefinitionWANObjectCache.php:3002
Wikimedia\ObjectCache\WANObjectCache\HOLDOFF_TTL_NONE
const HOLDOFF_TTL_NONE
Idiom for delete()/touchCheckKey() meaning "no hold-off period".
DefinitionWANObjectCache.php:235
Wikimedia\ObjectCache\WANObjectCache\$epoch
float $epoch
Unix timestamp of the oldest possible valid values.
DefinitionWANObjectCache.php:190
Wikimedia\ObjectCache\WANObjectCache\KEY_VERSION
const KEY_VERSION
Version number attribute for a key; keep value for b/c (< 1.36)
DefinitionWANObjectCache.php:283
Wikimedia\ObjectCache\WANObjectCache\fetchKeys
fetchKeys(array $keys, array $checkKeys, float $now, $touchedCb=null)
Fetch the value and key metadata of several keys from cache.
DefinitionWANObjectCache.php:568
Wikimedia\ObjectCache\WANObjectCache\isLotteryRefreshDue
isLotteryRefreshDue( $res, $lowTTL, $ageNew, $hotTTR, $now)
Check if a key is due for randomized regeneration due to near-expiration/popularity.
DefinitionWANObjectCache.php:2660
Wikimedia\ObjectCache\WANObjectCache\resetCheckKey
resetCheckKey( $key)
Clear the last-purge timestamp of a "check" key in all datacenters.
DefinitionWANObjectCache.php:1304
Wikimedia\ObjectCache\WANObjectCache\getQoS
getQoS( $flag)
DefinitionWANObjectCache.php:2478
Wikimedia\ObjectCache\WANObjectCache\$coalesceScheme
int $coalesceScheme
Scheme to use for key coalescing (Hash Tags or Hash Stops)
DefinitionWANObjectCache.php:192
Wikimedia\ObjectCache\WANObjectCache\worthRefreshExpiring
worthRefreshExpiring( $curTTL, $logicalTTL, $lowTTL)
Check if a key is nearing expiration and thus due for randomized regeneration.
DefinitionWANObjectCache.php:2730
Wikimedia\ObjectCache\WANObjectCache\makeGlobalKey
makeGlobalKey( $keygroup,... $components)
DefinitionWANObjectCache.php:2278
Wikimedia\ObjectCache\WANObjectCache\STALE_TTL_NONE
const STALE_TTL_NONE
Idiom for set()/getWithSetCallback() meaning "no post-expiration persistence".
DefinitionWANObjectCache.php:231
Wikimedia\ObjectCache\WANObjectCache\getRouteKey
getRouteKey(string $sisterKey)
DefinitionWANObjectCache.php:2572
Wikimedia\ObjectCache\WANObjectCache\isValid
isValid( $value, $asOf, $minAsOf)
Check that a wrapper value exists and has an acceptable age.
DefinitionWANObjectCache.php:2761
Wikimedia\ObjectCache\WANObjectCache\getWarmupKeyMisses
getWarmupKeyMisses()
DefinitionWANObjectCache.php:2563
Wikimedia\ObjectCache\WANObjectCache\TTL_LAGGED
const TTL_LAGGED
Max TTL, in seconds, to store keys when a data source has high replication lag.
DefinitionWANObjectCache.php:220
Wikimedia\ObjectCache\WANObjectCache\getMultiCheckKeyTime
getMultiCheckKeyTime(array $keys)
Fetch the values of each timestamp "check" key.
DefinitionWANObjectCache.php:1186
Wikimedia\ObjectCache\WANObjectCache\getWithSetCallback
getWithSetCallback( $key, $ttl, $callback, array $opts=[], array $cbParams=[])
Method to fetch/regenerate a cache key.
DefinitionWANObjectCache.php:1623
Wikimedia\ObjectCache\WANObjectCache\getMultiWithSetCallback
getMultiWithSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch multiple cache keys at once with regeneration.
DefinitionWANObjectCache.php:2065
Wikimedia\ObjectCache\WANObjectCache\KEY_CUR_TTL
const KEY_CUR_TTL
Remaining TTL attribute for a key; keep value for b/c (< 1.36)
DefinitionWANObjectCache.php:289
Wikimedia\ObjectCache\WANObjectCache\__construct
__construct(array $params)
DefinitionWANObjectCache.php:365
Wikimedia\ObjectCache\WANObjectCache\$cache
BagOStuff $cache
The local datacenter cache.
DefinitionWANObjectCache.php:169
Wikimedia\ObjectCache\WANObjectCache\HOLDOFF_TTL
const HOLDOFF_TTL
Seconds to tombstone keys on delete() and to treat keys as volatile after purges.
DefinitionWANObjectCache.php:215
Wikimedia\ObjectCache\WANObjectCache\worthRefreshPopular
worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now)
Check if a key is due for randomized regeneration due to its popularity.
DefinitionWANObjectCache.php:2686
Wikimedia\ObjectCache\WANObjectCache\$broadcastRoute
string null $broadcastRoute
Routing prefix for operations that should be broadcasted to all data centers.
DefinitionWANObjectCache.php:186
Wikimedia\ObjectCache\WANObjectCache\touchCheckKey
touchCheckKey( $key, $holdoff=self::HOLDOFF_TTL)
Increase the last-purge timestamp of a "check" key in all datacenters.
DefinitionWANObjectCache.php:1251
Wikimedia\ObjectCache\WANObjectCache\getLastError
getLastError( $watchPoint=0)
Get the "last error" registry.
DefinitionWANObjectCache.php:2426
Wikimedia\ObjectCache\WANObjectCache\KEY_TTL
const KEY_TTL
Logical TTL attribute for a key.
DefinitionWANObjectCache.php:287
Wikimedia\ObjectCache\WANObjectCache\KEY_AS_OF
const KEY_AS_OF
Generation completion timestamp attribute for a key; keep value for b/c (< 1.36)
DefinitionWANObjectCache.php:285
Wikimedia\ObjectCache\WANObjectCache\setMockTime
setMockTime(&$time)
DefinitionWANObjectCache.php:3010
Wikimedia\ObjectCache\WANObjectCache\KEY_CHECK_AS_OF
const KEY_CHECK_AS_OF
Highest "check" key timestamp for a key; keep value for b/c (< 1.36)
DefinitionWANObjectCache.php:293
Wikimedia\ObjectCache\WANObjectCache\setLogger
setLogger(LoggerInterface $logger)
DefinitionWANObjectCache.php:387
Wikimedia\ObjectCache\WANObjectCache\$asyncHandler
callable null $asyncHandler
Function that takes a WAN cache callback and runs it later.
DefinitionWANObjectCache.php:177
Wikimedia\ObjectCache\WANObjectCache\getCheckKeyTime
getCheckKeyTime( $key)
Fetch the value of a timestamp "check" key.
DefinitionWANObjectCache.php:1118
Wikimedia\ObjectCache\WANObjectCache\KEY_TOMB_AS_OF
const KEY_TOMB_AS_OF
Tombstone timestamp attribute for a key; keep value for b/c (< 1.36)
DefinitionWANObjectCache.php:291
Wikimedia\ObjectCache\WANObjectCache\$processCaches
MapCacheLRU[] $processCaches
Map of group PHP instance caches.
DefinitionWANObjectCache.php:171
Wikimedia\ObjectCache\WANObjectCache\makeKey
makeKey( $keygroup,... $components)
DefinitionWANObjectCache.php:2289
Wikimedia\ObjectCache\WANObjectCache\getMulti
getMulti(array $keys, &$curTTLs=[], array $checkKeys=[], &$info=[])
Fetch the value of several keys from cache.
DefinitionWANObjectCache.php:511
Wikimedia\ObjectCache\WANObjectCache\getMultiWithUnionSetCallback
getMultiWithUnionSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch/regenerate multiple cache keys at once.
DefinitionWANObjectCache.php:2175
Wikimedia\ObjectCache\WANObjectCache\$logger
LoggerInterface $logger
DefinitionWANObjectCache.php:173
Wikimedia\ObjectCache\WANObjectCache\PASS_BY_REF
const PASS_BY_REF
Idiom for get()/getMulti() to return extra information by reference.
DefinitionWANObjectCache.php:244
Wikimedia\ObjectCache\WANObjectCache\useInterimHoldOffCaching
useInterimHoldOffCaching( $enabled)
Enable or disable the use of brief caching for tombstoned keys.
DefinitionWANObjectCache.php:2469
Wikimedia\ObjectCache\WANObjectCache\clearProcessCache
clearProcessCache()
Clear the in-process caches; useful for testing.
DefinitionWANObjectCache.php:2445
Wikimedia\ObjectCache\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...
DefinitionWANObjectCache.php:2386
Wikimedia\ObjectCache\WANObjectCache\GRACE_TTL_NONE
const GRACE_TTL_NONE
Idiom for set()/getWithSetCallback() meaning "no post-expiration grace period".
DefinitionWANObjectCache.php:233
Wikimedia\Stats\StatsFactory
This is the primary interface for validating metrics definitions, caching defined metrics,...
DefinitionStatsFactory.php:46
Wikimedia\Telemetry\NoopTracer
A no-op tracer that creates no-op spans and persists no data.
DefinitionNoopTracer.php:12
Wikimedia\LightweightObjectStore\ExpirationAwareness
Generic interface providing Time-To-Live constants for expirable object storage.
DefinitionExpirationAwareness.php:30
Wikimedia\ObjectCache\IStoreKeyEncoder
Key-encoding methods for object caching (BagOStuff and WANObjectCache)
DefinitionIStoreKeyEncoder.php:11
Wikimedia\Telemetry\SpanInterface
Represents an OpenTelemetry span, i.e.
DefinitionSpanInterface.php:14
Wikimedia\Telemetry\SpanInterface\SPAN_KIND_CLIENT
const SPAN_KIND_CLIENT
Indicates that the span describes a request to some remote service.
DefinitionSpanInterface.php:30
Wikimedia\Telemetry\TracerInterface
Base interface for an OpenTelemetry tracer responsible for creating spans.
DefinitionTracerInterface.php:11
Wikimedia\ObjectCache
DefinitionAPCUBagOStuff.php:20

[8]ページ先頭

©2009-2025 Movatter.jp