MediaWiki  master
MediumSpecificBagOStuff.php
Go to the documentation of this file.
1 <?php
24 use Wikimedia\WaitConditionLoop;
25 
34 abstract class MediumSpecificBagOStuff extends BagOStuff {
36  protected $locks = [];
38  protected $lastError = self::ERR_NONE;
40  protected $syncTimeout;
42  protected $segmentationSize;
45 
47  private $duplicateKeyLookups = [];
49  private $reportDupes = false;
51  private $dupeTrackScheduled = false;
52 
54  protected $busyCallbacks = [];
55 
57  protected $preparedValues = [];
58 
60  private const SEGMENT_COMPONENT = 'segment';
61 
63  protected const PASS_BY_REF = -1;
64 
65  protected const METRIC_OP_GET = 'get';
66  protected const METRIC_OP_SET = 'set';
67  protected const METRIC_OP_DELETE = 'delete';
68  protected const METRIC_OP_CHANGE_TTL = 'change_ttl';
69  protected const METRIC_OP_ADD = 'add';
70  protected const METRIC_OP_INCR = 'incr';
71  protected const METRIC_OP_DECR = 'decr';
72  protected const METRIC_OP_CAS = 'cas';
73 
92  public function __construct( array $params = [] ) {
93  parent::__construct( $params );
94 
95  if ( !empty( $params['reportDupes'] ) && is_callable( $this->asyncHandler ) ) {
96  $this->reportDupes = true;
97  }
98 
99  $this->syncTimeout = $params['syncTimeout'] ?? 3;
100  $this->segmentationSize = $params['segmentationSize'] ?? 8388608; // 8MiB
101  $this->segmentedValueMaxSize = $params['segmentedValueMaxSize'] ?? 67108864; // 64MiB
102  }
103 
117  public function get( $key, $flags = 0 ) {
118  $this->trackDuplicateKeys( $key );
119 
120  return $this->resolveSegments( $key, $this->doGet( $key, $flags ) );
121  }
122 
127  private function trackDuplicateKeys( $key ) {
128  if ( !$this->reportDupes ) {
129  return;
130  }
131 
132  if ( !isset( $this->duplicateKeyLookups[$key] ) ) {
133  // Track that we have seen this key. This N-1 counting style allows
134  // easy filtering with array_filter() later.
135  $this->duplicateKeyLookups[$key] = 0;
136  } else {
137  $this->duplicateKeyLookups[$key] += 1;
138 
139  if ( $this->dupeTrackScheduled === false ) {
140  $this->dupeTrackScheduled = true;
141  // Schedule a callback that logs keys processed more than once by get().
142  call_user_func( $this->asyncHandler, function () {
143  $dups = array_filter( $this->duplicateKeyLookups );
144  foreach ( $dups as $key => $count ) {
145  $this->logger->warning(
146  'Duplicate get(): "{key}" fetched {count} times',
147  // Count is N-1 of the actual lookup count
148  [ 'key' => $key, 'count' => $count + 1, ]
149  );
150  }
151  } );
152  }
153  }
154  }
155 
162  abstract protected function doGet( $key, $flags = 0, &$casToken = null );
163 
173  public function set( $key, $value, $exptime = 0, $flags = 0 ) {
174  list( $entry, $usable ) = $this->makeValueOrSegmentList( $key, $value, $exptime, $flags );
175  // Only when all segments (if any) are stored should the main key be changed
176  return $usable ? $this->doSet( $key, $entry, $exptime, $flags ) : false;
177  }
178 
188  abstract protected function doSet( $key, $value, $exptime = 0, $flags = 0 );
189 
201  public function delete( $key, $flags = 0 ) {
202  if ( !$this->fieldHasFlags( $flags, self::WRITE_PRUNE_SEGMENTS ) ) {
203  return $this->doDelete( $key, $flags );
204  }
205 
206  $mainValue = $this->doGet( $key, self::READ_LATEST );
207  if ( !$this->doDelete( $key, $flags ) ) {
208  return false;
209  }
210 
211  if ( !SerializedValueContainer::isSegmented( $mainValue ) ) {
212  return true; // no segments to delete
213  }
214 
215  $orderedKeys = array_map(
216  function ( $segmentHash ) use ( $key ) {
217  return $this->makeGlobalKey( self::SEGMENT_COMPONENT, $key, $segmentHash );
218  },
220  );
221 
222  return $this->deleteMulti( $orderedKeys, $flags & ~self::WRITE_PRUNE_SEGMENTS );
223  }
224 
232  abstract protected function doDelete( $key, $flags = 0 );
233 
234  public function add( $key, $value, $exptime = 0, $flags = 0 ) {
235  list( $entry, $usable ) = $this->makeValueOrSegmentList( $key, $value, $exptime, $flags );
236  // Only when all segments (if any) are stored should the main key be changed
237  return $usable ? $this->doAdd( $key, $entry, $exptime, $flags ) : false;
238  }
239 
249  abstract protected function doAdd( $key, $value, $exptime = 0, $flags = 0 );
250 
267  public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
268  return $this->mergeViaCas( $key, $callback, $exptime, $attempts, $flags );
269  }
270 
280  final protected function mergeViaCas( $key, callable $callback, $exptime, $attempts, $flags ) {
281  $attemptsLeft = $attempts;
282  do {
283  $token = self::PASS_BY_REF; // passed by reference
284  // Get the old value and CAS token from cache
285  $this->clearLastError();
286  $currentValue = $this->resolveSegments(
287  $key,
288  $this->doGet( $key, $flags, $token )
289  );
290  if ( $this->getLastError() ) {
291  // Don't spam slow retries due to network problems (retry only on races)
292  $this->logger->warning(
293  __METHOD__ . ' failed due to read I/O error on get() for {key}.',
294  [ 'key' => $key ]
295  );
296  $success = false;
297  break;
298  }
299 
300  // Derive the new value from the old value
301  $value = $callback( $this, $key, $currentValue, $exptime );
302  $keyWasNonexistant = ( $currentValue === false );
303  $valueMatchesOldValue = ( $value === $currentValue );
304  unset( $currentValue ); // free RAM in case the value is large
305 
306  $this->clearLastError();
307  if ( $value === false || $exptime < 0 ) {
308  $success = true; // do nothing
309  } elseif ( $valueMatchesOldValue && $attemptsLeft !== $attempts ) {
310  $success = true; // recently set by another thread to the same value
311  } elseif ( $keyWasNonexistant ) {
312  // Try to create the key, failing if it gets created in the meantime
313  $success = $this->add( $key, $value, $exptime, $flags );
314  } else {
315  // Try to update the key, failing if it gets changed in the meantime
316  $success = $this->cas( $token, $key, $value, $exptime, $flags );
317  }
318  if ( $this->getLastError() ) {
319  // Don't spam slow retries due to network problems (retry only on races)
320  $this->logger->warning(
321  __METHOD__ . ' failed due to write I/O error for {key}.',
322  [ 'key' => $key ]
323  );
324  $success = false;
325  break;
326  }
327 
328  } while ( !$success && --$attemptsLeft );
329 
330  return $success;
331  }
332 
343  protected function cas( $casToken, $key, $value, $exptime = 0, $flags = 0 ) {
344  if ( $casToken === null ) {
345  $this->logger->warning(
346  __METHOD__ . ' got empty CAS token for {key}.',
347  [ 'key' => $key ]
348  );
349 
350  return false; // caller may have meant to use add()?
351  }
352 
353  list( $entry, $usable ) = $this->makeValueOrSegmentList( $key, $value, $exptime, $flags );
354  // Only when all segments (if any) are stored should the main key be changed
355  return $usable ? $this->doCas( $casToken, $key, $entry, $exptime, $flags ) : false;
356  }
357 
368  protected function doCas( $casToken, $key, $value, $exptime = 0, $flags = 0 ) {
369  // @TODO: the use of lock() assumes that all other relevant sets() use a lock
370  if ( !$this->lock( $key, 0 ) ) {
371  return false; // non-blocking
372  }
373 
374  $curCasToken = self::PASS_BY_REF; // passed by reference
375  $this->clearLastError();
376  $this->doGet( $key, self::READ_LATEST, $curCasToken );
377  if ( is_object( $curCasToken ) ) {
378  // Using === does not work with objects since it checks for instance identity
379  throw new UnexpectedValueException( "CAS token cannot be an object" );
380  }
381  if ( $this->getLastError() ) {
382  // Fail if the old CAS token could not be read
383  $success = false;
384  $this->logger->warning(
385  __METHOD__ . ' failed due to write I/O error for {key}.',
386  [ 'key' => $key ]
387  );
388  } elseif ( $casToken === $curCasToken ) {
389  $success = $this->doSet( $key, $value, $exptime, $flags );
390  } else {
391  $success = false; // mismatched or failed
392  $this->logger->info(
393  __METHOD__ . ' failed due to race condition for {key}.',
394  [ 'key' => $key ]
395  );
396  }
397 
398  $this->unlock( $key );
399 
400  return $success;
401  }
402 
420  public function changeTTL( $key, $exptime = 0, $flags = 0 ) {
421  return $this->doChangeTTL( $key, $exptime, $flags );
422  }
423 
430  protected function doChangeTTL( $key, $exptime, $flags ) {
431  // @TODO: the use of lock() assumes that all other relevant sets() use a lock
432  if ( !$this->lock( $key, 0 ) ) {
433  return false;
434  }
435 
436  $expiry = $this->getExpirationAsTimestamp( $exptime );
437  $delete = ( $expiry != self::TTL_INDEFINITE && $expiry < $this->getCurrentTime() );
438 
439  // Use doGet() to avoid having to trigger resolveSegments()
440  $blob = $this->doGet( $key, self::READ_LATEST );
441  if ( $blob ) {
442  if ( $delete ) {
443  $ok = $this->doDelete( $key, $flags );
444  } else {
445  $ok = $this->doSet( $key, $blob, $exptime, $flags );
446  }
447  } else {
448  $ok = false;
449  }
450 
451  $this->unlock( $key );
452 
453  return $ok;
454  }
455 
467  public function lock( $key, $timeout = 6, $expiry = 6, $rclass = '' ) {
468  // Avoid deadlocks and allow lock reentry if specified
469  if ( isset( $this->locks[$key] ) ) {
470  if ( $rclass != '' && $this->locks[$key]['class'] === $rclass ) {
471  ++$this->locks[$key]['depth'];
472  return true;
473  } else {
474  return false;
475  }
476  }
477 
478  $fname = __METHOD__;
479  $expiry = min( $expiry ?: INF, self::TTL_DAY );
480  $loop = new WaitConditionLoop(
481  function () use ( $key, $expiry, $fname ) {
482  $this->clearLastError();
483  if ( $this->add( "{$key}:lock", 1, $expiry ) ) {
484  return WaitConditionLoop::CONDITION_REACHED; // locked!
485  } elseif ( $this->getLastError() ) {
486  $this->logger->warning(
487  $fname . ' failed due to I/O error for {key}.',
488  [ 'key' => $key ]
489  );
490 
491  return WaitConditionLoop::CONDITION_ABORTED; // network partition?
492  }
493 
494  return WaitConditionLoop::CONDITION_CONTINUE;
495  },
496  $timeout
497  );
498 
499  $code = $loop->invoke();
500  $locked = ( $code === $loop::CONDITION_REACHED );
501  if ( $locked ) {
502  $this->locks[$key] = [ 'class' => $rclass, 'depth' => 1 ];
503  } elseif ( $code === $loop::CONDITION_TIMED_OUT ) {
504  $this->logger->warning(
505  "$fname failed due to timeout for {key}.",
506  [ 'key' => $key, 'timeout' => $timeout ]
507  );
508  }
509 
510  return $locked;
511  }
512 
519  public function unlock( $key ) {
520  if ( !isset( $this->locks[$key] ) ) {
521  return false;
522  }
523 
524  if ( --$this->locks[$key]['depth'] <= 0 ) {
525  unset( $this->locks[$key] );
526 
527  $ok = $this->doDelete( "{$key}:lock" );
528  if ( !$ok ) {
529  $this->logger->warning(
530  __METHOD__ . ' failed to release lock for {key}.',
531  [ 'key' => $key ]
532  );
533  }
534 
535  return $ok;
536  }
537 
538  return true;
539  }
540 
552  $timestamp,
553  callable $progress = null,
554  $limit = INF
555  ) {
556  return false;
557  }
558 
565  public function getMulti( array $keys, $flags = 0 ) {
566  $foundByKey = $this->doGetMulti( $keys, $flags );
567 
568  $res = [];
569  foreach ( $keys as $key ) {
570  // Resolve one blob at a time (avoids too much I/O at once)
571  if ( array_key_exists( $key, $foundByKey ) ) {
572  // A value should not appear in the key if a segment is missing
573  $value = $this->resolveSegments( $key, $foundByKey[$key] );
574  if ( $value !== false ) {
575  $res[$key] = $value;
576  }
577  }
578  }
579 
580  return $res;
581  }
582 
589  protected function doGetMulti( array $keys, $flags = 0 ) {
590  $res = [];
591  foreach ( $keys as $key ) {
592  $val = $this->doGet( $key, $flags );
593  if ( $val !== false ) {
594  $res[$key] = $val;
595  }
596  }
597 
598  return $res;
599  }
600 
612  public function setMulti( array $valueByKey, $exptime = 0, $flags = 0 ) {
613  if ( $this->fieldHasFlags( $flags, self::WRITE_ALLOW_SEGMENTS ) ) {
614  throw new InvalidArgumentException( __METHOD__ . ' got WRITE_ALLOW_SEGMENTS' );
615  }
616 
617  return $this->doSetMulti( $valueByKey, $exptime, $flags );
618  }
619 
626  protected function doSetMulti( array $data, $exptime = 0, $flags = 0 ) {
627  $res = true;
628  foreach ( $data as $key => $value ) {
629  $res = $this->doSet( $key, $value, $exptime, $flags ) && $res;
630  }
631 
632  return $res;
633  }
634 
645  public function deleteMulti( array $keys, $flags = 0 ) {
646  if ( $this->fieldHasFlags( $flags, self::WRITE_PRUNE_SEGMENTS ) ) {
647  throw new InvalidArgumentException( __METHOD__ . ' got WRITE_PRUNE_SEGMENTS' );
648  }
649 
650  return $this->doDeleteMulti( $keys, $flags );
651  }
652 
658  protected function doDeleteMulti( array $keys, $flags = 0 ) {
659  $res = true;
660  foreach ( $keys as $key ) {
661  $res = $this->doDelete( $key, $flags ) && $res;
662  }
663  return $res;
664  }
665 
676  public function changeTTLMulti( array $keys, $exptime, $flags = 0 ) {
677  return $this->doChangeTTLMulti( $keys, $exptime, $flags );
678  }
679 
686  protected function doChangeTTLMulti( array $keys, $exptime, $flags = 0 ) {
687  $res = true;
688  foreach ( $keys as $key ) {
689  $res = $this->doChangeTTL( $key, $exptime, $flags ) && $res;
690  }
691 
692  return $res;
693  }
694 
695  public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 ) {
696  $init = is_int( $init ) ? $init : $value;
697  $this->clearLastError();
698  $newValue = $this->incr( $key, $value, $flags );
699  if ( $newValue === false && !$this->getLastError() ) {
700  // No key set; initialize
701  $newValue = $this->add( $key, (int)$init, $exptime, $flags ) ? $init : false;
702  if ( $newValue === false && !$this->getLastError() ) {
703  // Raced out initializing; increment
704  $newValue = $this->incr( $key, $value, $flags );
705  }
706  }
707 
708  return $newValue;
709  }
710 
718  final protected function resolveSegments( $key, $mainValue ) {
719  if ( SerializedValueContainer::isUnified( $mainValue ) ) {
720  return $this->unserialize( $mainValue->{SerializedValueContainer::UNIFIED_DATA} );
721  }
722 
723  if ( SerializedValueContainer::isSegmented( $mainValue ) ) {
724  $orderedKeys = array_map(
725  function ( $segmentHash ) use ( $key ) {
726  return $this->makeGlobalKey( self::SEGMENT_COMPONENT, $key, $segmentHash );
727  },
729  );
730 
731  $segmentsByKey = $this->doGetMulti( $orderedKeys );
732 
733  $parts = [];
734  foreach ( $orderedKeys as $segmentKey ) {
735  if ( isset( $segmentsByKey[$segmentKey] ) ) {
736  $parts[] = $segmentsByKey[$segmentKey];
737  } else {
738  return false; // missing segment
739  }
740  }
741 
742  return $this->unserialize( implode( '', $parts ) );
743  }
744 
745  return $mainValue;
746  }
747 
753  public function getLastError() {
754  return $this->lastError;
755  }
756 
761  public function clearLastError() {
762  $this->lastError = self::ERR_NONE;
763  }
764 
770  protected function setLastError( $err ) {
771  $this->lastError = $err;
772  }
773 
774  final public function addBusyCallback( callable $workCallback ) {
775  $this->busyCallbacks[] = $workCallback;
776  }
777 
788  final protected function makeValueOrSegmentList( $key, $value, $exptime, $flags ) {
789  $entry = $value;
790  $usable = true;
791 
792  if (
793  $this->fieldHasFlags( $flags, self::WRITE_ALLOW_SEGMENTS ) &&
794  !is_int( $value ) && // avoid breaking incr()/decr()
795  is_finite( $this->segmentationSize )
796  ) {
797  $segmentSize = $this->segmentationSize;
798  $maxTotalSize = $this->segmentedValueMaxSize;
799 
800  $serialized = $this->getSerialized( $value, $key );
801  $size = strlen( $serialized );
802  if ( $size > $maxTotalSize ) {
803  $this->logger->warning(
804  "Value for {key} exceeds $maxTotalSize bytes; cannot segment.",
805  [ 'key' => $key ]
806  );
807  } elseif ( $size <= $segmentSize ) {
808  // The serialized value was already computed, so just use it inline
810  } else {
811  // Split the serialized value into chunks and store them at different keys
812  $chunksByKey = [];
813  $segmentHashes = [];
814  $count = intdiv( $size, $segmentSize ) + ( ( $size % $segmentSize ) ? 1 : 0 );
815  for ( $i = 0; $i < $count; ++$i ) {
816  $segment = substr( $serialized, $i * $segmentSize, $segmentSize );
817  $hash = sha1( $segment );
818  $chunkKey = $this->makeGlobalKey( self::SEGMENT_COMPONENT, $key, $hash );
819  $chunksByKey[$chunkKey] = $segment;
820  $segmentHashes[] = $hash;
821  }
822  $flags &= ~self::WRITE_ALLOW_SEGMENTS; // sanity
823  $usable = $this->setMulti( $chunksByKey, $exptime, $flags );
824  $entry = SerializedValueContainer::newSegmented( $segmentHashes );
825  }
826  }
827 
828  return [ $entry, $usable ];
829  }
830 
836  final protected function isRelativeExpiration( $exptime ) {
837  return ( $exptime !== self::TTL_INDEFINITE && $exptime < ( 10 * self::TTL_YEAR ) );
838  }
839 
853  final protected function getExpirationAsTimestamp( $exptime ) {
854  if ( $exptime == self::TTL_INDEFINITE ) {
855  return $exptime;
856  }
857 
858  return $this->isRelativeExpiration( $exptime )
859  ? intval( $this->getCurrentTime() + $exptime )
860  : $exptime;
861  }
862 
877  final protected function getExpirationAsTTL( $exptime ) {
878  if ( $exptime == self::TTL_INDEFINITE ) {
879  return $exptime;
880  }
881 
882  return $this->isRelativeExpiration( $exptime )
883  ? $exptime
884  : (int)max( $exptime - $this->getCurrentTime(), 1 );
885  }
886 
893  final protected function isInteger( $value ) {
894  if ( is_int( $value ) ) {
895  return true;
896  } elseif ( !is_string( $value ) ) {
897  return false;
898  }
899 
900  $integer = (int)$value;
901 
902  return ( $value === (string)$integer );
903  }
904 
905  public function makeGlobalKey( $collection, ...$components ) {
906  return $this->makeKeyInternal( self::GLOBAL_KEYSPACE, func_get_args() );
907  }
908 
909  public function makeKey( $collection, ...$components ) {
910  return $this->makeKeyInternal( $this->keyspace, func_get_args() );
911  }
912 
913  protected function convertGenericKey( $key ) {
914  $components = $this->componentsFromGenericKey( $key );
915  if ( count( $components ) < 2 ) {
916  // Legacy key not from makeKey()/makeGlobalKey(); keep it as-is
917  return $key;
918  }
919 
920  $keyspace = array_shift( $components );
921 
922  return $this->makeKeyInternal( $keyspace, $components );
923  }
924 
925  public function getQoS( $flag ) {
926  return $this->attrMap[$flag] ?? self::QOS_UNKNOWN;
927  }
928 
929  public function getSegmentationSize() {
930  return $this->segmentationSize;
931  }
932 
933  public function getSegmentedValueMaxSize() {
934  return $this->segmentedValueMaxSize;
935  }
936 
937  public function setNewPreparedValues( array $valueByKey ) {
938  $this->preparedValues = [];
939 
940  $sizes = [];
941  foreach ( $valueByKey as $key => $value ) {
942  if ( $value === false ) {
943  $sizes[] = null; // not storable, don't bother
944  continue;
945  }
946 
947  $serialized = $this->serialize( $value );
948  $sizes[] = ( $serialized !== false ) ? strlen( $serialized ) : null;
949 
950  $this->preparedValues[$key] = [ $value, $serialized ];
951  }
952 
953  return $sizes;
954  }
955 
966  protected function getSerialized( $value, $key ) {
967  // Reuse any available prepared (serialized) value
968  if ( array_key_exists( $key, $this->preparedValues ) ) {
969  list( $prepValue, $prepSerialized ) = $this->preparedValues[$key];
970  // Normally, this comparison should only take a few microseconds to confirm a match.
971  // Using "===" on variables of different types is always fast. It is also fast for
972  // variables of matching type int, float, bool, null, and object. Lastly, it is fast
973  // for comparing arrays/strings if they are copy-on-write references, which should be
974  // the case at this point, assuming prepareValues() was called correctly.
975  if ( $prepValue === $value ) {
976  unset( $this->preparedValues[$key] );
977 
978  return $prepSerialized;
979  }
980  }
981 
982  return $this->serialize( $value );
983  }
984 
994  protected function guessSerialValueSize( $value, $depth = 0, &$loops = 0 ) {
995  // Include serialization format overhead estimates roughly based on serialize(),
996  // without counting . Also, int/float variables use the largest case
997  // byte size for numbers of that type; this avoids CPU overhead for large arrays.
998  switch ( gettype( $value ) ) {
999  case 'string':
1000  // E.g. "<type><delim1><quote><value><quote><delim2>"
1001  return strlen( $value ) + 5;
1002  case 'integer':
1003  // E.g. "<type><delim1><sign><2^63><delim2>";
1004  // ceil(log10 (2^63)) = 19
1005  return 23;
1006  case 'double':
1007  // E.g. "<type><delim1><sign><2^52><esign><2^10><delim2>"
1008  // ceil(log10 (2^52)) = 16 and ceil(log10 (2^10)) = 4
1009  return 25;
1010  case 'boolean':
1011  // E.g. "true" becomes "1" and "false" is not storable
1012  return $value ? 1 : null;
1013  case 'NULL':
1014  return 1; // "\0"
1015  case 'array':
1016  case 'object':
1017  // Give up and guess if there is too much depth
1018  if ( $depth >= 5 && $loops >= 256 ) {
1019  return 1024;
1020  }
1021 
1022  ++$loops;
1023  // E.g. "<type><delim1><brace><<Kn><Vn> for all n><brace><delim2>"
1024  $size = 5;
1025  // Note that casting to an array includes private object members
1026  foreach ( (array)$value as $k => $v ) {
1027  // Inline the recursive result here for performance
1028  $size += is_string( $k ) ? ( strlen( $k ) + 5 ) : 23;
1029  $size += $this->guessSerialValueSize( $v, $depth + 1, $loops );
1030  }
1031 
1032  return $size;
1033  default:
1034  return null; // invalid
1035  }
1036  }
1037 
1043  protected function serialize( $value ) {
1044  return is_int( $value ) ? $value : serialize( $value );
1045  }
1046 
1052  protected function unserialize( $value ) {
1053  return $this->isInteger( $value ) ? (int)$value : unserialize( $value );
1054  }
1055 
1059  protected function debug( $text ) {
1060  $this->logger->debug( "{class} debug: $text", [ 'class' => static::class ] );
1061  }
1062 
1070  protected function updateOpStats( string $op, array $keyInfo ) {
1071  $deltasByMetric = [];
1072 
1073  foreach ( $keyInfo as $indexOrKey => $keyOrSizes ) {
1074  if ( is_array( $keyOrSizes ) ) {
1075  $key = $indexOrKey;
1076  list( $sPayloadSize, $rPayloadSize ) = $keyOrSizes;
1077  } else {
1078  $key = $keyOrSizes;
1079  $sPayloadSize = null;
1080  $rPayloadSize = null;
1081  }
1082 
1083  // Metric prefix for the cache wrapper and key collection name
1084  $prefix = $this->determineKeyPrefixForStats( $key );
1085 
1086  if ( $op === self::METRIC_OP_GET ) {
1087  // This operation was either a "hit" or "miss" for this key
1088  $name = "{$prefix}.{$op}_" . ( $rPayloadSize === false ? 'miss_rate' : 'hit_rate' );
1089  } else {
1090  // There is no concept of "hit" or "miss" for this operation
1091  $name = "{$prefix}.{$op}_call_rate";
1092  }
1093  $deltasByMetric[$name] = ( $deltasByMetric[$name] ?? 0 ) + 1;
1094 
1095  if ( $sPayloadSize > 0 ) {
1096  $name = "{$prefix}.{$op}_bytes_sent";
1097  $deltasByMetric[$name] = ( $deltasByMetric[$name] ?? 0 ) + $sPayloadSize;
1098  }
1099 
1100  if ( $rPayloadSize > 0 ) {
1101  $name = "{$prefix}.{$op}_bytes_read";
1102  $deltasByMetric[$name] = ( $deltasByMetric[$name] ?? 0 ) + $rPayloadSize;
1103  }
1104  }
1105 
1106  foreach ( $deltasByMetric as $name => $delta ) {
1107  $this->stats->updateCount( $name, $delta );
1108  }
1109  }
1110 }
MediumSpecificBagOStuff\mergeViaCas
mergeViaCas( $key, callable $callback, $exptime, $attempts, $flags)
Definition: MediumSpecificBagOStuff.php:280
MediumSpecificBagOStuff\setLastError
setLastError( $err)
Set the "last error" registry.
Definition: MediumSpecificBagOStuff.php:770
MediumSpecificBagOStuff\doCas
doCas( $casToken, $key, $value, $exptime=0, $flags=0)
Check and set an item.
Definition: MediumSpecificBagOStuff.php:368
MediumSpecificBagOStuff\isInteger
isInteger( $value)
Check if a value is an integer.
Definition: MediumSpecificBagOStuff.php:893
MediumSpecificBagOStuff\$reportDupes
bool $reportDupes
Definition: MediumSpecificBagOStuff.php:49
MediumSpecificBagOStuff\guessSerialValueSize
guessSerialValueSize( $value, $depth=0, &$loops=0)
Estimate the size of a variable once serialized.
Definition: MediumSpecificBagOStuff.php:994
MediumSpecificBagOStuff\debug
debug( $text)
Definition: MediumSpecificBagOStuff.php:1059
MediumSpecificBagOStuff\incrWithInit
incrWithInit( $key, $exptime, $value=1, $init=null, $flags=0)
Increase the value of the given key (no TTL change) if it exists or create it otherwise.
Definition: MediumSpecificBagOStuff.php:695
MediumSpecificBagOStuff\doAdd
doAdd( $key, $value, $exptime=0, $flags=0)
Insert an item if it does not already exist.
MediumSpecificBagOStuff\cas
cas( $casToken, $key, $value, $exptime=0, $flags=0)
Check and set an item.
Definition: MediumSpecificBagOStuff.php:343
$serialized
foreach( $res as $row) $serialized
Definition: testCompression.php:88
MediumSpecificBagOStuff\trackDuplicateKeys
trackDuplicateKeys( $key)
Track the number of times that a given key has been used.
Definition: MediumSpecificBagOStuff.php:127
BagOStuff
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:86
MediumSpecificBagOStuff\$duplicateKeyLookups
array $duplicateKeyLookups
Definition: MediumSpecificBagOStuff.php:47
MediumSpecificBagOStuff\unlock
unlock( $key)
Release an advisory lock on a key string.
Definition: MediumSpecificBagOStuff.php:519
$success
$success
Definition: NoLocalSettings.php:42
MediumSpecificBagOStuff\serialize
serialize( $value)
Definition: MediumSpecificBagOStuff.php:1043
$res
$res
Definition: testCompression.php:57
serialize
serialize()
Definition: ApiMessageTrait.php:138
MediumSpecificBagOStuff\METRIC_OP_ADD
const METRIC_OP_ADD
Definition: MediumSpecificBagOStuff.php:69
MediumSpecificBagOStuff\getMulti
getMulti(array $keys, $flags=0)
Get an associative array containing the item for each of the keys that have items.
Definition: MediumSpecificBagOStuff.php:565
MediumSpecificBagOStuff\convertGenericKey
convertGenericKey( $key)
Convert a "generic" reversible cache key into one for this cache.
Definition: MediumSpecificBagOStuff.php:913
MediumSpecificBagOStuff\changeTTL
changeTTL( $key, $exptime=0, $flags=0)
Change the expiration on a key if it exists.
Definition: MediumSpecificBagOStuff.php:420
MediumSpecificBagOStuff\$syncTimeout
int $syncTimeout
Seconds.
Definition: MediumSpecificBagOStuff.php:40
SerializedValueContainer\isSegmented
static isSegmented( $value)
Definition: SerializedValueContainer.php:50
MediumSpecificBagOStuff\$segmentedValueMaxSize
int $segmentedValueMaxSize
Bytes; maximum total size of a segmented cache value.
Definition: MediumSpecificBagOStuff.php:44
MediumSpecificBagOStuff\makeKey
makeKey( $collection,... $components)
Make a cache key for the global keyspace and given components.
Definition: MediumSpecificBagOStuff.php:909
MediumSpecificBagOStuff\resolveSegments
resolveSegments( $key, $mainValue)
Get and reassemble the chunks of blob at the given key.
Definition: MediumSpecificBagOStuff.php:718
MediumSpecificBagOStuff\deleteObjectsExpiringBefore
deleteObjectsExpiringBefore( $timestamp, callable $progress=null, $limit=INF)
Delete all objects expiring before a certain date.
Definition: MediumSpecificBagOStuff.php:551
MediumSpecificBagOStuff\doChangeTTLMulti
doChangeTTLMulti(array $keys, $exptime, $flags=0)
Definition: MediumSpecificBagOStuff.php:686
MediumSpecificBagOStuff\getExpirationAsTimestamp
getExpirationAsTimestamp( $exptime)
Convert an optionally relative timestamp to an absolute time.
Definition: MediumSpecificBagOStuff.php:853
MediumSpecificBagOStuff\changeTTLMulti
changeTTLMulti(array $keys, $exptime, $flags=0)
Change the expiration of multiple keys that exist.
Definition: MediumSpecificBagOStuff.php:676
MediumSpecificBagOStuff\doSetMulti
doSetMulti(array $data, $exptime=0, $flags=0)
Definition: MediumSpecificBagOStuff.php:626
$blob
$blob
Definition: testCompression.php:70
MediumSpecificBagOStuff\doSet
doSet( $key, $value, $exptime=0, $flags=0)
Set an item.
MediumSpecificBagOStuff\deleteMulti
deleteMulti(array $keys, $flags=0)
Batch deletion.
Definition: MediumSpecificBagOStuff.php:645
MediumSpecificBagOStuff\addBusyCallback
addBusyCallback(callable $workCallback)
Let a callback be run to avoid wasting time on special blocking calls.
Definition: MediumSpecificBagOStuff.php:774
MediumSpecificBagOStuff\doGet
doGet( $key, $flags=0, &$casToken=null)
MediumSpecificBagOStuff\getQoS
getQoS( $flag)
Definition: MediumSpecificBagOStuff.php:925
MediumSpecificBagOStuff\getExpirationAsTTL
getExpirationAsTTL( $exptime)
Convert an optionally absolute expiry time to a relative time.
Definition: MediumSpecificBagOStuff.php:877
MediumSpecificBagOStuff\makeGlobalKey
makeGlobalKey( $collection,... $components)
Make a cache key for the default keyspace and given components.
Definition: MediumSpecificBagOStuff.php:905
MediumSpecificBagOStuff
Storage medium specific cache for storing items (e.g.
Definition: MediumSpecificBagOStuff.php:34
MediumSpecificBagOStuff\updateOpStats
updateOpStats(string $op, array $keyInfo)
Definition: MediumSpecificBagOStuff.php:1070
MediumSpecificBagOStuff\METRIC_OP_DECR
const METRIC_OP_DECR
Definition: MediumSpecificBagOStuff.php:71
MediumSpecificBagOStuff\METRIC_OP_CHANGE_TTL
const METRIC_OP_CHANGE_TTL
Definition: MediumSpecificBagOStuff.php:68
MediumSpecificBagOStuff\$dupeTrackScheduled
bool $dupeTrackScheduled
Definition: MediumSpecificBagOStuff.php:51
MediumSpecificBagOStuff\doDelete
doDelete( $key, $flags=0)
Delete an item.
MediumSpecificBagOStuff\makeValueOrSegmentList
makeValueOrSegmentList( $key, $value, $exptime, $flags)
Determine the entry (inline or segment list) to store under a key to save the value.
Definition: MediumSpecificBagOStuff.php:788
MediumSpecificBagOStuff\setMulti
setMulti(array $valueByKey, $exptime=0, $flags=0)
Batch insertion/replace.
Definition: MediumSpecificBagOStuff.php:612
MediumSpecificBagOStuff\METRIC_OP_GET
const METRIC_OP_GET
Definition: MediumSpecificBagOStuff.php:65
MediumSpecificBagOStuff\METRIC_OP_SET
const METRIC_OP_SET
Definition: MediumSpecificBagOStuff.php:66
MediumSpecificBagOStuff\__construct
__construct(array $params=[])
Definition: MediumSpecificBagOStuff.php:92
MediumSpecificBagOStuff\$segmentationSize
int $segmentationSize
Bytes; chunk size of segmented cache values.
Definition: MediumSpecificBagOStuff.php:42
MediumSpecificBagOStuff\doGetMulti
doGetMulti(array $keys, $flags=0)
Get an associative array containing the item for each of the keys that have items.
Definition: MediumSpecificBagOStuff.php:589
SerializedValueContainer\UNIFIED_DATA
const UNIFIED_DATA
Definition: SerializedValueContainer.php:13
MediumSpecificBagOStuff\getSegmentationSize
getSegmentationSize()
Definition: MediumSpecificBagOStuff.php:929
MediumSpecificBagOStuff\$busyCallbacks
callable[] $busyCallbacks
Definition: MediumSpecificBagOStuff.php:54
MediumSpecificBagOStuff\getSerialized
getSerialized( $value, $key)
Get the serialized form a value, using any applicable prepared value.
Definition: MediumSpecificBagOStuff.php:966
SerializedValueContainer\newUnified
static newUnified( $serialized)
Definition: SerializedValueContainer.php:20
MediumSpecificBagOStuff\METRIC_OP_INCR
const METRIC_OP_INCR
Definition: MediumSpecificBagOStuff.php:70
MediumSpecificBagOStuff\METRIC_OP_DELETE
const METRIC_OP_DELETE
Definition: MediumSpecificBagOStuff.php:67
MediumSpecificBagOStuff\isRelativeExpiration
isRelativeExpiration( $exptime)
Definition: MediumSpecificBagOStuff.php:836
unserialize
unserialize( $serialized)
Definition: ApiMessageTrait.php:146
MediumSpecificBagOStuff\setNewPreparedValues
setNewPreparedValues(array $valueByKey)
Make a "generic" reversible cache key from the given components.
Definition: MediumSpecificBagOStuff.php:937
MediumSpecificBagOStuff\unserialize
unserialize( $value)
Definition: MediumSpecificBagOStuff.php:1052
SerializedValueContainer\SEGMENTED_HASHES
const SEGMENTED_HASHES
Definition: SerializedValueContainer.php:14
BagOStuff\fieldHasFlags
fieldHasFlags( $field, $flags)
Definition: BagOStuff.php:593
MediumSpecificBagOStuff\add
add( $key, $value, $exptime=0, $flags=0)
Insert an item if it does not already exist.
Definition: MediumSpecificBagOStuff.php:234
MediumSpecificBagOStuff\$preparedValues
array[] $preparedValues
Map of (key => (PHP variable value, serialized value))
Definition: MediumSpecificBagOStuff.php:57
$keys
$keys
Definition: testCompression.php:72
MediumSpecificBagOStuff\getSegmentedValueMaxSize
getSegmentedValueMaxSize()
Definition: MediumSpecificBagOStuff.php:933
SerializedValueContainer\isUnified
static isUnified( $value)
Definition: SerializedValueContainer.php:42
MediumSpecificBagOStuff\merge
merge( $key, callable $callback, $exptime=0, $attempts=10, $flags=0)
Merge changes into the existing cache value (possibly creating a new one)
Definition: MediumSpecificBagOStuff.php:267
MediumSpecificBagOStuff\METRIC_OP_CAS
const METRIC_OP_CAS
Definition: MediumSpecificBagOStuff.php:72
MediumSpecificBagOStuff\lock
lock( $key, $timeout=6, $expiry=6, $rclass='')
Acquire an advisory lock on a key string.
Definition: MediumSpecificBagOStuff.php:467
MediumSpecificBagOStuff\$locks
array[] $locks
Lock tracking.
Definition: MediumSpecificBagOStuff.php:36
MediumSpecificBagOStuff\$lastError
int $lastError
ERR_* class constant.
Definition: MediumSpecificBagOStuff.php:38
MediumSpecificBagOStuff\clearLastError
clearLastError()
Clear the "last error" registry.
Definition: MediumSpecificBagOStuff.php:761
MediumSpecificBagOStuff\doDeleteMulti
doDeleteMulti(array $keys, $flags=0)
Definition: MediumSpecificBagOStuff.php:658
MediumSpecificBagOStuff\doChangeTTL
doChangeTTL( $key, $exptime, $flags)
Definition: MediumSpecificBagOStuff.php:430
SerializedValueContainer\newSegmented
static newSegmented(array $segmentHashList)
Definition: SerializedValueContainer.php:31
MediumSpecificBagOStuff\getLastError
getLastError()
Get the "last error" registered; clearLastError() should be called manually.
Definition: MediumSpecificBagOStuff.php:753