[CACHE] Add partial implmentation for caching hashmaps (non-list arrays, i.e. array<string,string>). No non-redis-fallback yet

This commit is contained in:
Hugo Sales 2021-11-08 15:05:12 +00:00 committed by Diogo Peralta Cordeiro
parent cd470cbf93
commit 705bf815ab
Signed by: diogo
GPG Key ID: 18D2D35001FBFAB0
1 changed files with 73 additions and 0 deletions

View File

@ -31,6 +31,7 @@ use App\Util\Common;
use App\Util\Exception\ConfigurationException;
use Functional as F;
use InvalidArgumentException;
use NotImplementedException;
use Redis;
use RedisCluster;
use Symfony\Component\Cache\Adapter;
@ -152,6 +153,8 @@ abstract class Cache
/**
* Retrieve a list from the cache, with a different implementation
* for redis and others, trimming to $max_count if given
*
* @param callable(?Item $item, bool &$save): string|object|array<int,mixed> $calculate
*/
public static function getList(string $key, callable $calculate, string $pool = 'default', ?int $max_count = null, ?int $left = null, ?int $right = null, float $beta = 1.0): array
{
@ -246,6 +249,76 @@ abstract class Cache
}
}
/**
* Retrieve a hashmap from the cache, with a different implementation
* for redis and others. Different from lists, works with string map_keys
*
* @param callable(?Item $item, bool &$save): string|object|array<string,mixed> $calculate
* @TODO cleanup
*/
public static function getHashMap(string $map_key, callable $calculate, string $pool = 'default', float $beta = 1.0): array
{
if (isset(self::$redis[$pool])) {
if (!($recompute = $beta === \INF || !(self::$redis[$pool]->exists($map_key)))) {
if (\is_float($er = Common::config('cache', 'early_recompute'))) {
$recompute = (mt_rand() / mt_getrandmax() > $er);
Log::info('Item "{map_key}" elected for early recomputation', ['key' => $map_key]);
} else {
if ($recompute = ($idletime = self::$redis[$pool]->object('idletime', $map_key) ?? false) && ($expiry = self::$redis[$pool]->ttl($map_key) ?? false) && $expiry <= $idletime / 1000 * $beta * log(random_int(1, \PHP_INT_MAX) / \PHP_INT_MAX)) {
// @codeCoverageIgnoreStart
Log::info('Item "{key}" elected for early recomputation {delta}s before its expiration', [
'key' => $map_key,
'delta' => sprintf('%.1f', $expiry - microtime(true)),
]);
// @codeCoverageIgnoreEnd
}
}
}
if ($recompute) {
$save = true; // Pass by reference
$res = $calculate(null, $save);
if ($save) {
self::setHashMap($map_key, $res, $pool, $beta);
return $res;
}
}
return self::$redis[$pool]->hGetAll($map_key);
} else {
throw new NotImplementedException();
}
}
/**
* Set the list
*/
public static function setHashMap(string $map_key, array $value, string $pool = 'default', float $beta = 1.0): void
{
if (isset(self::$redis[$pool])) {
if (empty($value)) {
self::$redis[$pool]->del($map_key); // Redis doesn't support empty lists
} else {
self::$redis[$pool] // Ensure atomic
->multi(Redis::MULTI)
->del($map_key);
foreach ($value as $field_key => $field_value) {
self::$redis[$pool]->hSet($map_key, $field_key, $field_value);
}
self::$redis[$pool]->exec();
}
} else {
self::set($map_key, \array_slice($value, 0, $max_count), $pool);
}
}
public static function getHashMapKey(string $map_key, string $key, string $pool = 'default')
{
if (isset(self::$redis[$pool])) {
return self::$redis[$pool]->hget($map_key, $key);
} else {
throw new NotImplementedException;
}
}
/**
* Create a cached stream of Notes, paged
*