From 3053707a892e100e81efb53dc8c294dca5e076d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Km=C3=ADnek?= Date: Sun, 10 Mar 2024 22:27:05 +0100 Subject: [PATCH] Bulk write implementation (#73) --- src/Caching/BulkWriter.php | 28 ++++++++++++++ src/Caching/Cache.php | 39 ++++++++++++++++++++ src/Caching/Storages/MemcachedStorage.php | 44 +++++++++++++++++++++- tests/Caching/Cache.bulkSave.phpt | 45 +++++++++++++++++++++++ tests/Caching/Cache.php | 31 ++++++++++++++++ tests/Storages/Memcached.bulkWrite.phpt | 36 ++++++++++++++++++ 6 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 src/Caching/BulkWriter.php create mode 100644 tests/Caching/Cache.bulkSave.phpt create mode 100644 tests/Storages/Memcached.bulkWrite.phpt diff --git a/src/Caching/BulkWriter.php b/src/Caching/BulkWriter.php new file mode 100644 index 0000000..38d88fb --- /dev/null +++ b/src/Caching/BulkWriter.php @@ -0,0 +1,28 @@ +storage instanceof BulkWriter) { + foreach ($items as $key => $data) { + $this->save($key, $data, $dependencies); + } + return; + } + + $dependencies = $this->completeDependencies($dependencies); + if (isset($dependencies[self::Expire]) && $dependencies[self::Expire] <= 0) { + $this->storage->bulkRemove(array_map(fn($key) => $this->generateKey($key), array_keys($items))); + return; + } + + foreach ($items as $key => $data) { + $key = $this->generateKey($key); + if ($data === null) { + $remove[] = $key; + } else { + $write[$key] = $data; + } + } + + if ($remove) { + $this->storage->bulkRemove($remove); + } + + if ($write) { + $this->storage->bulkWrite($write, $dependencies); + } + } + + private function completeDependencies(?array $dp): array { // convert expire into relative amount of seconds diff --git a/src/Caching/Storages/MemcachedStorage.php b/src/Caching/Storages/MemcachedStorage.php index 03af23b..6fe9ce4 100644 --- a/src/Caching/Storages/MemcachedStorage.php +++ b/src/Caching/Storages/MemcachedStorage.php @@ -16,7 +16,7 @@ /** * Memcached storage using memcached extension. */ -class MemcachedStorage implements Nette\Caching\Storage, Nette\Caching\BulkReader +class MemcachedStorage implements Nette\Caching\Storage, Nette\Caching\BulkReader, Nette\Caching\BulkWriter { /** @internal cache structure */ private const @@ -168,12 +168,54 @@ public function write(string $key, $data, array $dp): void } + public function bulkWrite(array $items, array $dp): void + { + if (isset($dp[Cache::Items])) { + throw new Nette\NotSupportedException('Dependent items are not supported by MemcachedStorage.'); + } + + $meta = $records = []; + $expire = 0; + if (isset($dp[Cache::Expire])) { + $expire = (int) $dp[Cache::Expire]; + if (!empty($dp[Cache::Sliding])) { + $meta[self::MetaDelta] = $expire; // sliding time + } + } + + if (isset($dp[Cache::Callbacks])) { + $meta[self::MetaCallbacks] = $dp[Cache::Callbacks]; + } + + foreach ($items as $key => $meta[self::MetaData]) { + $key = urlencode($this->prefix . $key); + $records[$key] = $meta; + + if (isset($dp[Cache::Tags]) || isset($dp[Cache::Priority])) { + if (!$this->journal) { + throw new Nette\InvalidStateException('CacheJournal has not been provided.'); + } + + $this->journal->write($key, $dp); + } + } + + $this->memcached->setMulti($records, $expire); + } + + public function remove(string $key): void { $this->memcached->delete(urlencode($this->prefix . $key), 0); } + public function bulkRemove(array $keys): void + { + $this->memcached->deleteMulti(array_map(fn($key) => urlencode($this->prefix . $key), $keys), 0); + } + + public function clean(array $conditions): void { if (!empty($conditions[Cache::All])) { diff --git a/tests/Caching/Cache.bulkSave.phpt b/tests/Caching/Cache.bulkSave.phpt new file mode 100644 index 0000000..6bd59a4 --- /dev/null +++ b/tests/Caching/Cache.bulkSave.phpt @@ -0,0 +1,45 @@ +bulkSave([1 => 'value1', 2 => 'value2']); + + $data = $cache->bulkLoad([1, 2]); + Assert::same('value1', $data[1]['data']); + Assert::same('value2', $data[2]['data']); +}); + +test('storage with bulk write support', function () { + $storage = new BulkWriteTestStorage; + $cache = new Cache($storage, 'ns'); + $cache->bulkSave([1 => 'value1', 2 => 'value2']); + + $data = $cache->bulkLoad([1, 2]); + Assert::same('value1', $data[1]['data']); + Assert::same('value2', $data[2]['data']); +}); + +test('dependencies', function () { + $storage = new BulkWriteTestStorage; + $cache = new Cache($storage, 'ns'); + $dependencies = [Cache::Tags => ['tag']]; + $cache->bulkSave([1 => 'value1', 2 => 'value2'], $dependencies); + + $data = $cache->bulkLoad([1, 2]); + Assert::same($dependencies, $data[1]['dependencies']); + Assert::same($dependencies, $data[2]['dependencies']); +}); diff --git a/tests/Caching/Cache.php b/tests/Caching/Cache.php index c25f852..c5e1436 100644 --- a/tests/Caching/Cache.php +++ b/tests/Caching/Cache.php @@ -3,6 +3,7 @@ declare(strict_types=1); use Nette\Caching\BulkReader; +use Nette\Caching\BulkWriter; use Nette\Caching\Storage; class TestStorage implements Storage @@ -55,3 +56,33 @@ public function bulkRead(array $keys): array return $result; } } + +class BulkWriteTestStorage extends TestStorage implements BulkWriter +{ + public function bulkRead(array $keys): array + { + $result = []; + foreach ($keys as $key) { + $data = $this->read($key); + if ($data !== null) { + $result[$key] = $data; + } + } + + return $result; + } + + + public function bulkRemove(array $keys): void + { + + } + + + public function bulkWrite($items, array $dp): void + { + foreach ($items as $key => $data) { + $this->write($key, $data, $dp); + } + } +} diff --git a/tests/Storages/Memcached.bulkWrite.phpt b/tests/Storages/Memcached.bulkWrite.phpt new file mode 100644 index 0000000..304f568 --- /dev/null +++ b/tests/Storages/Memcached.bulkWrite.phpt @@ -0,0 +1,36 @@ +bulkSave(['foo' => 'bar']); +Assert::same(['foo' => 'bar', 'lorem' => null], $cache->bulkLoad(['foo', 'lorem'])); + +//tags +$dependencies = [Cache::Tags => ['tag']]; +$cache->bulkSave(['foo' => 'bar'], $dependencies); +Assert::same(['foo' => 'bar'], $cache->bulkLoad(['foo'])); +$cache->clean($dependencies); +Assert::same(['foo' => null], $cache->bulkLoad(['foo']));