Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GarbageCollectorInterface.php #15

Merged
merged 4 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading