You are here

function memcache_wildcards in Memcache API and Integration 6

Utilize multiget to retrieve all possible wildcard matches, storing statically so multiple cache requests for the same item on the same page load doesn't add overhead.

3 calls to memcache_wildcards()
cache_clear_all in ./memcache.inc
Expire data from the cache. If called without arguments, expirable entries will be cleared from the cache_page and cache_block tables.
MemCacheClearCase::clearWildcardPrefixTest in tests/memcache.test
Test cache clears using wildcard prefixes.
memcache_wildcard_flushes in ./memcache.inc
Sum of all matching wildcards. Checking any single cache item's flush value against this single-value sum tells us whether or not a new wildcard flush has affected the cached item.

File

./memcache.inc, line 333

Code

function memcache_wildcards($cid, $table, $flush = FALSE) {
  static $wildcards = array();
  $matching = array();
  if (!is_string($cid) && !is_int($cid)) {
    register_shutdown_function('watchdog', 'memcache', 'Invalid cache id received in memcache.inc wildcards() of type !type.', array(
      '!type' => gettype($cid),
    ), WATCHDOG_ERROR);
    return $matching;
  }
  $length = strlen($cid);
  $wildcard_flushes = variable_get('memcache_wildcard_flushes', array());
  $wildcard_invalidate = variable_get('memcache_wildcard_invalidate', MEMCACHE_WILDCARD_INVALIDATE);
  if (isset($wildcard_flushes[$table]) && is_array($wildcard_flushes[$table])) {

    // Wildcard flushes per table are keyed by a substring equal to the
    // shortest wildcard clear on the table so far. So if the shortest
    // wildcard was "links:foo:", and the cid we're checking for is
    // "links:bar:bar", then the key will be "links:bar:".
    $keys = array_keys($wildcard_flushes[$table]);

    // All keys are the same length, so just get the length of the first one.
    $wildcard_length = strlen(reset($keys));
    $wildcard_key = substr($cid, 0, $wildcard_length);

    // Determine which lookups we need to perform to determine whether or not
    // our cid was impacted by a wildcard flush.
    $lookup = array();

    // Find statically cached wildcards, and determine possibly matching
    // wildcards for this cid based on a history of the lengths of past valid
    // wildcard flushes in this bin.
    if (isset($wildcard_flushes[$table][$wildcard_key])) {
      foreach ($wildcard_flushes[$table][$wildcard_key] as $flush_length => $timestamp) {
        if ($length >= $flush_length && $timestamp >= $_SERVER['REQUEST_TIME'] - $wildcard_invalidate) {
          $key = '.wildcard-' . substr($cid, 0, $flush_length);
          $wildcard = dmemcache_key($key, $table);
          if (isset($wildcards[$table][$wildcard])) {
            $matching[$wildcard] = $wildcards[$table][$wildcard];
          }
          else {
            $lookup[$wildcard] = $key;
          }
        }
      }
    }

    // Do a multi-get to retrieve all possibly matching wildcard flushes.
    if (!empty($lookup)) {
      $memcache_values = dmemcache_get_multi($lookup, $table);
      if (is_array($memcache_values)) {

        // Map .wildcard-* to the dmemcache_key().
        $values = array();
        $rmap = array_flip($lookup);
        foreach ($memcache_values as $key => $value) {
          $values[$rmap[$key]] = $value;
        }

        // Build an array of matching wildcards.
        $matching = array_merge($matching, $values);
        if (isset($wildcards[$table])) {
          $wildcards[$table] = array_merge($wildcards[$table], $values);
        }
        else {
          $wildcards[$table] = $values;
        }
        $lookup = array_diff_key($lookup, $values);
      }

      // Also store failed lookups in our static cache, so we don't have to
      // do repeat lookups on single page loads.
      foreach ($lookup as $wildcard => $key) {
        $wildcards[$table][$wildcard] = 0;
      }
    }
  }
  if ($flush) {

    // Avoid too many calls to variable_set() by only recording a flush for a
    // fraction of the wildcard invalidation variable, per cid length.  Defaults
    // to 28 / 4, or one week.
    $length = strlen($cid);
    if (isset($wildcard_flushes[$table])) {
      $wildcard_flushes_keys = array_keys($wildcard_flushes[$table]);
      $key_length = strlen(reset($wildcard_flushes_keys));
    }
    else {
      $key_length = $length;
    }
    $key = substr($cid, 0, $key_length);
    if (!isset($wildcard_flushes[$table][$key][$length]) || $_SERVER['REQUEST_TIME'] - $wildcard_flushes[$table][$key][$length] > $wildcard_invalidate / 4) {

      // If there are more than 50 different wildcard keys for this table
      // shorten the key by one, this should reduce variability by
      // an order of magnitude and ensure we don't use too much memory.
      if (isset($wildcard_flushes[$table]) && count($wildcard_flushes[$table]) > 50) {
        $key = substr($cid, 0, $key_length - 1);
        $length = strlen($key);
      }

      // If this is the shortest key length so far, we need to remove all
      // other wildcards lengths recorded so far for this table and start
      // again. This is equivalent to a full cache flush for this table, but
      // it ensures the minimum possible number of wildcards are requested
      // along with cache consistency.
      if ($length < $key_length) {
        $wildcard_flushes[$table] = array();
        memcache_variable_set("cache_flush_{$table}", time());
      }
      $key = substr($cid, 0, $key_length);
      $wildcard_flushes[$table][$key][$length] = $_SERVER['REQUEST_TIME'];
      memcache_variable_set('memcache_wildcard_flushes', $wildcard_flushes);
    }
    $wildcard = dmemcache_key('.wildcard-' . $cid, $table);
    if (isset($wildcards[$table][$wildcard]) && $wildcards[$table][$wildcard] != 0) {
      $mc = dmemcache_object($table);
      if ($mc) {
        $mc
          ->increment($wildcard);
      }
      $wildcards[$table][$wildcard]++;
    }
    else {
      $wildcards[$table][$wildcard] = 1;
      dmemcache_set('.wildcard-' . $cid, '1', 0, $table);
    }
  }
  return $matching;
}