Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
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 |
||
| 67 | class WANObjectCache implements IExpiringStore, LoggerAwareInterface { |
||
| 68 | /** @var BagOStuff The local datacenter cache */ |
||
| 69 | protected $cache; |
||
| 70 | /** @var HashBagOStuff Script instance PHP cache */ |
||
| 71 | protected $procCache; |
||
| 72 | /** @var string Cache pool name */ |
||
| 73 | protected $pool; |
||
| 74 | /** @var EventRelayer Bus that handles purge broadcasts */ |
||
| 75 | protected $relayer; |
||
| 76 | /** @var LoggerInterface */ |
||
| 77 | protected $logger; |
||
| 78 | |||
| 79 | /** @var int ERR_* constant for the "last error" registry */ |
||
| 80 | protected $lastRelayError = self::ERR_NONE; |
||
| 81 | |||
| 82 | /** Max time expected to pass between delete() and DB commit finishing */ |
||
| 83 | const MAX_COMMIT_DELAY = 3; |
||
| 84 | /** Max replication+snapshot lag before applying TTL_LAGGED or disallowing set() */ |
||
| 85 | const MAX_READ_LAG = 7; |
||
| 86 | /** Seconds to tombstone keys on delete() */ |
||
| 87 | const HOLDOFF_TTL = 11; // MAX_COMMIT_DELAY + MAX_READ_LAG + 1 |
||
| 88 | |||
| 89 | /** Seconds to keep dependency purge keys around */ |
||
| 90 | const CHECK_KEY_TTL = self::TTL_YEAR; |
||
| 91 | /** Seconds to keep lock keys around */ |
||
| 92 | const LOCK_TTL = 10; |
||
| 93 | /** Default remaining TTL at which to consider pre-emptive regeneration */ |
||
| 94 | const LOW_TTL = 30; |
||
| 95 | /** Default time-since-expiry on a miss that makes a key "hot" */ |
||
| 96 | const LOCK_TSE = 1; |
||
| 97 | |||
| 98 | /** Idiom for getWithSetCallback() callbacks to avoid calling set() */ |
||
| 99 | const TTL_UNCACHEABLE = -1; |
||
| 100 | /** Idiom for getWithSetCallback() callbacks to 'lockTSE' logic */ |
||
| 101 | const TSE_NONE = -1; |
||
| 102 | /** Max TTL to store keys when a data sourced is lagged */ |
||
| 103 | const TTL_LAGGED = 30; |
||
| 104 | /** Idiom for delete() for "no hold-off" */ |
||
| 105 | const HOLDOFF_NONE = 0; |
||
| 106 | |||
| 107 | /** Tiny negative float to use when CTL comes up >= 0 due to clock skew */ |
||
| 108 | const TINY_NEGATIVE = -0.000001; |
||
| 109 | |||
| 110 | /** Cache format version number */ |
||
| 111 | const VERSION = 1; |
||
| 112 | |||
| 113 | const FLD_VERSION = 0; |
||
| 114 | const FLD_VALUE = 1; |
||
| 115 | const FLD_TTL = 2; |
||
| 116 | const FLD_TIME = 3; |
||
| 117 | const FLD_FLAGS = 4; |
||
| 118 | const FLD_HOLDOFF = 5; |
||
| 119 | |||
| 120 | /** @var integer Treat this value as expired-on-arrival */ |
||
| 121 | const FLG_STALE = 1; |
||
| 122 | |||
| 123 | const ERR_NONE = 0; // no error |
||
| 124 | const ERR_NO_RESPONSE = 1; // no response |
||
| 125 | const ERR_UNREACHABLE = 2; // can't connect |
||
| 126 | const ERR_UNEXPECTED = 3; // response gave some error |
||
| 127 | const ERR_RELAY = 4; // relay broadcast failed |
||
| 128 | |||
| 129 | const VALUE_KEY_PREFIX = 'WANCache:v:'; |
||
| 130 | const STASH_KEY_PREFIX = 'WANCache:s:'; |
||
| 131 | const TIME_KEY_PREFIX = 'WANCache:t:'; |
||
| 132 | |||
| 133 | const PURGE_VAL_PREFIX = 'PURGED:'; |
||
| 134 | |||
| 135 | const MAX_PC_KEYS = 1000; // max keys to keep in process cache |
||
| 136 | |||
| 137 | /** |
||
| 138 | * @param array $params |
||
| 139 | * - cache : BagOStuff object |
||
| 140 | * - pool : pool name |
||
| 141 | * - relayer : EventRelayer object |
||
| 142 | * - logger : LoggerInterface object |
||
| 143 | */ |
||
| 144 | public function __construct( array $params ) { |
||
| 151 | |||
| 152 | public function setLogger( LoggerInterface $logger ) { |
||
| 155 | |||
| 156 | /** |
||
| 157 | * Get an instance that wraps EmptyBagOStuff |
||
| 158 | * |
||
| 159 | * @return WANObjectCache |
||
| 160 | */ |
||
| 161 | public static function newEmpty() { |
||
| 168 | |||
| 169 | /** |
||
| 170 | * Fetch the value of a key from cache |
||
| 171 | * |
||
| 172 | * If supplied, $curTTL is set to the remaining TTL (current time left): |
||
| 173 | * - a) INF; if $key exists, has no TTL, and is not expired by $checkKeys |
||
| 174 | * - b) float (>=0); if $key exists, has a TTL, and is not expired by $checkKeys |
||
| 175 | * - c) float (<0); if $key is tombstoned, stale, or existing but expired by $checkKeys |
||
| 176 | * - d) null; if $key does not exist and is not tombstoned |
||
| 177 | * |
||
| 178 | * If a key is tombstoned, $curTTL will reflect the time since delete(). |
||
| 179 | * |
||
| 180 | * The timestamp of $key will be checked against the last-purge timestamp |
||
| 181 | * of each of $checkKeys. Those $checkKeys not in cache will have the last-purge |
||
| 182 | * initialized to the current timestamp. If any of $checkKeys have a timestamp |
||
| 183 | * greater than that of $key, then $curTTL will reflect how long ago $key |
||
| 184 | * became invalid. Callers can use $curTTL to know when the value is stale. |
||
| 185 | * The $checkKeys parameter allow mass invalidations by updating a single key: |
||
| 186 | * - a) Each "check" key represents "last purged" of some source data |
||
| 187 | * - b) Callers pass in relevant "check" keys as $checkKeys in get() |
||
| 188 | * - c) When the source data that "check" keys represent changes, |
||
| 189 | * the touchCheckKey() method is called on them |
||
| 190 | * |
||
| 191 | * Source data entities might exists in a DB that uses snapshot isolation |
||
| 192 | * (e.g. the default REPEATABLE-READ in innoDB). Even for mutable data, that |
||
| 193 | * isolation can largely be maintained by doing the following: |
||
| 194 | * - a) Calling delete() on entity change *and* creation, before DB commit |
||
| 195 | * - b) Keeping transaction duration shorter than delete() hold-off TTL |
||
| 196 | * |
||
| 197 | * However, pre-snapshot values might still be seen if an update was made |
||
| 198 | * in a remote datacenter but the purge from delete() didn't relay yet. |
||
| 199 | * |
||
| 200 | * Consider using getWithSetCallback() instead of get() and set() cycles. |
||
| 201 | * That method has cache slam avoiding features for hot/expensive keys. |
||
| 202 | * |
||
| 203 | * @param string $key Cache key |
||
| 204 | * @param mixed $curTTL Approximate TTL left on the key if present [returned] |
||
| 205 | * @param array $checkKeys List of "check" keys |
||
| 206 | * @return mixed Value of cache key or false on failure |
||
| 207 | */ |
||
| 208 | final public function get( $key, &$curTTL = null, array $checkKeys = [] ) { |
||
| 215 | |||
| 216 | /** |
||
| 217 | * Fetch the value of several keys from cache |
||
| 218 | * |
||
| 219 | * @see WANObjectCache::get() |
||
| 220 | * |
||
| 221 | * @param array $keys List of cache keys |
||
| 222 | * @param array $curTTLs Map of (key => approximate TTL left) for existing keys [returned] |
||
| 223 | * @param array $checkKeys List of check keys to apply to all $keys. May also apply "check" |
||
| 224 | * keys to specific cache keys only by using cache keys as keys in the $checkKeys array. |
||
| 225 | * @return array Map of (key => value) for keys that exist |
||
| 226 | */ |
||
| 227 | final public function getMulti( |
||
| 298 | |||
| 299 | /** |
||
| 300 | * @since 1.27 |
||
| 301 | * @param array $timeKeys List of prefixed time check keys |
||
| 302 | * @param array $wrappedValues |
||
| 303 | * @param float $now |
||
| 304 | * @return array List of purge value arrays |
||
| 305 | */ |
||
| 306 | private function processCheckKeys( array $timeKeys, array $wrappedValues, $now ) { |
||
| 322 | |||
| 323 | /** |
||
| 324 | * Set the value of a key in cache |
||
| 325 | * |
||
| 326 | * Simply calling this method when source data changes is not valid because |
||
| 327 | * the changes do not replicate to the other WAN sites. In that case, delete() |
||
| 328 | * should be used instead. This method is intended for use on cache misses. |
||
| 329 | * |
||
| 330 | * If the data was read from a snapshot-isolated transactions (e.g. the default |
||
| 331 | * REPEATABLE-READ in innoDB), use 'since' to avoid the following race condition: |
||
| 332 | * - a) T1 starts |
||
| 333 | * - b) T2 updates a row, calls delete(), and commits |
||
| 334 | * - c) The HOLDOFF_TTL passes, expiring the delete() tombstone |
||
| 335 | * - d) T1 reads the row and calls set() due to a cache miss |
||
| 336 | * - e) Stale value is stuck in cache |
||
| 337 | * |
||
| 338 | * Setting 'lag' and 'since' help avoids keys getting stuck in stale states. |
||
| 339 | * |
||
| 340 | * Example usage: |
||
| 341 | * @code |
||
| 342 | * $dbr = wfGetDB( DB_SLAVE ); |
||
| 343 | * $setOpts = Database::getCacheSetOptions( $dbr ); |
||
| 344 | * // Fetch the row from the DB |
||
| 345 | * $row = $dbr->selectRow( ... ); |
||
| 346 | * $key = $cache->makeKey( 'building', $buildingId ); |
||
| 347 | * $cache->set( $key, $row, $cache::TTL_DAY, $setOpts ); |
||
| 348 | * @endcode |
||
| 349 | * |
||
| 350 | * @param string $key Cache key |
||
| 351 | * @param mixed $value |
||
| 352 | * @param integer $ttl Seconds to live. Special values are: |
||
| 353 | * - WANObjectCache::TTL_INDEFINITE: Cache forever |
||
| 354 | * @param array $opts Options map: |
||
| 355 | * - lag : Seconds of slave lag. Typically, this is either the slave lag |
||
| 356 | * before the data was read or, if applicable, the slave lag before |
||
| 357 | * the snapshot-isolated transaction the data was read from started. |
||
| 358 | * Default: 0 seconds |
||
| 359 | * - since : UNIX timestamp of the data in $value. Typically, this is either |
||
| 360 | * the current time the data was read or (if applicable) the time when |
||
| 361 | * the snapshot-isolated transaction the data was read from started. |
||
| 362 | * Default: 0 seconds |
||
| 363 | * - pending : Whether this data is possibly from an uncommitted write transaction. |
||
| 364 | * Generally, other threads should not see values from the future and |
||
| 365 | * they certainly should not see ones that ended up getting rolled back. |
||
| 366 | * Default: false |
||
| 367 | * - lockTSE : if excessive replication/snapshot lag is detected, then store the value |
||
| 368 | * with this TTL and flag it as stale. This is only useful if the reads for |
||
| 369 | * this key use getWithSetCallback() with "lockTSE" set. |
||
| 370 | * Default: WANObjectCache::TSE_NONE |
||
| 371 | * @return bool Success |
||
| 372 | */ |
||
| 373 | final public function set( $key, $value, $ttl = 0, array $opts = [] ) { |
||
| 420 | |||
| 421 | /** |
||
| 422 | * Purge a key from all datacenters |
||
| 423 | * |
||
| 424 | * This should only be called when the underlying data (being cached) |
||
| 425 | * changes in a significant way. This deletes the key and starts a hold-off |
||
| 426 | * period where the key cannot be written to for a few seconds (HOLDOFF_TTL). |
||
| 427 | * This is done to avoid the following race condition: |
||
| 428 | * - a) Some DB data changes and delete() is called on a corresponding key |
||
| 429 | * - b) A request refills the key with a stale value from a lagged DB |
||
| 430 | * - c) The stale value is stuck there until the key is expired/evicted |
||
| 431 | * |
||
| 432 | * This is implemented by storing a special "tombstone" value at the cache |
||
| 433 | * key that this class recognizes; get() calls will return false for the key |
||
| 434 | * and any set() calls will refuse to replace tombstone values at the key. |
||
| 435 | * For this to always avoid stale value writes, the following must hold: |
||
| 436 | * - a) Replication lag is bounded to being less than HOLDOFF_TTL; or |
||
| 437 | * - b) If lag is higher, the DB will have gone into read-only mode already |
||
| 438 | * |
||
| 439 | * Note that set() can also be lag-aware and lower the TTL if it's high. |
||
| 440 | * |
||
| 441 | * When using potentially long-running ACID transactions, a good pattern is |
||
| 442 | * to use a pre-commit hook to issue the delete. This means that immediately |
||
| 443 | * after commit, callers will see the tombstone in cache in the local datacenter |
||
| 444 | * and in the others upon relay. It also avoids the following race condition: |
||
| 445 | * - a) T1 begins, changes a row, and calls delete() |
||
| 446 | * - b) The HOLDOFF_TTL passes, expiring the delete() tombstone |
||
| 447 | * - c) T2 starts, reads the row and calls set() due to a cache miss |
||
| 448 | * - d) T1 finally commits |
||
| 449 | * - e) Stale value is stuck in cache |
||
| 450 | * |
||
| 451 | * Example usage: |
||
| 452 | * @code |
||
| 453 | * $dbw->begin( __METHOD__ ); // start of request |
||
| 454 | * ... <execute some stuff> ... |
||
| 455 | * // Update the row in the DB |
||
| 456 | * $dbw->update( ... ); |
||
| 457 | * $key = $cache->makeKey( 'homes', $homeId ); |
||
| 458 | * // Purge the corresponding cache entry just before committing |
||
| 459 | * $dbw->onTransactionPreCommitOrIdle( function() use ( $cache, $key ) { |
||
| 460 | * $cache->delete( $key ); |
||
| 461 | * } ); |
||
| 462 | * ... <execute some stuff> ... |
||
| 463 | * $dbw->commit( __METHOD__ ); // end of request |
||
| 464 | * @endcode |
||
| 465 | * |
||
| 466 | * The $ttl parameter can be used when purging values that have not actually changed |
||
| 467 | * recently. For example, a cleanup script to purge cache entries does not really need |
||
| 468 | * a hold-off period, so it can use HOLDOFF_NONE. Likewise for user-requested purge. |
||
| 469 | * Note that $ttl limits the effective range of 'lockTSE' for getWithSetCallback(). |
||
| 470 | * |
||
| 471 | * If called twice on the same key, then the last hold-off TTL takes precedence. For |
||
| 472 | * idempotence, the $ttl should not vary for different delete() calls on the same key. |
||
| 473 | * |
||
| 474 | * @param string $key Cache key |
||
| 475 | * @param integer $ttl Tombstone TTL; Default: WANObjectCache::HOLDOFF_TTL |
||
| 476 | * @return bool True if the item was purged or not found, false on failure |
||
| 477 | */ |
||
| 478 | final public function delete( $key, $ttl = self::HOLDOFF_TTL ) { |
||
| 498 | |||
| 499 | /** |
||
| 500 | * Fetch the value of a timestamp "check" key |
||
| 501 | * |
||
| 502 | * The key will be *initialized* to the current time if not set, |
||
| 503 | * so only call this method if this behavior is actually desired |
||
| 504 | * |
||
| 505 | * The timestamp can be used to check whether a cached value is valid. |
||
| 506 | * Callers should not assume that this returns the same timestamp in |
||
| 507 | * all datacenters due to relay delays. |
||
| 508 | * |
||
| 509 | * The level of staleness can roughly be estimated from this key, but |
||
| 510 | * if the key was evicted from cache, such calculations may show the |
||
| 511 | * time since expiry as ~0 seconds. |
||
| 512 | * |
||
| 513 | * Note that "check" keys won't collide with other regular keys. |
||
| 514 | * |
||
| 515 | * @param string $key |
||
| 516 | * @return float UNIX timestamp of the check key |
||
| 517 | */ |
||
| 518 | final public function getCheckKeyTime( $key ) { |
||
| 536 | |||
| 537 | /** |
||
| 538 | * Purge a "check" key from all datacenters, invalidating keys that use it |
||
| 539 | * |
||
| 540 | * This should only be called when the underlying data (being cached) |
||
| 541 | * changes in a significant way, and it is impractical to call delete() |
||
| 542 | * on all keys that should be changed. When get() is called on those |
||
| 543 | * keys, the relevant "check" keys must be supplied for this to work. |
||
| 544 | * |
||
| 545 | * The "check" key essentially represents a last-modified field. |
||
| 546 | * When touched, keys using it via get(), getMulti(), or getWithSetCallback() |
||
| 547 | * will be invalidated. It is treated as being HOLDOFF_TTL seconds in the future |
||
| 548 | * by those methods to avoid race conditions where dependent keys get updated |
||
| 549 | * with stale values (e.g. from a DB slave). |
||
| 550 | * |
||
| 551 | * This is typically useful for keys with hardcoded names or in some cases |
||
| 552 | * dynamically generated names where a low number of combinations exist. |
||
| 553 | * When a few important keys get a large number of hits, a high cache |
||
| 554 | * time is usually desired as well as "lockTSE" logic. The resetCheckKey() |
||
| 555 | * method is less appropriate in such cases since the "time since expiry" |
||
| 556 | * cannot be inferred. |
||
| 557 | * |
||
| 558 | * Note that "check" keys won't collide with other regular keys. |
||
| 559 | * |
||
| 560 | * @see WANObjectCache::get() |
||
| 561 | * @see WANObjectCache::getWithSetCallback() |
||
| 562 | * @see WANObjectCache::resetCheckKey() |
||
| 563 | * |
||
| 564 | * @param string $key Cache key |
||
| 565 | * @param int $holdoff HOLDOFF_TTL or HOLDOFF_NONE constant |
||
| 566 | * @return bool True if the item was purged or not found, false on failure |
||
| 567 | */ |
||
| 568 | final public function touchCheckKey( $key, $holdoff = self::HOLDOFF_TTL ) { |
||
| 578 | |||
| 579 | /** |
||
| 580 | * Delete a "check" key from all datacenters, invalidating keys that use it |
||
| 581 | * |
||
| 582 | * This is similar to touchCheckKey() in that keys using it via get(), getMulti(), |
||
| 583 | * or getWithSetCallback() will be invalidated. The differences are: |
||
| 584 | * - a) The timestamp will be deleted from all caches and lazily |
||
| 585 | * re-initialized when accessed (rather than set everywhere) |
||
| 586 | * - b) Thus, dependent keys will be known to be invalid, but not |
||
| 587 | * for how long (they are treated as "just" purged), which |
||
| 588 | * effects any lockTSE logic in getWithSetCallback() |
||
| 589 | * |
||
| 590 | * The advantage is that this does not place high TTL keys on every cache |
||
| 591 | * server, making it better for code that will cache many different keys |
||
| 592 | * and either does not use lockTSE or uses a low enough TTL anyway. |
||
| 593 | * |
||
| 594 | * This is typically useful for keys with dynamically generated names |
||
| 595 | * where a high number of combinations exist. |
||
| 596 | * |
||
| 597 | * Note that "check" keys won't collide with other regular keys. |
||
| 598 | * |
||
| 599 | * @see WANObjectCache::get() |
||
| 600 | * @see WANObjectCache::getWithSetCallback() |
||
| 601 | * @see WANObjectCache::touchCheckKey() |
||
| 602 | * |
||
| 603 | * @param string $key Cache key |
||
| 604 | * @return bool True if the item was purged or not found, false on failure |
||
| 605 | */ |
||
| 606 | final public function resetCheckKey( $key ) { |
||
| 613 | |||
| 614 | /** |
||
| 615 | * Method to fetch/regenerate cache keys |
||
| 616 | * |
||
| 617 | * On cache miss, the key will be set to the callback result via set() |
||
| 618 | * (unless the callback returns false) and that result will be returned. |
||
| 619 | * The arguments supplied to the callback are: |
||
| 620 | * - $oldValue : current cache value or false if not present |
||
| 621 | * - &$ttl : a reference to the TTL which can be altered |
||
| 622 | * - &$setOpts : a reference to options for set() which can be altered |
||
| 623 | * |
||
| 624 | * It is strongly recommended to set the 'lag' and 'since' fields to avoid race conditions |
||
| 625 | * that can cause stale values to get stuck at keys. Usually, callbacks ignore the current |
||
| 626 | * value, but it can be used to maintain "most recent X" values that come from time or |
||
| 627 | * sequence based source data, provided that the "as of" id/time is tracked. Note that |
||
| 628 | * preemptive regeneration and $checkKeys can result in a non-false current value. |
||
| 629 | * |
||
| 630 | * Usage of $checkKeys is similar to get() and getMulti(). However, rather than the caller |
||
| 631 | * having to inspect a "current time left" variable (e.g. $curTTL, $curTTLs), a cache |
||
| 632 | * regeneration will automatically be triggered using the callback. |
||
| 633 | * |
||
| 634 | * The simplest way to avoid stampedes for hot keys is to use |
||
| 635 | * the 'lockTSE' option in $opts. If cache purges are needed, also: |
||
| 636 | * - a) Pass $key into $checkKeys |
||
| 637 | * - b) Use touchCheckKey( $key ) instead of delete( $key ) |
||
| 638 | * |
||
| 639 | * Example usage (typical key): |
||
| 640 | * @code |
||
| 641 | * $catInfo = $cache->getWithSetCallback( |
||
| 642 | * // Key to store the cached value under |
||
| 643 | * $cache->makeKey( 'cat-attributes', $catId ), |
||
| 644 | * // Time-to-live (in seconds) |
||
| 645 | * $cache::TTL_MINUTE, |
||
| 646 | * // Function that derives the new key value |
||
| 647 | * function ( $oldValue, &$ttl, array &$setOpts ) { |
||
| 648 | * $dbr = wfGetDB( DB_SLAVE ); |
||
| 649 | * // Account for any snapshot/slave lag |
||
| 650 | * $setOpts += Database::getCacheSetOptions( $dbr ); |
||
| 651 | * |
||
| 652 | * return $dbr->selectRow( ... ); |
||
| 653 | * } |
||
| 654 | * ); |
||
| 655 | * @endcode |
||
| 656 | * |
||
| 657 | * Example usage (key that is expensive and hot): |
||
| 658 | * @code |
||
| 659 | * $catConfig = $cache->getWithSetCallback( |
||
| 660 | * // Key to store the cached value under |
||
| 661 | * $cache->makeKey( 'site-cat-config' ), |
||
| 662 | * // Time-to-live (in seconds) |
||
| 663 | * $cache::TTL_DAY, |
||
| 664 | * // Function that derives the new key value |
||
| 665 | * function ( $oldValue, &$ttl, array &$setOpts ) { |
||
| 666 | * $dbr = wfGetDB( DB_SLAVE ); |
||
| 667 | * // Account for any snapshot/slave lag |
||
| 668 | * $setOpts += Database::getCacheSetOptions( $dbr ); |
||
| 669 | * |
||
| 670 | * return CatConfig::newFromRow( $dbr->selectRow( ... ) ); |
||
| 671 | * }, |
||
| 672 | * array( |
||
| 673 | * // Calling touchCheckKey() on this key invalidates the cache |
||
| 674 | * 'checkKeys' => array( $cache->makeKey( 'site-cat-config' ) ), |
||
| 675 | * // Try to only let one datacenter thread manage cache updates at a time |
||
| 676 | * 'lockTSE' => 30 |
||
| 677 | * ) |
||
| 678 | * ); |
||
| 679 | * @endcode |
||
| 680 | * |
||
| 681 | * Example usage (key with dynamic dependencies): |
||
| 682 | * @code |
||
| 683 | * $catState = $cache->getWithSetCallback( |
||
| 684 | * // Key to store the cached value under |
||
| 685 | * $cache->makeKey( 'cat-state', $cat->getId() ), |
||
| 686 | * // Time-to-live (seconds) |
||
| 687 | * $cache::TTL_HOUR, |
||
| 688 | * // Function that derives the new key value |
||
| 689 | * function ( $oldValue, &$ttl, array &$setOpts ) { |
||
| 690 | * // Determine new value from the DB |
||
| 691 | * $dbr = wfGetDB( DB_SLAVE ); |
||
| 692 | * // Account for any snapshot/slave lag |
||
| 693 | * $setOpts += Database::getCacheSetOptions( $dbr ); |
||
| 694 | * |
||
| 695 | * return CatState::newFromResults( $dbr->select( ... ) ); |
||
| 696 | * }, |
||
| 697 | * array( |
||
| 698 | * // The "check" keys that represent things the value depends on; |
||
| 699 | * // Calling touchCheckKey() on any of them invalidates the cache |
||
| 700 | * 'checkKeys' => array( |
||
| 701 | * $cache->makeKey( 'sustenance-bowls', $cat->getRoomId() ), |
||
| 702 | * $cache->makeKey( 'people-present', $cat->getHouseId() ), |
||
| 703 | * $cache->makeKey( 'cat-laws', $cat->getCityId() ), |
||
| 704 | * ) |
||
| 705 | * ) |
||
| 706 | * ); |
||
| 707 | * @endcode |
||
| 708 | * |
||
| 709 | * Example usage (hot key holding most recent 100 events): |
||
| 710 | * @code |
||
| 711 | * $lastCatActions = $cache->getWithSetCallback( |
||
| 712 | * // Key to store the cached value under |
||
| 713 | * $cache->makeKey( 'cat-last-actions', 100 ), |
||
| 714 | * // Time-to-live (in seconds) |
||
| 715 | * 10, |
||
| 716 | * // Function that derives the new key value |
||
| 717 | * function ( $oldValue, &$ttl, array &$setOpts ) { |
||
| 718 | * $dbr = wfGetDB( DB_SLAVE ); |
||
| 719 | * // Account for any snapshot/slave lag |
||
| 720 | * $setOpts += Database::getCacheSetOptions( $dbr ); |
||
| 721 | * |
||
| 722 | * // Start off with the last cached list |
||
| 723 | * $list = $oldValue ?: array(); |
||
| 724 | * // Fetch the last 100 relevant rows in descending order; |
||
| 725 | * // only fetch rows newer than $list[0] to reduce scanning |
||
| 726 | * $rows = iterator_to_array( $dbr->select( ... ) ); |
||
| 727 | * // Merge them and get the new "last 100" rows |
||
| 728 | * return array_slice( array_merge( $new, $list ), 0, 100 ); |
||
| 729 | * }, |
||
| 730 | * // Try to only let one datacenter thread manage cache updates at a time |
||
| 731 | * array( 'lockTSE' => 30 ) |
||
| 732 | * ); |
||
| 733 | * @endcode |
||
| 734 | * |
||
| 735 | * @see WANObjectCache::get() |
||
| 736 | * @see WANObjectCache::set() |
||
| 737 | * |
||
| 738 | * @param string $key Cache key |
||
| 739 | * @param integer $ttl Seconds to live for key updates. Special values are: |
||
| 740 | * - WANObjectCache::TTL_INDEFINITE: Cache forever |
||
| 741 | * - WANObjectCache::TTL_UNCACHEABLE: Do not cache at all |
||
| 742 | * @param callable $callback Value generation function |
||
| 743 | * @param array $opts Options map: |
||
| 744 | * - checkKeys: List of "check" keys. The key at $key will be seen as invalid when either |
||
| 745 | * touchCheckKey() or resetCheckKey() is called on any of these keys. |
||
| 746 | * - lowTTL: Consider pre-emptive updates when the current TTL (sec) of the key is less than |
||
| 747 | * this. It becomes more likely over time, becoming a certainty once the key is expired. |
||
| 748 | * Default: WANObjectCache::LOW_TTL seconds. |
||
| 749 | * - lockTSE: If the key is tombstoned or expired (by checkKeys) less than this many seconds |
||
| 750 | * ago, then try to have a single thread handle cache regeneration at any given time. |
||
| 751 | * Other threads will try to use stale values if possible. If, on miss, the time since |
||
| 752 | * expiration is low, the assumption is that the key is hot and that a stampede is worth |
||
| 753 | * avoiding. Setting this above WANObjectCache::HOLDOFF_TTL makes no difference. The |
||
| 754 | * higher this is set, the higher the worst-case staleness can be. |
||
| 755 | * Use WANObjectCache::TSE_NONE to disable this logic. |
||
| 756 | * Default: WANObjectCache::TSE_NONE. |
||
| 757 | * - pcTTL : process cache the value in this PHP instance with this TTL. This avoids |
||
| 758 | * network I/O when a key is read several times. This will not cache if the callback |
||
| 759 | * returns false however. Note that any purges will not be seen while process cached; |
||
| 760 | * since the callback should use slave DBs and they may be lagged or have snapshot |
||
| 761 | * isolation anyway, this should not typically matter. |
||
| 762 | * Default: WANObjectCache::TTL_UNCACHEABLE. |
||
| 763 | * @return mixed Value to use for the key |
||
| 764 | */ |
||
| 765 | final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [] ) { |
||
| 782 | |||
| 783 | /** |
||
| 784 | * Do the actual I/O for getWithSetCallback() when needed |
||
| 785 | * |
||
| 786 | * @see WANObjectCache::getWithSetCallback() |
||
| 787 | * |
||
| 788 | * @param string $key |
||
| 789 | * @param integer $ttl |
||
| 790 | * @param callback $callback |
||
| 791 | * @param array $opts |
||
| 792 | * @return mixed |
||
| 793 | */ |
||
| 794 | protected function doGetWithSetCallback( $key, $ttl, $callback, array $opts ) { |
||
| 865 | |||
| 866 | /** |
||
| 867 | * @see BagOStuff::makeKey() |
||
| 868 | * @param string ... Key component |
||
| 869 | * @return string |
||
| 870 | * @since 1.27 |
||
| 871 | */ |
||
| 872 | public function makeKey() { |
||
| 875 | |||
| 876 | /** |
||
| 877 | * @see BagOStuff::makeGlobalKey() |
||
| 878 | * @param string ... Key component |
||
| 879 | * @return string |
||
| 880 | * @since 1.27 |
||
| 881 | */ |
||
| 882 | public function makeGlobalKey() { |
||
| 885 | |||
| 886 | /** |
||
| 887 | * Get the "last error" registered; clearLastError() should be called manually |
||
| 888 | * @return int ERR_* constant for the "last error" registry |
||
| 889 | */ |
||
| 890 | final public function getLastError() { |
||
| 912 | |||
| 913 | /** |
||
| 914 | * Clear the "last error" registry |
||
| 915 | */ |
||
| 916 | final public function clearLastError() { |
||
| 920 | |||
| 921 | /** |
||
| 922 | * Clear the in-process caches; useful for testing |
||
| 923 | * |
||
| 924 | * @since 1.27 |
||
| 925 | */ |
||
| 926 | public function clearProcessCache() { |
||
| 929 | |||
| 930 | /** |
||
| 931 | * Do the actual async bus purge of a key |
||
| 932 | * |
||
| 933 | * This must set the key to "PURGED:<UNIX timestamp>:<holdoff>" |
||
| 934 | * |
||
| 935 | * @param string $key Cache key |
||
| 936 | * @param integer $ttl How long to keep the tombstone [seconds] |
||
| 937 | * @param integer $holdoff HOLDOFF_* constant controlling how long to ignore sets for this key |
||
| 938 | * @return bool Success |
||
| 939 | */ |
||
| 940 | protected function relayPurge( $key, $ttl, $holdoff ) { |
||
| 956 | |||
| 957 | /** |
||
| 958 | * Do the actual async bus delete of a key |
||
| 959 | * |
||
| 960 | * @param string $key Cache key |
||
| 961 | * @return bool Success |
||
| 962 | */ |
||
| 963 | protected function relayDelete( $key ) { |
||
| 976 | |||
| 977 | /** |
||
| 978 | * Check if a key should be regenerated (using random probability) |
||
| 979 | * |
||
| 980 | * This returns false if $curTTL >= $lowTTL. Otherwise, the chance |
||
| 981 | * of returning true increases steadily from 0% to 100% as the $curTTL |
||
| 982 | * moves from $lowTTL to 0 seconds. This handles widely varying |
||
| 983 | * levels of cache access traffic. |
||
| 984 | * |
||
| 985 | * @param float $curTTL Approximate TTL left on the key if present |
||
| 986 | * @param float $lowTTL Consider a refresh when $curTTL is less than this |
||
| 987 | * @return bool |
||
| 988 | */ |
||
| 989 | protected function worthRefresh( $curTTL, $lowTTL ) { |
||
| 1000 | |||
| 1001 | /** |
||
| 1002 | * Do not use this method outside WANObjectCache |
||
| 1003 | * |
||
| 1004 | * @param mixed $value |
||
| 1005 | * @param integer $ttl [0=forever] |
||
| 1006 | * @return array |
||
| 1007 | */ |
||
| 1008 | protected function wrap( $value, $ttl ) { |
||
| 1016 | |||
| 1017 | /** |
||
| 1018 | * Do not use this method outside WANObjectCache |
||
| 1019 | * |
||
| 1020 | * @param array|string|bool $wrapped |
||
| 1021 | * @param float $now Unix Current timestamp (preferrable pre-query) |
||
| 1022 | * @return array (mixed; false if absent/invalid, current time left) |
||
| 1023 | */ |
||
| 1024 | protected function unwrap( $wrapped, $now ) { |
||
| 1056 | |||
| 1057 | /** |
||
| 1058 | * @param array $keys |
||
| 1059 | * @param string $prefix |
||
| 1060 | * @return string[] |
||
| 1061 | */ |
||
| 1062 | protected static function prefixCacheKeys( array $keys, $prefix ) { |
||
| 1070 | |||
| 1071 | /** |
||
| 1072 | * @param string $value Wrapped value like "PURGED:<timestamp>:<holdoff>" |
||
| 1073 | * @return array|bool Array containing a UNIX timestamp (float) and holdoff period (integer), |
||
| 1074 | * or false if value isn't a valid purge value |
||
| 1075 | */ |
||
| 1076 | protected static function parsePurgeValue( $value ) { |
||
| 1095 | |||
| 1096 | /** |
||
| 1097 | * @param float $timestamp |
||
| 1098 | * @param int $holdoff In seconds |
||
| 1099 | * @return string Wrapped purge value |
||
| 1100 | */ |
||
| 1101 | protected function makePurgeValue( $timestamp, $holdoff ) { |
||
| 1104 | } |
||
| 1105 |