Skip to content

Commit

Permalink
added bulk read support [Closes #2][Closes #42]
Browse files Browse the repository at this point in the history
  • Loading branch information
matej21 authored and dg committed Jun 14, 2016
1 parent 65cc60b commit e07955b
Show file tree
Hide file tree
Showing 10 changed files with 307 additions and 3 deletions.
49 changes: 49 additions & 0 deletions src/Caching/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,55 @@ public function load($key, $fallback = NULL)
}


/**
* Reads multiple items from the cache.
* @param array
* @param callable
* @return array
*/
public function bulkLoad(array $keys, $fallback = NULL)
{
if (count($keys) === 0) {
return [];
}
foreach ($keys as $key) {
if (!is_scalar($key)) {
throw new Nette\InvalidArgumentException('Only scalar keys are allowed in bulkLoad()');
}
}
$storageKeys = array_map([$this, 'generateKey'], $keys);
if (!$this->storage instanceof IBulkReader) {
$result = array_combine($keys, array_map([$this->storage, 'read'], $storageKeys));
if ($fallback !== NULL) {
foreach ($result as $key => $value) {
if ($value === NULL) {
$result[$key] = $this->save($key, function (& $dependencies) use ($key, $fallback) {
return call_user_func_array($fallback, [$key, & $dependencies]);
});
}
}
}
return $result;
}

$cacheData = $this->storage->bulkRead($storageKeys);
$result = [];
foreach ($keys as $i => $key) {
$storageKey = $storageKeys[$i];
if (isset($cacheData[$storageKey])) {
$result[$key] = $cacheData[$storageKey];
} elseif ($fallback) {
$result[$key] = $this->save($key, function (& $dependencies) use ($key, $fallback) {
return call_user_func_array($fallback, [$key, & $dependencies]);
});
} else {
$result[$key] = NULL;
}
}
return $result;
}


/**
* Writes item into the cache.
* Dependencies are:
Expand Down
24 changes: 24 additions & 0 deletions src/Caching/IBulkReader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

namespace Nette\Caching;


/**
* Cache storage with a bulk read support.
*/
interface IBulkReader
{

/**
* Reads from cache in bulk.
* @param string key
* @return array key => value pairs, missing items are omitted
*/
function bulkRead(array $keys);

}
35 changes: 34 additions & 1 deletion src/Caching/Storages/NewMemcachedStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/**
* Memcached storage using memcached extension.
*/
class NewMemcachedStorage implements Nette\Caching\IStorage
class NewMemcachedStorage implements Nette\Caching\IStorage, Nette\Caching\IBulkReader
{
use Nette\SmartObject;

Expand Down Expand Up @@ -110,6 +110,39 @@ public function read($key)
}


/**
* Reads from cache in bulk.
* @param string key
* @return array key => value pairs, missing items are omitted
*/
public function bulkRead(array $keys)
{
$prefixedKeys = array_map(function ($key) {
return urlencode($this->prefix . $key);
}, $keys);
$keys = array_combine($prefixedKeys, $keys);
$metas = $this->memcached->getMulti($prefixedKeys);
$result = [];
$deleteKeys = [];
foreach ($metas as $prefixedKey => $meta) {
if (!empty($meta[self::META_CALLBACKS]) && !Cache::checkCallbacks($meta[self::META_CALLBACKS])) {
$deleteKeys[] = $prefixedKey;
} else {
$result[$keys[$prefixedKey]] = $meta[self::META_DATA];
}

if (!empty($meta[self::META_DELTA])) {
$this->memcached->replace($prefixedKey, $meta, $meta[self::META_DELTA] + time());
}
}
if (!empty($deleteKeys)) {
$this->memcached->deleteMulti($deleteKeys, 0);
}

return $result;
}


/**
* Prevents item reading and writing. Lock is released by write() or remove().
* @param string key
Expand Down
27 changes: 26 additions & 1 deletion src/Caching/Storages/SQLiteStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/**
* SQLite storage.
*/
class SQLiteStorage implements Nette\Caching\IStorage
class SQLiteStorage implements Nette\Caching\IStorage, Nette\Caching\IBulkReader
{
use Nette\SmartObject;

Expand Down Expand Up @@ -64,6 +64,31 @@ public function read($key)
}


/**
* Reads from cache in bulk.
* @param string key
* @return array key => value pairs, missing items are omitted
*/
public function bulkRead(array $keys)
{
$stmt = $this->pdo->prepare('SELECT key, data, slide FROM cache WHERE key IN (?' . str_repeat(',?', count($keys) - 1) . ') AND (expire IS NULL OR expire >= ?)');
$stmt->execute(array_merge($keys, [time()]));
$result = [];
$updateSlide = [];
foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
if ($row['slide'] !== NULL) {
$updateSlide[] = $row['key'];
}
$result[$row['key']] = unserialize($row['data']);
}
if (!empty($updateSlide)) {
$stmt = $this->pdo->prepare('UPDATE cache SET expire = ? + slide WHERE key IN(?' . str_repeat(',?', count($updateSlide) - 1) . ')');
$stmt->execute(array_merge([time()], $updateSlide));
}
return $result;
}


