protected function appendToSubtreeCache(UUID $descendant, array $rootPath) { $callback = function (BagOStuff $cache, $key, $value) use($descendant) { if ($value === false) { return false; } $value[$descendant->getAlphadecimal()] = $descendant; return $value; }; // This could be pretty slow if there is contention foreach ($rootPath as $subtreeRoot) { $cacheKey = $this->cacheKey('subtree', $subtreeRoot); $success = $this->cache->merge($cacheKey, $callback); // $success is always true if bufferCache starts with begin() // if we failed to CAS new data, kill the cached value so it'll be // re-fetched from DB if (!$success) { $this->cache->delete($cacheKey); } } }
/** * {@inheritDoc} */ public function findMulti(array $queries, array $options = array()) { if (!$queries) { return array(); } // get cache keys for all queries $cacheKeys = $this->getCacheKeys($queries); // retrieve from cache (only query duplicate queries once) // $fromCache will be an array containing compacted results as value and // cache keys as key $fromCache = $this->cache->getMulti(array_unique($cacheKeys)); // figure out what queries were resolved in cache // $keysFromCache will be an array where values are cache keys and keys // are the same index as their corresponding $queries $keysFromCache = array_intersect($cacheKeys, array_keys($fromCache)); // filter out all queries that have been resolved from cache and fetch // them from storage // $fromStorage will be an array containing (expanded) results as value // and indexes matching $query as key $storageQueries = array_diff_key($queries, $keysFromCache); $fromStorage = array(); if ($storageQueries) { $fromStorage = $this->backingStoreFindMulti($storageQueries); // store the data we've just retrieved to cache foreach ($fromStorage as $index => $rows) { // backing store returns data that may not be valid to cache (e.g. // if we couldn't retrieve content from ExternalStore, we shouldn't // cache that result) $rows = array_filter($rows, array($this->storage, 'validate')); if ($rows !== $fromStorage[$index]) { continue; } $compacted = $this->rowCompactor->compactRows($rows); $callback = function (\BagOStuff $cache, $key, $value) use($compacted) { if ($value !== false) { // somehow, the data was already cached in the meantime return false; } return $compacted; }; $this->cache->merge($cacheKeys[$index], $callback); } } $results = $fromStorage; // $queries may have had duplicates that we've ignored to minimize // cache requests - now re-duplicate values from cache & match the // results against their respective original keys in $queries foreach ($keysFromCache as $index => $cacheKey) { $results[$index] = $fromCache[$cacheKey]; } // now that we have all data, both from cache & backing storage, filter // out all data we don't need $results = $this->filterResults($results, $options); // if we have no data from cache, there's nothing left - quit early if (!$fromCache) { return $results; } // because we may have combined data from 2 different sources, chances // are the order of the data is no longer in sync with the order // $queries were in - fix that by replacing $queries values with // the corresponding $results value // note that there may be missing results, hence the intersect ;) $order = array_intersect_key($queries, $results); $results = array_replace($order, $results); $keyToQuery = array(); foreach ($keysFromCache as $index => $key) { // all redundant data has been stripped, now expand all cache values // (we're only doing this now to avoid expanding redundant data) $fromCache[$key] = $results[$index]; // to expand rows, we'll need the $query info mapped to the cache // key instead of the $query index if (!isset($keyToQuery[$key])) { $keyToQuery[$key] = $queries[$index]; $keyToQuery[$key] = UUID::convertUUIDs($keyToQuery[$key], 'alphadecimal'); } } // expand and replace the stubs in $results with complete data $fromCache = $this->rowCompactor->expandCacheResult($fromCache, $keyToQuery); foreach ($keysFromCache as $index => $cacheKey) { $results[$index] = $fromCache[$cacheKey]; } return $results; }