Skip to content

Commit

Permalink
Merge pull request #15 from byjg/5.0
Browse files Browse the repository at this point in the history
Add GarbageCollectorInterface.php
  • Loading branch information
byjg authored Dec 24, 2024
2 parents cc010ce + 62e0d2e commit b52b5d7
Show file tree
Hide file tree
Showing 18 changed files with 599 additions and 124 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/phpunit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ jobs:
services:
memcached:
image: memcached
ports:
- "11211:11211"
redis:
image: redis
ports:
- "6379:6379"
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ PSR-6 Getting Started: [here](docs/basic-usage-psr6-cachepool.md)
| [\ByJG\Cache\Psr16\TmpfsCacheEngine](docs/class-tmpfs-cache-engine.md) | Uses the Tmpfs as the cache engine |
| [\ByJG\Cache\Psr16\RedisCachedEngine](docs/class-redis-cache-engine.md) | uses the Redis as cache |
| [\ByJG\Cache\Psr16\SessionCachedEngine](docs/class-session-cache-engine.md) | uses the PHP session as cache |
| [\ByJG\Cache\Psr16\ShmopCachedEngine](docs/class-shmop-cache-engine.md) | uses the shared memory area for cache |
| [\ByJG\Cache\Psr16\ShmopCacheEngine](docs/class-shmop-cache-engine.md) (deprecated) | uses the shared memory area for cache. Use TmpfsCacheEngine. |


## Logging cache commands
Expand All @@ -52,6 +52,17 @@ You can use a PSR-11 compatible to retrieve the cache keys.

See more [here](docs/psr11-usage.md)

## Beyond the PSR protocol

The PSR protocol is a good way to standardize the cache access,
but sometimes you need to go beyond the protocol.

Some cache engines have additional features that are not covered by the PSR protocol.

Some examples are:
- [Atomic Operations](docs/atomic-operations.md)
- [Garbage Collection](docs/garbage-collection.md)

## Install

Just type:
Expand Down
17 changes: 17 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
services:
memcached:
image: memcached
container_name: memcached
ports:
- "11211:11211"

redis:
image: redis
container_name: redis
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
56 changes: 56 additions & 0 deletions docs/atomic-operations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Atomic Operations

Some cache engines allow you to do atomic operations such as incrementing or decrementing a value.

Besides this is not cache operation, it is a common operation in cache engines.

The advantage of using atomic operations is that you can avoid race conditions when multiple processes
are trying to update the same value.

The atomic operations are:
- Increment: Increment a value by a given number
- Decrement: Decrement a value by a given number
- Add: Add a value to a list in the cache

The engines that support atomic operations have to implement the `AtomicOperationInterface`.

Some engines that support atomic operations are:
- RedisCachedEngine
- MemcachedEngine
- TmpfsCacheEngine
- FileSystemCacheEngine

## Increment

The increment operation is used to increment a value by a given number.

```php
<?php
/** @var \ByJG\Cache\AtomicOperationInterface $cache */
$cache->increment('my-key', 1);
```

## Decrement

The decrement operation is used to decrement a value by a given number.

```php
<?php
/** @var \ByJG\Cache\AtomicOperationInterface $cache */
$cache->decrement('my-key', 1);
```

## Add

The add operation is used to add a value to a list in the cache.

```php
<?php
/** @var \ByJG\Cache\AtomicOperationInterface $cache */
$cache->add('my-key', 'value1');
$cache->add('my-key', 'value2');
$cache->add('my-key', 'value3');

print_r($cache->get('my-key')); // ['value1', 'value2', 'value3']
```

30 changes: 30 additions & 0 deletions docs/garbage-collection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Garbage Collection

Some cache engines need to have a garbage collection process to remove the expired keys.

In some engines like `Memcached` and `Redis` the garbage collection is done automatically by the engine itself.

In other engines like `FileSystem` and `Array` there is no such process. The current implementation
is based on the Best Effort. It means an expired key is removed only when you try to access it.

