Complex classes like WANObjectCache often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use WANObjectCache, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 76 | class WANObjectCache implements IExpiringStore, LoggerAwareInterface { |
||
| 77 | /** @var BagOStuff The local datacenter cache */ |
||
| 78 | protected $cache; |
||
| 79 | /** @var HashBagOStuff Script instance PHP cache */ |
||
| 80 | protected $procCache; |
||
| 81 | /** @var string Purge channel name */ |
||
| 82 | protected $purgeChannel; |
||
| 83 | /** @var EventRelayer Bus that handles purge broadcasts */ |
||
| 84 | protected $purgeRelayer; |
||
| 85 | /** @var LoggerInterface */ |
||
| 86 | protected $logger; |
||
| 87 | |||
| 88 | /** @var int ERR_* constant for the "last error" registry */ |
||
| 89 | protected $lastRelayError = self::ERR_NONE; |
||
| 90 | |||
| 91 | /** Max time expected to pass between delete() and DB commit finishing */ |
||
| 92 | const MAX_COMMIT_DELAY = 3; |
||
| 93 | /** Max replication+snapshot lag before applying TTL_LAGGED or disallowing set() */ |
||
| 94 | const MAX_READ_LAG = 7; |
||
| 95 | /** Seconds to tombstone keys on delete() */ |
||
| 96 | const HOLDOFF_TTL = 11; // MAX_COMMIT_DELAY + MAX_READ_LAG + 1 |
||
| 97 | |||
| 98 | /** Seconds to keep dependency purge keys around */ |
||
| 99 | const CHECK_KEY_TTL = self::TTL_YEAR; |
||
| 100 | /** Seconds to keep lock keys around */ |
||
| 101 | const LOCK_TTL = 10; |
||
| 102 | /** Default remaining TTL at which to consider pre-emptive regeneration */ |
||
| 103 | const LOW_TTL = 30; |
||
| 104 | /** Default time-since-expiry on a miss that makes a key "hot" */ |
||
| 105 | const LOCK_TSE = 1; |
||
| 106 | |||
| 107 | /** Never consider performing "popularity" refreshes until a key reaches this age */ |
||
| 108 | const AGE_NEW = 60; |
||
| 109 | /** The time length of the "popularity" refresh window for hot keys */ |
||
| 110 | const HOT_TTR = 900; |
||
| 111 | /** Hits/second for a refresh to be expected within the "popularity" window */ |
||
| 112 | const HIT_RATE_HIGH = 1; |
||
| 113 | /** Seconds to ramp up to the "popularity" refresh chance after a key is no longer new */ |
||
| 114 | const RAMPUP_TTL = 30; |
||
| 115 | |||
| 116 | /** Idiom for getWithSetCallback() callbacks to avoid calling set() */ |
||
| 117 | const TTL_UNCACHEABLE = -1; |
||
| 118 | /** Idiom for getWithSetCallback() callbacks to 'lockTSE' logic */ |
||
| 119 | const TSE_NONE = -1; |
||
| 120 | /** Max TTL to store keys when a data sourced is lagged */ |
||
| 121 | const TTL_LAGGED = 30; |
||
| 122 | /** Idiom for delete() for "no hold-off" */ |
||
| 123 | const HOLDOFF_NONE = 0; |
||
| 124 | |||
| 125 | /** Tiny negative float to use when CTL comes up >= 0 due to clock skew */ |
||
| 126 | const TINY_NEGATIVE = -0.000001; |
||
| 127 | |||
| 128 | /** Cache format version number */ |
||
| 129 | const VERSION = 1; |
||
| 130 | |||
| 131 | const FLD_VERSION = 0; // key to cache version number |
||
| 132 | const FLD_VALUE = 1; // key to the cached value |
||
| 133 | const FLD_TTL = 2; // key to the original TTL |
||
| 134 | const FLD_TIME = 3; // key to the cache time |
||
| 135 | const FLD_FLAGS = 4; // key to the flags bitfield |
||
| 136 | const FLD_HOLDOFF = 5; // key to any hold-off TTL |
||
| 137 | |||
| 138 | /** @var integer Treat this value as expired-on-arrival */ |
||
| 139 | const FLG_STALE = 1; |
||
| 140 | |||
| 141 | const ERR_NONE = 0; // no error |
||
| 142 | const ERR_NO_RESPONSE = 1; // no response |
||
| 143 | const ERR_UNREACHABLE = 2; // can't connect |
||
| 144 | const ERR_UNEXPECTED = 3; // response gave some error |
||
| 145 | const ERR_RELAY = 4; // relay broadcast failed |
||
| 146 | |||
| 147 | const VALUE_KEY_PREFIX = 'WANCache:v:'; |
||
| 148 | const INTERIM_KEY_PREFIX = 'WANCache:i:'; |
||
| 149 | const TIME_KEY_PREFIX = 'WANCache:t:'; |
||
| 150 | const MUTEX_KEY_PREFIX = 'WANCache:m:'; |
||
| 151 | |||
| 152 | const PURGE_VAL_PREFIX = 'PURGED:'; |
||
| 153 | |||
| 154 | const VFLD_DATA = 'WOC:d'; // key to the value of versioned data |
||
| 155 | const VFLD_VERSION = 'WOC:v'; // key to the version of the value present |
||
| 156 | |||
| 157 | const MAX_PC_KEYS = 1000; // max keys to keep in process cache |
||
| 158 | |||
| 159 | const DEFAULT_PURGE_CHANNEL = 'wancache-purge'; |
||
| 160 | |||
| 161 | /** |
||
| 162 | * @param array $params |
||
| 163 | * - cache : BagOStuff object for a persistent cache |
||
| 164 | * - channels : Map of (action => channel string). Actions include "purge". |
||
| 165 | * - relayers : Map of (action => EventRelayer object). Actions include "purge". |
||
| 166 | * - logger : LoggerInterface object |
||
| 167 | */ |
||
| 168 | public function __construct( array $params ) { |
||
| 179 | |||
| 180 | public function setLogger( LoggerInterface $logger ) { |
||
| 183 | |||
| 184 | /** |
||
| 185 | * Get an instance that wraps EmptyBagOStuff |
||
| 186 | * |
||
| 187 | * @return WANObjectCache |
||
| 188 | */ |
||
| 189 | public static function newEmpty() { |
||
| 196 | |||
| 197 | /** |
||
| 198 | * Fetch the value of a key from cache |
||
| 199 | * |
||
| 200 | * If supplied, $curTTL is set to the remaining TTL (current time left): |
||
| 201 | * - a) INF; if $key exists, has no TTL, and is not expired by $checkKeys |
||
| 202 | * - b) float (>=0); if $key exists, has a TTL, and is not expired by $checkKeys |
||
| 203 | * - c) float (<0); if $key is tombstoned, stale, or existing but expired by $checkKeys |
||
| 204 | * - d) null; if $key does not exist and is not tombstoned |
||
| 205 | * |
||
| 206 | * If a key is tombstoned, $curTTL will reflect the time since delete(). |
||
| 207 | * |
||
| 208 | * The timestamp of $key will be checked against the last-purge timestamp |
||
| 209 | * of each of $checkKeys. Those $checkKeys not in cache will have the last-purge |
||
| 210 | * initialized to the current timestamp. If any of $checkKeys have a timestamp |
||
| 211 | * greater than that of $key, then $curTTL will reflect how long ago $key |
||
| 212 | * became invalid. Callers can use $curTTL to know when the value is stale. |
||
| 213 | * The $checkKeys parameter allow mass invalidations by updating a single key: |
||
| 214 | * - a) Each "check" key represents "last purged" of some source data |
||
| 215 | * - b) Callers pass in relevant "check" keys as $checkKeys in get() |
||
| 216 | * - c) When the source data that "check" keys represent changes, |
||
| 217 | * the touchCheckKey() method is called on them |
||
| 218 | * |
||
| 219 | * Source data entities might exists in a DB that uses snapshot isolation |
||
| 220 | * (e.g. the default REPEATABLE-READ in innoDB). Even for mutable data, that |
||
| 221 | * isolation can largely be maintained by doing the following: |
||
| 222 | * - a) Calling delete() on entity change *and* creation, before DB commit |
||
| 223 | * - b) Keeping transaction duration shorter than delete() hold-off TTL |
||
| 224 | * |
||
| 225 | * However, pre-snapshot values might still be seen if an update was made |
||
| 226 | * in a remote datacenter but the purge from delete() didn't relay yet. |
||
| 227 | * |
||
| 228 | * Consider using getWithSetCallback() instead of get() and set() cycles. |
||
| 229 | * That method has cache slam avoiding features for hot/expensive keys. |
||
| 230 | * |
||
| 231 | * @param string $key Cache key |
||
| 232 | * @param mixed $curTTL Approximate TTL left on the key if present/tombstoned [returned] |
||
| 233 | * @param array $checkKeys List of "check" keys |
||
| 234 | * @param float &$asOf UNIX timestamp of cached value; null on failure [returned] |
||
| 235 | * @return mixed Value of cache key or false on failure |
||
| 236 | */ |
||
| 237 | final public function get( $key, &$curTTL = null, array $checkKeys = [], &$asOf = null ) { |
||
| 246 | |||
| 247 | /** |
||
| 248 | * Fetch the value of several keys from cache |
||
| 249 | * |
||
| 250 | * @see WANObjectCache::get() |
||
| 251 | * |
||
| 252 | * @param array $keys List of cache keys |
||
| 253 | * @param array $curTTLs Map of (key => approximate TTL left) for existing keys [returned] |
||
| 254 | * @param array $checkKeys List of check keys to apply to all $keys. May also apply "check" |
||
| 255 | * keys to specific cache keys only by using cache keys as keys in the $checkKeys array. |
||
| 256 | * @param float[] &$asOfs Map of (key => UNIX timestamp of cached value; null on failure) |
||
| 257 | * @return array Map of (key => value) for keys that exist |
||
| 258 | */ |
||
| 259 | final public function getMulti( |
||
| 332 | |||
| 333 | /** |
||
| 334 | * @since 1.27 |
||
| 335 | * @param array $timeKeys List of prefixed time check keys |
||
| 336 | * @param array $wrappedValues |
||
| 337 | * @param float $now |
||
| 338 | * @return array List of purge value arrays |
||
| 339 | */ |
||
| 340 | private function processCheckKeys( array $timeKeys, array $wrappedValues, $now ) { |
||
| 356 | |||
| 357 | /** |
||
| 358 | * Set the value of a key in cache |
||
| 359 | * |
||
| 360 | * Simply calling this method when source data changes is not valid because |
||
| 361 | * the changes do not replicate to the other WAN sites. In that case, delete() |
||
| 362 | * should be used instead. This method is intended for use on cache misses. |
||
| 363 | * |
||
| 364 | * If the data was read from a snapshot-isolated transactions (e.g. the default |
||
| 365 | * REPEATABLE-READ in innoDB), use 'since' to avoid the following race condition: |
||
| 366 | * - a) T1 starts |
||
| 367 | * - b) T2 updates a row, calls delete(), and commits |
||
| 368 | * - c) The HOLDOFF_TTL passes, expiring the delete() tombstone |
||
| 369 | * - d) T1 reads the row and calls set() due to a cache miss |
||
| 370 | * - e) Stale value is stuck in cache |
||
| 371 | * |
||
| 372 | * Setting 'lag' and 'since' help avoids keys getting stuck in stale states. |
||
| 373 | * |
||
| 374 | * Example usage: |
||
| 375 | * @code |
||
| 376 | * $dbr = wfGetDB( DB_REPLICA ); |
||
| 377 | * $setOpts = Database::getCacheSetOptions( $dbr ); |
||
| 378 | * // Fetch the row from the DB |
||
| 379 | * $row = $dbr->selectRow( ... ); |
||
| 380 | * $key = $cache->makeKey( 'building', $buildingId ); |
||
| 381 | * $cache->set( $key, $row, $cache::TTL_DAY, $setOpts ); |
||
| 382 | * @endcode |
||
| 383 | * |
||
| 384 | * @param string $key Cache key |
||
| 385 | * @param mixed $value |
||
| 386 | * @param integer $ttl Seconds to live. Special values are: |
||
| 387 | * - WANObjectCache::TTL_INDEFINITE: Cache forever |
||
| 388 | * @param array $opts Options map: |
||
| 389 | * - lag : Seconds of replica DB lag. Typically, this is either the replica DB lag |
||
| 390 | * before the data was read or, if applicable, the replica DB lag before |
||
| 391 | * the snapshot-isolated transaction the data was read from started. |
||
| 392 | * Default: 0 seconds |
||
| 393 | * - since : UNIX timestamp of the data in $value. Typically, this is either |
||
| 394 | * the current time the data was read or (if applicable) the time when |
||
| 395 | * the snapshot-isolated transaction the data was read from started. |
||
| 396 | * Default: 0 seconds |
||
| 397 | * - pending : Whether this data is possibly from an uncommitted write transaction. |
||
| 398 | * Generally, other threads should not see values from the future and |
||
| 399 | * they certainly should not see ones that ended up getting rolled back. |
||
| 400 | * Default: false |
||
| 401 | * - lockTSE : if excessive replication/snapshot lag is detected, then store the value |
||
| 402 | * with this TTL and flag it as stale. This is only useful if the reads for |
||
| 403 | * this key use getWithSetCallback() with "lockTSE" set. |
||
| 404 | * Default: WANObjectCache::TSE_NONE |
||
| 405 | * @return bool Success |
||
| 406 | */ |
||
| 407 | final public function set( $key, $value, $ttl = 0, array $opts = [] ) { |
||
| 455 | |||
| 456 | /** |
||
| 457 | * Purge a key from all datacenters |
||
| 458 | * |
||
| 459 | * This should only be called when the underlying data (being cached) |
||
| 460 | * changes in a significant way. This deletes the key and starts a hold-off |
||
| 461 | * period where the key cannot be written to for a few seconds (HOLDOFF_TTL). |
||
| 462 | * This is done to avoid the following race condition: |
||
| 463 | * - a) Some DB data changes and delete() is called on a corresponding key |
||
| 464 | * - b) A request refills the key with a stale value from a lagged DB |
||
| 465 | * - c) The stale value is stuck there until the key is expired/evicted |
||
| 466 | * |
||
| 467 | * This is implemented by storing a special "tombstone" value at the cache |
||
| 468 | * key that this class recognizes; get() calls will return false for the key |
||
| 469 | * and any set() calls will refuse to replace tombstone values at the key. |
||
| 470 | * For this to always avoid stale value writes, the following must hold: |
||
| 471 | * - a) Replication lag is bounded to being less than HOLDOFF_TTL; or |
||
| 472 | * - b) If lag is higher, the DB will have gone into read-only mode already |
||
| 473 | * |
||
| 474 | * Note that set() can also be lag-aware and lower the TTL if it's high. |
||
| 475 | * |
||
| 476 | * When using potentially long-running ACID transactions, a good pattern is |
||
| 477 | * to use a pre-commit hook to issue the delete. This means that immediately |
||
| 478 | * after commit, callers will see the tombstone in cache upon purge relay. |
||
| 479 | * It also avoids the following race condition: |
||
| 480 | * - a) T1 begins, changes a row, and calls delete() |
||
| 481 | * - b) The HOLDOFF_TTL passes, expiring the delete() tombstone |
||
| 482 | * - c) T2 starts, reads the row and calls set() due to a cache miss |
||
| 483 | * - d) T1 finally commits |
||
| 484 | * - e) Stale value is stuck in cache |
||
| 485 | * |
||
| 486 | * Example usage: |
||
| 487 | * @code |
||
| 488 | * $dbw->startAtomic( __METHOD__ ); // start of request |
||
| 489 | * ... <execute some stuff> ... |
||
| 490 | * // Update the row in the DB |
||
| 491 | * $dbw->update( ... ); |
||
| 492 | * $key = $cache->makeKey( 'homes', $homeId ); |
||
| 493 | * // Purge the corresponding cache entry just before committing |
||
| 494 | * $dbw->onTransactionPreCommitOrIdle( function() use ( $cache, $key ) { |
||
| 495 | * $cache->delete( $key ); |
||
| 496 | * } ); |
||
| 497 | * ... <execute some stuff> ... |
||
| 498 | * $dbw->endAtomic( __METHOD__ ); // end of request |
||
| 499 | * @endcode |
||
| 500 | * |
||
| 501 | * The $ttl parameter can be used when purging values that have not actually changed |
||
| 502 | * recently. For example, a cleanup script to purge cache entries does not really need |
||
| 503 | * a hold-off period, so it can use HOLDOFF_NONE. Likewise for user-requested purge. |
||
| 504 | * Note that $ttl limits the effective range of 'lockTSE' for getWithSetCallback(). |
||
| 505 | * |
||
| 506 | * If called twice on the same key, then the last hold-off TTL takes precedence. For |
||
| 507 | * idempotence, the $ttl should not vary for different delete() calls on the same key. |
||
| 508 | * |
||
| 509 | * @param string $key Cache key |
||
| 510 | * @param integer $ttl Tombstone TTL; Default: WANObjectCache::HOLDOFF_TTL |
||
| 511 | * @return bool True if the item was purged or not found, false on failure |
||
| 512 | */ |
||
| 513 | final public function delete( $key, $ttl = self::HOLDOFF_TTL ) { |
||
| 526 | |||
| 527 | /** |
||
| 528 | * Fetch the value of a timestamp "check" key |
||
| 529 | * |
||
| 530 | * The key will be *initialized* to the current time if not set, |
||
| 531 | * so only call this method if this behavior is actually desired |
||
| 532 | * |
||
| 533 | * The timestamp can be used to check whether a cached value is valid. |
||
| 534 | * Callers should not assume that this returns the same timestamp in |
||
| 535 | * all datacenters due to relay delays. |
||
| 536 | * |
||
| 537 | * The level of staleness can roughly be estimated from this key, but |
||
| 538 | * if the key was evicted from cache, such calculations may show the |
||
| 539 | * time since expiry as ~0 seconds. |
||
| 540 | * |
||
| 541 | * Note that "check" keys won't collide with other regular keys. |
||
| 542 | * |
||
| 543 | * @param string $key |
||
| 544 | * @return float UNIX timestamp of the check key |
||
| 545 | */ |
||
| 546 | final public function getCheckKeyTime( $key ) { |
||
| 564 | |||
| 565 | /** |
||
| 566 | * Purge a "check" key from all datacenters, invalidating keys that use it |
||
| 567 | * |
||
| 568 | * This should only be called when the underlying data (being cached) |
||
| 569 | * changes in a significant way, and it is impractical to call delete() |
||
| 570 | * on all keys that should be changed. When get() is called on those |
||
| 571 | * keys, the relevant "check" keys must be supplied for this to work. |
||
| 572 | * |
||
| 573 | * The "check" key essentially represents a last-modified field. |
||
| 574 | * When touched, the field will be updated on all cache servers. |
||
| 575 | * Keys using it via get(), getMulti(), or getWithSetCallback() will |
||
| 576 | * be invalidated. It is treated as being HOLDOFF_TTL seconds in the future |
||
| 577 | * by those methods to avoid race conditions where dependent keys get updated |
||
| 578 | * with stale values (e.g. from a DB replica DB). |
||
| 579 | * |
||
| 580 | * This is typically useful for keys with hardcoded names or in some cases |
||
| 581 | * dynamically generated names where a low number of combinations exist. |
||
| 582 | * When a few important keys get a large number of hits, a high cache |
||
| 583 | * time is usually desired as well as "lockTSE" logic. The resetCheckKey() |
||
| 584 | * method is less appropriate in such cases since the "time since expiry" |
||
| 585 | * cannot be inferred, causing any get() after the reset to treat the key |
||
| 586 | * as being "hot", resulting in more stale value usage. |
||
| 587 | * |
||
| 588 | * Note that "check" keys won't collide with other regular keys. |
||
| 589 | * |
||
| 590 | * @see WANObjectCache::get() |
||
| 591 | * @see WANObjectCache::getWithSetCallback() |
||
| 592 | * @see WANObjectCache::resetCheckKey() |
||
| 593 | * |
||
| 594 | * @param string $key Cache key |
||
| 595 | * @param int $holdoff HOLDOFF_TTL or HOLDOFF_NONE constant |
||
| 596 | * @return bool True if the item was purged or not found, false on failure |
||
| 597 | */ |
||
| 598 | final public function touchCheckKey( $key, $holdoff = self::HOLDOFF_TTL ) { |
||
| 602 | |||
| 603 | /** |
||
| 604 | * Delete a "check" key from all datacenters, invalidating keys that use it |
||
| 605 | * |
||
| 606 | * This is similar to touchCheckKey() in that keys using it via get(), getMulti(), |
||
| 607 | * or getWithSetCallback() will be invalidated. The differences are: |
||
| 608 | * - a) The "check" key will be deleted from all caches and lazily |
||
| 609 | * re-initialized when accessed (rather than set everywhere) |
||
| 610 | * - b) Thus, dependent keys will be known to be invalid, but not |
||
| 611 | * for how long (they are treated as "just" purged), which |
||
| 612 | * effects any lockTSE logic in getWithSetCallback() |
||
| 613 | * - c) Since "check" keys are initialized only on the server the key hashes |
||
| 614 | * to, any temporary ejection of that server will cause the value to be |
||
| 615 | * seen as purged as a new server will initialize the "check" key. |
||
| 616 | * |
||
| 617 | * The advantage is that this does not place high TTL keys on every cache |
||
| 618 | * server, making it better for code that will cache many different keys |
||
| 619 | * and either does not use lockTSE or uses a low enough TTL anyway. |
||
| 620 | * |
||
| 621 | * This is typically useful for keys with dynamically generated names |
||
| 622 | * where a high number of combinations exist. |
||
| 623 | * |
||
| 624 | * Note that "check" keys won't collide with other regular keys. |
||
| 625 | * |
||
| 626 | * @see WANObjectCache::get() |
||
| 627 | * @see WANObjectCache::getWithSetCallback() |
||
| 628 | * @see WANObjectCache::touchCheckKey() |
||
| 629 | * |
||
| 630 | * @param string $key Cache key |
||
| 631 | * @return bool True if the item was purged or not found, false on failure |
||
| 632 | */ |
||
| 633 | final public function resetCheckKey( $key ) { |
||
| 637 | |||
| 638 | /** |
||
| 639 | * Method to fetch/regenerate cache keys |
||
| 640 | * |
||
| 641 | * On cache miss, the key will be set to the callback result via set() |
||
| 642 | * (unless the callback returns false) and that result will be returned. |
||
| 643 | * The arguments supplied to the callback are: |
||
| 644 | * - $oldValue : current cache value or false if not present |
||
| 645 | * - &$ttl : a reference to the TTL which can be altered |
||
| 646 | * - &$setOpts : a reference to options for set() which can be altered |
||
| 647 | * |
||
| 648 | * It is strongly recommended to set the 'lag' and 'since' fields to avoid race conditions |
||
| 649 | * that can cause stale values to get stuck at keys. Usually, callbacks ignore the current |
||
| 650 | * value, but it can be used to maintain "most recent X" values that come from time or |
||
| 651 | * sequence based source data, provided that the "as of" id/time is tracked. Note that |
||
| 652 | * preemptive regeneration and $checkKeys can result in a non-false current value. |
||
| 653 | * |
||
| 654 | * Usage of $checkKeys is similar to get() and getMulti(). However, rather than the caller |
||
| 655 | * having to inspect a "current time left" variable (e.g. $curTTL, $curTTLs), a cache |
||
| 656 | * regeneration will automatically be triggered using the callback. |
||
| 657 | * |
||
| 658 | * The simplest way to avoid stampedes for hot keys is to use |
||
| 659 | * the 'lockTSE' option in $opts. If cache purges are needed, also: |
||
| 660 | * - a) Pass $key into $checkKeys |
||
| 661 | * - b) Use touchCheckKey( $key ) instead of delete( $key ) |
||
| 662 | * |
||
| 663 | * Example usage (typical key): |
||
| 664 | * @code |
||
| 665 | * $catInfo = $cache->getWithSetCallback( |
||
| 666 | * // Key to store the cached value under |
||
| 667 | * $cache->makeKey( 'cat-attributes', $catId ), |
||
| 668 | * // Time-to-live (in seconds) |
||
| 669 | * $cache::TTL_MINUTE, |
||
| 670 | * // Function that derives the new key value |
||
| 671 | * function ( $oldValue, &$ttl, array &$setOpts ) { |
||
| 672 | * $dbr = wfGetDB( DB_REPLICA ); |
||
| 673 | * // Account for any snapshot/replica DB lag |
||
| 674 | * $setOpts += Database::getCacheSetOptions( $dbr ); |
||
| 675 | * |
||
| 676 | * return $dbr->selectRow( ... ); |
||
| 677 | * } |
||
| 678 | * ); |
||
| 679 | * @endcode |
||
| 680 | * |
||
| 681 | * Example usage (key that is expensive and hot): |
||
| 682 | * @code |
||
| 683 | * $catConfig = $cache->getWithSetCallback( |
||
| 684 | * // Key to store the cached value under |
||
| 685 | * $cache->makeKey( 'site-cat-config' ), |
||
| 686 | * // Time-to-live (in seconds) |
||
| 687 | * $cache::TTL_DAY, |
||
| 688 | * // Function that derives the new key value |
||
| 689 | * function ( $oldValue, &$ttl, array &$setOpts ) { |
||
| 690 | * $dbr = wfGetDB( DB_REPLICA ); |
||
| 691 | * // Account for any snapshot/replica DB lag |
||
| 692 | * $setOpts += Database::getCacheSetOptions( $dbr ); |
||
| 693 | * |
||
| 694 | * return CatConfig::newFromRow( $dbr->selectRow( ... ) ); |
||
| 695 | * }, |
||
| 696 | * [ |
||
| 697 | * // Calling touchCheckKey() on this key invalidates the cache |
||
| 698 | * 'checkKeys' => [ $cache->makeKey( 'site-cat-config' ) ], |
||
| 699 | * // Try to only let one datacenter thread manage cache updates at a time |
||
| 700 | * 'lockTSE' => 30, |
||
| 701 | * // Avoid querying cache servers multiple times in a web request |
||
| 702 | * 'pcTTL' => $cache::TTL_PROC_LONG |
||
| 703 | * ] |
||
| 704 | * ); |
||
| 705 | * @endcode |
||
| 706 | * |
||
| 707 | * Example usage (key with dynamic dependencies): |
||
| 708 | * @code |
||
| 709 | * $catState = $cache->getWithSetCallback( |
||
| 710 | * // Key to store the cached value under |
||
| 711 | * $cache->makeKey( 'cat-state', $cat->getId() ), |
||
| 712 | * // Time-to-live (seconds) |
||
| 713 | * $cache::TTL_HOUR, |
||
| 714 | * // Function that derives the new key value |
||
| 715 | * function ( $oldValue, &$ttl, array &$setOpts ) { |
||
| 716 | * // Determine new value from the DB |
||
| 717 | * $dbr = wfGetDB( DB_REPLICA ); |
||
| 718 | * // Account for any snapshot/replica DB lag |
||
| 719 | * $setOpts += Database::getCacheSetOptions( $dbr ); |
||
| 720 | * |
||
| 721 | * return CatState::newFromResults( $dbr->select( ... ) ); |
||
| 722 | * }, |
||
| 723 | * [ |
||
| 724 | * // The "check" keys that represent things the value depends on; |
||
| 725 | * // Calling touchCheckKey() on any of them invalidates the cache |
||
| 726 | * 'checkKeys' => [ |
||
| 727 | * $cache->makeKey( 'sustenance-bowls', $cat->getRoomId() ), |
||
| 728 | * $cache->makeKey( 'people-present', $cat->getHouseId() ), |
||
| 729 | * $cache->makeKey( 'cat-laws', $cat->getCityId() ), |
||
| 730 | * ] |
||
| 731 | * ] |
||
| 732 | * ); |
||
| 733 | * @endcode |
||
| 734 | * |
||
| 735 | * Example usage (hot key holding most recent 100 events): |
||
| 736 | * @code |
||
| 737 | * $lastCatActions = $cache->getWithSetCallback( |
||
| 738 | * // Key to store the cached value under |
||
| 739 | * $cache->makeKey( 'cat-last-actions', 100 ), |
||
| 740 | * // Time-to-live (in seconds) |
||
| 741 | * 10, |
||
| 742 | * // Function that derives the new key value |
||
| 743 | * function ( $oldValue, &$ttl, array &$setOpts ) { |
||
| 744 | * $dbr = wfGetDB( DB_REPLICA ); |
||
| 745 | * // Account for any snapshot/replica DB lag |
||
| 746 | * $setOpts += Database::getCacheSetOptions( $dbr ); |
||
| 747 | * |
||
| 748 | * // Start off with the last cached list |
||
| 749 | * $list = $oldValue ?: []; |
||
| 750 | * // Fetch the last 100 relevant rows in descending order; |
||
| 751 | * // only fetch rows newer than $list[0] to reduce scanning |
||
| 752 | * $rows = iterator_to_array( $dbr->select( ... ) ); |
||
| 753 | * // Merge them and get the new "last 100" rows |
||
| 754 | * return array_slice( array_merge( $new, $list ), 0, 100 ); |
||
| 755 | * }, |
||
| 756 | * [ |
||
| 757 | * // Try to only let one datacenter thread manage cache updates at a time |
||
| 758 | * 'lockTSE' => 30, |
||
| 759 | * // Use a magic value when no cache value is ready rather than stampeding |
||
| 760 | * 'busyValue' => 'computing' |
||
| 761 | * ] |
||
| 762 | * ); |
||
| 763 | * @endcode |
||
| 764 | * |
||
| 765 | * @see WANObjectCache::get() |
||
| 766 | * @see WANObjectCache::set() |
||
| 767 | * |
||
| 768 | * @param string $key Cache key |
||
| 769 | * @param integer $ttl Seconds to live for key updates. Special values are: |
||
| 770 | * - WANObjectCache::TTL_INDEFINITE: Cache forever |
||
| 771 | * - WANObjectCache::TTL_UNCACHEABLE: Do not cache at all |
||
| 772 | * @param callable $callback Value generation function |
||
| 773 | * @param array $opts Options map: |
||
| 774 | * - checkKeys: List of "check" keys. The key at $key will be seen as invalid when either |
||
| 775 | * touchCheckKey() or resetCheckKey() is called on any of these keys. |
||
| 776 | * Default: []. |
||
| 777 | * - lockTSE: If the key is tombstoned or expired (by checkKeys) less than this many seconds |
||
| 778 | * ago, then try to have a single thread handle cache regeneration at any given time. |
||
| 779 | * Other threads will try to use stale values if possible. If, on miss, the time since |
||
| 780 | * expiration is low, the assumption is that the key is hot and that a stampede is worth |
||
| 781 | * avoiding. Setting this above WANObjectCache::HOLDOFF_TTL makes no difference. The |
||
| 782 | * higher this is set, the higher the worst-case staleness can be. |
||
| 783 | * Use WANObjectCache::TSE_NONE to disable this logic. |
||
| 784 | * Default: WANObjectCache::TSE_NONE. |
||
| 785 | * - busyValue: If no value exists and another thread is currently regenerating it, use this |
||
| 786 | * as a fallback value (or a callback to generate such a value). This assures that cache |
||
| 787 | * stampedes cannot happen if the value falls out of cache. This can be used as insurance |
||
| 788 | * against cache regeneration becoming very slow for some reason (greater than the TTL). |
||
| 789 | * Default: null. |
||
| 790 | * - pcTTL: Process cache the value in this PHP instance for this many seconds. This avoids |
||
| 791 | * network I/O when a key is read several times. This will not cache when the callback |
||
| 792 | * returns false, however. Note that any purges will not be seen while process cached; |
||
| 793 | * since the callback should use replica DBs and they may be lagged or have snapshot |
||
| 794 | * isolation anyway, this should not typically matter. |
||
| 795 | * Default: WANObjectCache::TTL_UNCACHEABLE. |
||
| 796 | * - version: Integer version number. This allows for callers to make breaking changes to |
||
| 797 | * how values are stored while maintaining compatability and correct cache purges. New |
||
| 798 | * versions are stored alongside older versions concurrently. Avoid storing class objects |
||
| 799 | * however, as this reduces compatibility (due to serialization). |
||
| 800 | * Default: null. |
||
| 801 | * - hotTTR: Expected time-till-refresh for keys that average ~1 hit/second. |
||
| 802 | * This should be greater than "ageNew". Keys with higher hit rates will regenerate |
||
| 803 | * more often. This is useful when a popular key is changed but the cache purge was |
||
| 804 | * delayed or lost. Seldom used keys are rarely affected by this setting, unless an |
||
| 805 | * extremely low "hotTTR" value is passed in. |
||
| 806 | * Default: WANObjectCache::HOT_TTR. |
||
| 807 | * - lowTTL: Consider pre-emptive updates when the current TTL (seconds) of the key is less |
||
| 808 | * than this. It becomes more likely over time, becoming certain once the key is expired. |
||
| 809 | * Default: WANObjectCache::LOW_TTL. |
||
| 810 | * - ageNew: Consider popularity refreshes only once a key reaches this age in seconds. |
||
| 811 | * Default: WANObjectCache::AGE_NEW. |
||
| 812 | * @return mixed Value found or written to the key |
||
| 813 | * @note Callable type hints are not used to avoid class-autoloading |
||
| 814 | */ |
||
| 815 | final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [] ) { |
||
| 875 | |||
| 876 | /** |
||
| 877 | * Do the actual I/O for getWithSetCallback() when needed |
||
| 878 | * |
||
| 879 | * @see WANObjectCache::getWithSetCallback() |
||
| 880 | * |
||
| 881 | * @param string $key |
||
| 882 | * @param integer $ttl |
||
| 883 | * @param callback $callback |
||
| 884 | * @param array $opts Options map for getWithSetCallback() which also includes: |
||
| 885 | * - minTime: Treat values older than this UNIX timestamp as not existing. Default: null. |
||
| 886 | * @param float &$asOf Cache generation timestamp of returned value [returned] |
||
| 887 | * @return mixed |
||
| 888 | * @note Callable type hints are not used to avoid class-autoloading |
||
| 889 | */ |
||
| 890 | protected function doGetWithSetCallback( $key, $ttl, $callback, array $opts, &$asOf = null ) { |
||
| 991 | |||
| 992 | /** |
||
| 993 | * @see BagOStuff::makeKey() |
||
| 994 | * @param string ... Key component |
||
| 995 | * @return string |
||
| 996 | * @since 1.27 |
||
| 997 | */ |
||
| 998 | public function makeKey() { |
||
| 1001 | |||
| 1002 | /** |
||
| 1003 | * @see BagOStuff::makeGlobalKey() |
||
| 1004 | * @param string ... Key component |
||
| 1005 | * @return string |
||
| 1006 | * @since 1.27 |
||
| 1007 | */ |
||
| 1008 | public function makeGlobalKey() { |
||
| 1011 | |||
| 1012 | /** |
||
| 1013 | * Get the "last error" registered; clearLastError() should be called manually |
||
| 1014 | * @return int ERR_* class constant for the "last error" registry |
||
| 1015 | */ |
||
| 1016 | final public function getLastError() { |
||
| 1038 | |||
| 1039 | /** |
||
| 1040 | * Clear the "last error" registry |
||
| 1041 | */ |
||
| 1042 | final public function clearLastError() { |
||
| 1046 | |||
| 1047 | /** |
||
| 1048 | * Clear the in-process caches; useful for testing |
||
| 1049 | * |
||
| 1050 | * @since 1.27 |
||
| 1051 | */ |
||
| 1052 | public function clearProcessCache() { |
||
| 1055 | |||
| 1056 | /** |
||
| 1057 | * @param integer $flag ATTR_* class constant |
||
| 1058 | * @return integer QOS_* class constant |
||
| 1059 | * @since 1.28 |
||
| 1060 | */ |
||
| 1061 | public function getQoS( $flag ) { |
||
| 1064 | |||
| 1065 | /** |
||
| 1066 | * Get a TTL that is higher for objects that have not changed recently |
||
| 1067 | * |
||
| 1068 | * This is useful for keys that get explicit purges and DB or purge relay |
||
| 1069 | * lag is a potential concern (especially how it interacts with CDN cache) |
||
| 1070 | * |
||
| 1071 | * Example usage: |
||
| 1072 | * @code |
||
| 1073 | * // Last-modified time of page |
||
| 1074 | * $mtime = wfTimestamp( TS_UNIX, $page->getTimestamp() ); |
||
| 1075 | * // Get adjusted TTL. If $mtime is 3600 seconds ago and $minTTL/$factor left at |
||
| 1076 | * // defaults, then $ttl is 3600 * .2 = 720. If $minTTL was greater than 720, then |
||
| 1077 | * // $ttl would be $minTTL. If $maxTTL was smaller than 720, $ttl would be $maxTTL. |
||
| 1078 | * $ttl = $cache->adaptiveTTL( $mtime, $cache::TTL_DAY ); |
||
| 1079 | * @endcode |
||
| 1080 | * |
||
| 1081 | * @param integer|float $mtime UNIX timestamp |
||
| 1082 | * @param integer $maxTTL Maximum TTL (seconds) |
||
| 1083 | * @param integer $minTTL Minimum TTL (seconds); Default: 30 |
||
| 1084 | * @param float $factor Value in the range (0,1); Default: .2 |
||
| 1085 | * @return integer Adaptive TTL |
||
| 1086 | * @since 1.28 |
||
| 1087 | */ |
||
| 1088 | public function adaptiveTTL( $mtime, $maxTTL, $minTTL = 30, $factor = .2 ) { |
||
| 1101 | |||
| 1102 | /** |
||
| 1103 | * Do the actual async bus purge of a key |
||
| 1104 | * |
||
| 1105 | * This must set the key to "PURGED:<UNIX timestamp>:<holdoff>" |
||
| 1106 | * |
||
| 1107 | * @param string $key Cache key |
||
| 1108 | * @param integer $ttl How long to keep the tombstone [seconds] |
||
| 1109 | * @param integer $holdoff HOLDOFF_* constant controlling how long to ignore sets for this key |
||
| 1110 | * @return bool Success |
||
| 1111 | */ |
||
| 1112 | protected function relayPurge( $key, $ttl, $holdoff ) { |
||
| 1136 | |||
| 1137 | /** |
||
| 1138 | * Do the actual async bus delete of a key |
||
| 1139 | * |
||
| 1140 | * @param string $key Cache key |
||
| 1141 | * @return bool Success |
||
| 1142 | */ |
||
| 1143 | protected function relayDelete( $key ) { |
||
| 1161 | |||
| 1162 | /** |
||
| 1163 | * Check if a key should be regenerated (using random probability) |
||
| 1164 | * |
||
| 1165 | * This returns false if $curTTL >= $lowTTL. Otherwise, the chance |
||
| 1166 | * of returning true increases steadily from 0% to 100% as the $curTTL |
||
| 1167 | * moves from $lowTTL to 0 seconds. This handles widely varying |
||
| 1168 | * levels of cache access traffic. |
||
| 1169 | * |
||
| 1170 | * @param float $curTTL Approximate TTL left on the key if present |
||
| 1171 | * @param float $lowTTL Consider a refresh when $curTTL is less than this |
||
| 1172 | * @return bool |
||
| 1173 | */ |
||
| 1174 | protected function worthRefreshExpiring( $curTTL, $lowTTL ) { |
||
| 1185 | |||
| 1186 | /** |
||
| 1187 | * Check if a key is due for randomized regeneration due to its popularity |
||
| 1188 | * |
||
| 1189 | * This is used so that popular keys can preemptively refresh themselves for higher |
||
| 1190 | * consistency (especially in the case of purge loss/delay). Unpopular keys can remain |
||
| 1191 | * in cache with their high nominal TTL. This means popular keys keep good consistency, |
||
| 1192 | * whether the data changes frequently or not, and long-tail keys get to stay in cache |
||
| 1193 | * and get hits too. Similar to worthRefreshExpiring(), randomization is used. |
||
| 1194 | * |
||
| 1195 | * @param float $asOf UNIX timestamp of the value |
||
| 1196 | * @param integer $ageNew Age of key when this might recommend refreshing (seconds) |
||
| 1197 | * @param integer $timeTillRefresh Age of key when it should be refreshed if popular (seconds) |
||
| 1198 | * @return bool |
||
| 1199 | */ |
||
| 1200 | protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh ) { |
||
| 1219 | |||
| 1220 | /** |
||
| 1221 | * Check whether $value is appropriately versioned and not older than $minTime (if set) |
||
| 1222 | * |
||
| 1223 | * @param array $value |
||
| 1224 | * @param bool $versioned |
||
| 1225 | * @param float $asOf The time $value was generated |
||
| 1226 | * @param float $minTime The last time the main value was generated (0.0 if unknown) |
||
| 1227 | * @return bool |
||
| 1228 | */ |
||
| 1229 | protected function isValid( $value, $versioned, $asOf, $minTime ) { |
||
| 1238 | |||
| 1239 | /** |
||
| 1240 | * Do not use this method outside WANObjectCache |
||
| 1241 | * |
||
| 1242 | * @param mixed $value |
||
| 1243 | * @param integer $ttl [0=forever] |
||
| 1244 | * @param float $now Unix Current timestamp just before calling set() |
||
| 1245 | * @return array |
||
| 1246 | */ |
||
| 1247 | protected function wrap( $value, $ttl, $now ) { |
||
| 1255 | |||
| 1256 | /** |
||
| 1257 | * Do not use this method outside WANObjectCache |
||
| 1258 | * |
||
| 1259 | * @param array|string|bool $wrapped |
||
| 1260 | * @param float $now Unix Current timestamp (preferrably pre-query) |
||
| 1261 | * @return array (mixed; false if absent/invalid, current time left) |
||
| 1262 | */ |
||
| 1263 | protected function unwrap( $wrapped, $now ) { |
||
| 1295 | |||
| 1296 | /** |
||
| 1297 | * @param array $keys |
||
| 1298 | * @param string $prefix |
||
| 1299 | * @return string[] |
||
| 1300 | */ |
||
| 1301 | protected static function prefixCacheKeys( array $keys, $prefix ) { |
||
| 1309 | |||
| 1310 | /** |
||
| 1311 | * @param string $value Wrapped value like "PURGED:<timestamp>:<holdoff>" |
||
| 1312 | * @return array|bool Array containing a UNIX timestamp (float) and holdoff period (integer), |
||
| 1313 | * or false if value isn't a valid purge value |
||
| 1314 | */ |
||
| 1315 | protected static function parsePurgeValue( $value ) { |
||
| 1334 | |||
| 1335 | /** |
||
| 1336 | * @param float $timestamp |
||
| 1337 | * @param int $holdoff In seconds |
||
| 1338 | * @return string Wrapped purge value |
||
| 1339 | */ |
||
| 1340 | protected function makePurgeValue( $timestamp, $holdoff ) { |
||
| 1343 | } |
||
| 1344 |