Skip to content

Instantly share code, notes, and snippets.

@andybeak
Last active October 21, 2020 16:20
Show Gist options
  • Save andybeak/171f4c358debcc304462 to your computer and use it in GitHub Desktop.
Save andybeak/171f4c358debcc304462 to your computer and use it in GitHub Desktop.
Storing large values in Memcached
/**
* store
*
* Stores a large array into memcache as a number of key=>values in order to overcome the limitation placed
* on memcache. You'll need to use discretion when choosing to do this.
*
* The principle is to split the data into several smaller chunks. The original key is used to store an
* index array which provides retrieveBigCacheValue() with information on where to look for the data.
*
* See retrieveBigCacheValue for the inverse
*
* @version 1.0.0
* @author Andy Beak
* @since 1.0.0
* @access public
* @param $tag
* @param $key
* @param array $value
*/
public function storeBigCacheValue($tag, $key, array $value) {
$randomKey = $this->createUid();
$splitSize = 1000 * 900; // 900kb (less than 1 meg)
$value = gzcompress(serialize($value));
$chunks = str_split($value, $splitSize);
$cacheTime = Config::get('app.cacheLifetime');
foreach ($chunks as $index => $store) {
$indexKey = $randomKey.'-'.$index;
Log::debug(__METHOD__.' : Storing ' . $indexKey . ' for ' . $cacheTime . ' minutes');
Cache::tags($tag)->put($indexKey, $store, $cacheTime);
}
if (!Cache::tags($tag)->has($randomKey.'-0')) {
throw new \Exception(__METHOD__.' : Could not find first cache entry - Split size is ' . $splitSize . ' bytes');
}
$info = [
'key' => $randomKey,
'count' => count($chunks)
];
Cache::tags($tag)->put($key, $info, $cacheTime);
}
/**
* retrieveBigCacheValue
*
* Retrieves a big cache value that was stored in a split-up format by storeBigCacheValue
*
*
* @version 1.0.0
* @author Andy Beak
* @since 1.0.0
* @access public
* @param $tag
* @param $key
* @return mixed
*/
public function retrieveBigCacheValue($tag, $key) {
if (!Cache::tags($tag)->has($key)) {
Log::debug(__METHOD__.' : Key ' . $key . ' in tag ' . $tag . ' is not present. Has Memcached already removed it?');
return false;
}
$index = Cache::tags($tag)->get($key);
if (!is_array($index) || !isset($index['key']) || !isset($index['count'])) {
Log::debug(__METHOD__.' : Key ' . $key . ' in tag ' . $tag . ' is not one stored by storeBigCacheValue');
return false;
}
$lookupKey = $index['key'];
$count = $index['count'];
$chunks = [];
for ($i = 0; $i < $count; $i++) {
if (!Cache::tags($tag)->has($lookupKey.'-'.$i)) {
Log::debug(__METHOD__.' : The chunk ' . $i . ' is missing from ' . $key);
return false;
}
$chunks[] = Cache::tags($tag)->get($lookupKey.'-'.$i);
}
$compressed = implode('', $chunks);
unset($chunks);
return unserialize(gzuncompress($compressed));
}
/**
* createUid
*
* Pattern defines the number of characters in each part of the uuid. We loop through the
* pattern and replace the number with the proper amount of random hex chars.
*
* @version 1.0.0
* @author Andy Beak
* @since 1.0.0
* @access public
* @return string
*/
public function createUid() {
$pattern = [8, 4, 4, 4, 12];
foreach ($pattern as $key => $length) {
$bytes = openssl_random_pseudo_bytes($length / 2, $cstrong);
if (false === $cstrong) {
Log::warning(__METHOD__.' : Host has insecure crypto method - try updating');
}
$pattern[$key] = bin2hex($bytes);
}
return implode('-', $pattern);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment