cache persistence improvement, only update keys which have changed. More efficient, and less likely to cause race conditions.

This commit is contained in:
2026-03-12 17:15:49 -04:00
parent a0dc1a6049
commit 6ed1ab26f1
2 changed files with 69 additions and 55 deletions
+60 -50
View File
@@ -11,18 +11,17 @@ use Psr\Cache\InvalidArgumentException;
/* We must not store the key-list item or values within this object,
* because it can change from outside this object instance. */
final readonly class MonitorCacheKeys implements CacheItemPoolInterface {
/* TODO store clean/dirty status in the key-list, so that in PersistCache,
* we can only need to update/remove specific items that changed,
* instead of the full list */
private const KEY_LIST = '__key_list';
private const IS_DIRTY = '__is_dirty';
private const CHANGE_LIST = '__chg_list';
public const UPDATED = 1;
public const REMOVED = 2;
private CacheItemPoolInterface $cache;
/** @throws InvalidArgumentException */
public function __construct(CacheItemPoolInterface $cache) {
$this->cache = $cache;
$items = $cache->getItems([self::KEY_LIST, self::IS_DIRTY]);
$items = $cache->getItems([self::KEY_LIST, self::CHANGE_LIST]);
foreach ($items as $item) {
if ( ! $item->isHit()) {
$this->initialize();
@@ -34,11 +33,11 @@ final readonly class MonitorCacheKeys implements CacheItemPoolInterface {
/** @throws InvalidArgumentException */
private function initialize(): void {
$keyList = $this->cache->getItem(self::KEY_LIST);
$isDirty = $this->cache->getItem(self::IS_DIRTY);
$changeList = $this->cache->getItem(self::CHANGE_LIST);
$keyList->set([]);
$isDirty->set(false);
$changeList->set([]);
$this->cache->saveDeferred($keyList);
$this->cache->saveDeferred($isDirty);
$this->cache->saveDeferred($changeList);
$this->cache->commit();
}
@@ -49,22 +48,24 @@ final readonly class MonitorCacheKeys implements CacheItemPoolInterface {
}
/** @throws InvalidArgumentException */
public function isDirty(): bool {
$isDirty = $this->cache->getItem(self::IS_DIRTY);
return $isDirty->get() ?? false;
public function getChanges(): array {
$changeList = $this->cache->getItem(self::CHANGE_LIST);
return $changeList->get() ?? [];
}
/** @throws InvalidArgumentException */
public function markClean(): void {
$isDirty = $this->cache->getItem(self::IS_DIRTY);
$isDirty->set(false);
$this->cache->save($isDirty);
$changeList = $this->cache->getItem(self::CHANGE_LIST);
$changeList->set([]);
$this->cache->save($changeList);
}
public function getItem(string $key): CacheItemInterface {
return $this->cache->getItem($key);
}
/** @return CacheItemInterface[]
* @throws InvalidArgumentException */
public function getItems(array $keys = []): iterable {
return $this->cache->getItems($keys);
}
@@ -86,20 +87,14 @@ final readonly class MonitorCacheKeys implements CacheItemPoolInterface {
}
public function deleteItem(string $key): bool {
if ($key === self::KEY_LIST || $key === self::IS_DIRTY) {
throw new OutOfBoundsException(
'Can not delete the private key list or is dirty flag'
);
}
$this->isValid($key);
$keyList = $this->cache->getItem(self::KEY_LIST);
$isDirty = $this->cache->getItem(self::IS_DIRTY);
$keyValues = $keyList->get();
if (isset($keyValues[$key])) {
unset($keyValues[$key]);
$keyList->set($keyValues);
$isDirty->set(true);
$this->cache->saveDeferred($keyList);
$this->cache->saveDeferred($isDirty);
$this->logChange($key, MonitorCacheKeys::REMOVED);
$this->cache->commit();
}
@@ -107,25 +102,17 @@ final readonly class MonitorCacheKeys implements CacheItemPoolInterface {
}
public function deleteItems(array $keys): bool {
if (in_array(self::KEY_LIST, $keys, true) ||
in_array(self::IS_DIRTY, $keys, true)
) {
throw new OutOfBoundsException(
'Can not delete the private key list or is dirty flag'
);
}
$this->allValid($keys);
$keyList = $this->cache->getItem(self::KEY_LIST);
$isDirty = $this->cache->getItem(self::IS_DIRTY);
$keyValues = $keyList->get();
foreach ($keys as $key) {
if (isset($keyValues[$key])) {
unset($keyValues[$key]);
$isDirty->set(true);
$this->logChange($key, MonitorCacheKeys::REMOVED);
}
}
$keyList->set($keyValues);
$this->cache->saveDeferred($keyList);
$this->cache->saveDeferred($isDirty);
$this->cache->commit();
return $this->cache->deleteItems($keys);
@@ -143,25 +130,48 @@ final readonly class MonitorCacheKeys implements CacheItemPoolInterface {
return $this->cache->saveDeferred($item);
}
/** @throws InvalidArgumentException */
private function update(CacheItemInterface $item) {
if ($item->getKey() === self::KEY_LIST || $item->getKey() === self::IS_DIRTY) {
throw new OutOfBoundsException(
'Can not alter the private key list or is dirty flag'
);
}
$keyList = $this->cache->getItem(self::KEY_LIST);
$isDirty = $this->cache->getItem(self::IS_DIRTY);
$keyValues = $keyList->get();
$keyValues[$item->getKey()] = true;
$keyList->set($keyValues);
$isDirty->set(true);
$this->cache->saveDeferred($keyList);
$this->cache->saveDeferred($isDirty);
$this->cache->commit();
}
public function commit(): bool {
return $this->cache->commit();
}
/** @throws InvalidArgumentException|OutOfBoundsException */
private function update(CacheItemInterface $item) {
$this->isValid($item->getKey());
$keyList = $this->cache->getItem(self::KEY_LIST);
$keyValues = $keyList->get();
$keyValues[$item->getKey()] = true;
$keyList->set($keyValues);
$this->logChange($item->getKey());
$this->cache->saveDeferred($keyList);
$this->cache->commit();
}
/** @throws OutOfBoundsException */
private function isValid(string $key): void {
if ($key === self::KEY_LIST || $key === self::CHANGE_LIST) {
throw new OutOfBoundsException(
'Can not modify the private key or change lists'
);
}
}
/** @throws OutOfBoundsException */
private function allValid(array $keys): void {
if (in_array(self::KEY_LIST, $keys, true) ||
in_array(self::CHANGE_LIST, $keys, true)
) {
throw new OutOfBoundsException(
'Can not modify the private key or change lists'
);
}
}
/** @throws InvalidArgumentException */
private function logChange(string $key, int $code = MonitorCacheKeys::UPDATED): void {
$changeList = $this->cache->getItem(self::CHANGE_LIST);
$changeValues = $changeList->get();
$changeValues[$key] = $code;
$changeList->set($changeValues);
$this->cache->saveDeferred($changeList);
}
}