Skip to content

Commit

Permalink
Suggestion #2 - Clean up of expired cache items
Browse files Browse the repository at this point in the history
  • Loading branch information
terrylinooo committed Oct 29, 2020
1 parent 70ac4ac commit d3b029c
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 73 deletions.
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Before you use, make sure you have the required PHP modules installed on the sys
- setMultiple
- deleteMultiple
- clear
- clearExpiredItems `(Non-PSR-16)`
- Build Data Schema
- MySQL
- SQLite
Expand Down Expand Up @@ -173,6 +174,7 @@ Those API methods are defined on `Psr\SimpleCache\CacheInterface`. Please check
- getMultiple
- deleteMultiple
- clear
- clearExpiredItems *(Non-PSR-16)*

### set

Expand Down Expand Up @@ -369,6 +371,38 @@ if ($cache->clear()) {
// All cached data has been deleted successfully.
```

### clearExpiredItems `Non-PSR-16`

```php
public function clearExpiredItems(): array
```

This method will return a list of the removed cache keys.

*Note*: **Redis** and **Memcache**, **Memcached** drivers will always return an empty array. See *Garbage Collection* section below.

Example:

```php
$cache->set('foo', 'bar', 300);
$cache->set('foo2', 'bar2', 5);
$cache->set('foo3', 'bar3', 5);

sleep(6);

$expiredItems = $cache->clearExpiredItems();
var_dump($expiredItems);

/*
array(2) {
["foo2"]=>
string(4) "bar2"
["foo3"]=>
string(4) "bar3"
}
*/
```

---

## Build Data Schema
Expand Down Expand Up @@ -402,6 +436,39 @@ CREATE TABLE IF NOT EXISTS cache_data (
);
```

---

## Garbage Collection

For built-in drivers, you can enable the garbage collection to clear expired cache from your system automatically.

Use those parameters:
```php
$config = [
'gc_enable' => true,
'gc_divisor' => 100, // default
'gc_probability' => 1, // default
];
```
It means there will be a `1%` chance of performing the garbage collection.
Do not use it as 100% chance becasue it will fetch all keys and check them one by one, totally unnecessary.

Example:
```php
$driver = new \Shieldon\SimpleCache\Cache('file', [
'storage' => __DIR__ . '/../tmp',
'gc_enable' => true,
]);
```

You can just use the `gc_enable` to enable garbage collection.

### Note

For **Redis** and **Memcache**, **Memcached** drivers, there is no need to use this method becasue that the expired items will be cleared automatically.



---

## Author
Expand Down
44 changes: 43 additions & 1 deletion src/SimpleCache/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public function __construct($driver = '', array $settings = [])
$class = '\Shieldon\SimpleCache\Driver\\' . $class;

$this->driver = new $class($settings);
$this->gc($settings);
}
}

Expand Down Expand Up @@ -126,7 +127,7 @@ public function deleteMultiple($keys)
}

/**
* Create or rebuid the data schema.
* Create or rebuid the data schema. [Non-PSR-16]
* This method is avaialbe for Mysql and Sqlite drivers.
*
* @return bool
Expand All @@ -139,4 +140,45 @@ public function rebuild(): bool

return false;
}

/**
* Clear all expired items. [Non-PSR-16]
*
* @return array The list of the removed items.
*/
public function clearExpiredItems(): array
{
return $this->gc([
'gc_enable' => true,
'gc_probability' => 1,
'gc_divisor' => 1,
]);
}

/**
* Performing cache data garbage collection for drivers that don't have
* ability to remove expired items automatically.
* This method is not needed for Redis and Memcached driver.
*
* @param array $settings [bool $gc_enable, int $gc_probability, int $gc_divisor]
*
* @return array The list of the removed items.
*/
protected function gc(array $settings = []): array
{
if (empty($settings['gc_enable'])) {
return [];
}

$removedList = [];

$probability = $settings['gc_probability'] ?? 1;
$divisor = $settings['gc_divisor'] ?? 100;

if (method_exists($this->driver, 'gc')) {
$removedList = $this->driver->gc($probability, $divisor);
}

return $removedList;
}
}
75 changes: 41 additions & 34 deletions src/SimpleCache/CacheProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,70 +154,77 @@ public function deleteMultiple($keys)
return true;
}

/**
* Check if the TTL is expired or not.
*
* @param int $ttl The time to live of a cached data.
* @param int $timestamp The unix timesamp that want to check.
*
* @return bool
*/
protected function isExpired(int $ttl, int $timestamp): bool
{
$now = time();

// If $ttl equal to 0 means that never expires.
if (empty($ttl)) {
return false;

} elseif ($now - $timestamp < $ttl) {
return false;
}

return true;
}

/**
* Performing cache data garbage collection for drivers that don't have
* ability to remove expired items automatically.
* This method is not needed for Redis and Memcached driver.
*
* @param int $expires The time of expiring.
* @param int $probability Numerator.
* @param int $divisor Denominator.
*
* @return bool
* @return array
*/
protected function gc(int $expires, int $probability, int $divisor): bool
public function gc(int $probability, int $divisor): array
{
if ($probability > $divisor) {
$probability = $divisor;
}
$chance = intval($divisor / $probability);
$hit = rand(1, $chance);
$hit = rand(1, $chance);
$list = [];

if ($hit === 1) {


// Always return [] from Redis and Memcached driver.
$data = $this->getAll();

if (!empty($data)) {
foreach ($data as $key => $value) {
$ttl = (int) $value['ttl'];
$lasttime = (int) $value['timestamp'];
if (time() - $lasttime > $expires) {

if ($this->isExpired($ttl, $lasttime)) {
$this->delete($key);

$list[] = $key;
}
}
}
return true;
}
return false;
return $list;
}

/**
* Check if the TTL is expired or not.
*
* @param int $ttl The time to live of a cached data.
* @param int $timestamp The unix timesamp that want to check.
*
* @return bool
*/
protected function isExpired(int $ttl, int $timestamp): bool
{
$now = time();

// If $ttl equal to 0 means that never expires.
if (empty($ttl)) {
return false;

} elseif ($now - $timestamp < $ttl) {
return false;
}

return true;
}

/**
* Fetch all cache items to prepare removing expired items.
* This method is used only in `gc()`.
* This method is not needed for Redis and Memcached driver because that
* it is used only in `gc()`.
*
* @return array
*/
private function getAll(): array
protected function getAll(): array
{
return [];
}
Expand Down
20 changes: 0 additions & 20 deletions src/SimpleCache/Driver/Redis.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,26 +228,6 @@ protected function doHas(string $key): bool
// @codeCoverageIgnoreEnd
}

/**
* Fetch all cache items.
*
* @return array
*/
protected function getAll(): array
{
$list = [];
$keys = $this->redis->keys('sc:*');

if (!empty($keys)) {
foreach ($keys as $key) {
$value = $this->doGet($key);
$key = str_replace('sc_', '', $key);
$list[$key] = $value;
}
}
return $list;
}

/**
* Get the key name of a cache.
*
Expand Down
Loading

0 comments on commit d3b029c

Please sign in to comment.