/**
* Prevents item reading and writing. Lock is released by write() or remove().
* @param string key
Expand Down
64 changes: 64 additions & 0 deletions tests/Caching/Cache.bulkLoad.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

/**
* Test: Nette\Caching\Cache load().
*/

use Nette\Caching\Cache;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';

require __DIR__ . '/Cache.php';

// storage without bulk load support
test(function () {
$storage = new TestStorage;
$cache = new Cache($storage, 'ns');
Assert::same([1 => NULL, 2 => NULL], $cache->bulkLoad([1, 2]), 'data');

Assert::same([1 => 1, 2 => 2], $cache->bulkLoad([1, 2], function ($key) {
return $key;
}));

$data = $cache->bulkLoad([1, 2]);
Assert::same(1, $data[1]['data']);
Assert::same(2, $data[2]['data']);
});

// storage with bulk load support
test(function () {
$storage = new BulkReadTestStorage;
$cache = new Cache($storage, 'ns');
Assert::same([1 => NULL, 2 => NULL], $cache->bulkLoad([1, 2]));

Assert::same([1 => 1, 2 => 2], $cache->bulkLoad([1, 2], function ($key) {
return $key;
}));

$data = $cache->bulkLoad([1, 2]);
Assert::same(1, $data[1]['data']);
Assert::same(2, $data[2]['data']);
});

// dependencies
test(function () {
$storage = new BulkReadTestStorage;
$cache = new Cache($storage, 'ns');
$dependencies = [Cache::TAGS => 'tag'];
$cache->bulkLoad([1], function ($key, & $deps) use ($dependencies) {
$deps = $dependencies;
return $key;
});

$data = $cache->bulkLoad([1, 2]);
Assert::same($dependencies, $data[1]['dependencies']);
});

test(function () {
Assert::exception(function () {
$cache = new Cache(new BulkReadTestStorage());
$cache->bulkLoad([[1]]);
}, Nette\InvalidArgumentException::class, 'Only scalar keys are allowed in bulkLoad()');
});
18 changes: 18 additions & 0 deletions tests/Caching/Cache.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use Nette\Caching\IBulkReader;
use Nette\Caching\IStorage;

class TestStorage implements IStorage
Expand All @@ -25,3 +26,20 @@ public function remove($key) {}

public function clean(array $conditions) {}
}

class BulkReadTestStorage extends TestStorage implements IBulkReader
{
function bulkRead(array $keys)
{
$result = [];
foreach ($keys as $key) {
$data = $this->read($key);
if ($data !== NULL) {
$result[$key] = $data;
}
}

return $result;
}

}
27 changes: 27 additions & 0 deletions tests/Storages/NewMemcached.bulkRead.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/**
* Test: Nette\Caching\Storages\NewMemcachedStorage and bulkRead
*/

use Nette\Caching\Storages\NewMemcachedStorage;
use Nette\Caching\Cache;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


if (!NewMemcachedStorage::isAvailable()) {
Tester\Environment::skip('Requires PHP extension Memcached.');
}

Tester\Environment::lock('memcached-files', TEMP_DIR);



$cache = new Cache(new NewMemcachedStorage('localhost'));

$cache->save('foo', 'bar');

Assert::same(['foo' => 'bar', 'lorem' => NULL], $cache->bulkLoad(['foo', 'lorem']));
23 changes: 22 additions & 1 deletion tests/Storages/NewMemcached.sliding.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,31 @@ for ($i = 0; $i < 5; $i++) {
sleep(1);

Assert::truthy($cache->load($key));

}

// Sleeping few seconds...
sleep(5);

Assert::null($cache->load($key));


// Bulk

// Writing cache...
$cache->save($key, $value, [
Cache::EXPIRATION => time() + 3,
Cache::SLIDING => TRUE,
]);


for ($i = 0; $i < 5; $i++) {
// Sleeping 1 second
sleep(1);

Assert::truthy($cache->bulkLoad([$key])[$key]);
}

// Sleeping few seconds...
sleep(5);

Assert::null($cache->load([$key])[$key]);
23 changes: 23 additions & 0 deletions tests/Storages/SQLiteStorage.bulkRead.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

/**
* Test: Nette\Caching\Storages\SQLiteStorage and bulk read.
*/

use Nette\Caching\Cache;
use Nette\Caching\Storages\SQLiteStorage;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


if (!extension_loaded('pdo_sqlite')) {
Tester\Environment::skip('Requires PHP extension pdo_sqlite.');
}


$cache = new Cache(new SQLiteStorage(':memory:'));
$cache->save('foo', 'bar');

Assert::same(['foo' => 'bar', 'lorem' => NULL], $cache->bulkLoad(['foo', 'lorem']));
20 changes: 20 additions & 0 deletions tests/Storages/SQLiteStorage.sliding.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,23 @@ for ($i = 0; $i < 5; $i++) {
sleep(5);

Assert::null($cache->load($key));


// Writing cache...
$cache->save($key, $value, [
Cache::EXPIRATION => time() + 3,
Cache::SLIDING => TRUE,
]);


for ($i = 0; $i < 5; $i++) {
// Sleeping 1 second
sleep(1);

Assert::truthy($cache->bulkLoad([$key])[$key]);
}

// Sleeping few seconds...
sleep(5);

Assert::null($cache->bulkLoad([$key])[$key]);

0 comments on commit e07955b

Please sign in to comment.