If the cache engine has a low hit rate, it is recommended to run a garbage collection process
to avoid the cache to grow indefinitely.

The classes that implement the `GarbageCollectionInterface` have the method `collectGarbage()`.

Some engines that support garbage collection are:
- FileSystemCacheEngine
- ArrayCacheEngine
- TmpfsCacheEngine

## Example

```php
<?php
/** @var \ByJG\Cache\GarbageCollectionInterface $cache */
$cache->collectGarbage();
```

Note: The garbage collection process is blocking.
It means the process will be slow if you have a lot of keys to remove.

14 changes: 14 additions & 0 deletions src/AtomicOperationInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace ByJG\Cache;

use DateInterval;

interface AtomicOperationInterface
{
public function increment(string $key, int $value = 1, DateInterval|int|null $ttl = null): int;

public function decrement(string $key, int $value = 1, DateInterval|int|null $ttl = null): int;

public function add(string $key, $value, DateInterval|int|null $ttl = null): array;
}
24 changes: 0 additions & 24 deletions src/CacheLockInterface.php

This file was deleted.

4 changes: 2 additions & 2 deletions src/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ public static function createRedisCacheEngine(?string $servers = null, ?string $
);
}

public static function createTmpfsCachePool(?LoggerInterface $logger = null): CachePool
public static function createTmpfsCachePool(string $prefix = 'cache', ?LoggerInterface $logger = null): CachePool
{
return new CachePool(
new TmpfsCacheEngine($logger)
new TmpfsCacheEngine($prefix, $logger)
);
}

Expand Down
10 changes: 10 additions & 0 deletions src/GarbageCollectorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace ByJG\Cache;

interface GarbageCollectorInterface
{
public function collectGarbage();

public function getTtl(string $key): ?int;
}
33 changes: 28 additions & 5 deletions src/Psr16/ArrayCacheEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
namespace ByJG\Cache\Psr16;

use ByJG\Cache\Exception\InvalidArgumentException;
use ByJG\Cache\GarbageCollectorInterface;
use DateInterval;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

class ArrayCacheEngine extends BaseCacheEngine
class ArrayCacheEngine extends BaseCacheEngine implements GarbageCollectorInterface
{

protected array $cache = [];
protected array $cache = [
"ttl" => []
];

protected LoggerInterface|null $logger = null;

Expand Down Expand Up @@ -41,7 +44,7 @@ public function has(string $key): bool
{
$key = $this->getKeyFromContainer($key);
if (isset($this->cache[$key])) {
if (isset($this->cache["$key.ttl"]) && time() >= $this->cache["$key.ttl"]) {
if (isset($this->cache['ttl']["$key"]) && time() >= $this->cache["ttl"]["$key"]) {
$this->delete($key);
return false;
}
Expand Down Expand Up @@ -93,7 +96,7 @@ public function set(string $key, mixed $value, DateInterval|int|null $ttl = null

$this->cache[$key] = serialize($value);
if (!empty($ttl)) {
$this->cache["$key.ttl"] = $this->addToNow($ttl);
$this->cache["ttl"]["$key"] = $this->addToNow($ttl);
}

return true;
Expand All @@ -116,12 +119,32 @@ public function delete(string $key): bool
$key = $this->getKeyFromContainer($key);

unset($this->cache[$key]);
unset($this->cache["$key.ttl"]);
unset($this->cache["ttl"]["$key"]);
return true;
}

public function isAvailable(): bool
{
return true;
}

public function collectGarbage()
{
foreach ($this->cache["ttl"] as $key => $ttl) {
if (time() >= $ttl) {
unset($this->cache[$key]);
unset($this->cache["ttl"]["$key"]);
}
}
}

public function getTtl(string $key): ?int
{
$key = $this->getKeyFromContainer($key);
if (isset($this->cache["ttl"]["$key"])) {
return $this->cache["ttl"]["$key"];
}

return null;
}
}
Loading

0 comments on commit b52b5d7

Please sign in to comment.