Skip to content

Commit d2bf895

Browse files
Merge pull request #12 from Vectorial1024/db_evict_loop
Database Evictor: fix handling of cache prefixes
2 parents c31b5de + b599e3a commit d2bf895

File tree

2 files changed

+30
-17
lines changed

2 files changed

+30
-17
lines changed

README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ Using any of the above cache drivers without regularly removing the expired item
2020

2121
The `cache:clear` command from Laravel works, but might not be the thing you want. It does not check item expiry (it removes everything), and also clears the Laravel framework cache (e.g. `/bootstrap/cache/*`), which can be especially problematic when you are using the `file` cache driver (consider a case: cache items are created by the `www-data` user but `/bootstrap/cache/*` is owned by the `ubuntu` user).
2222

23-
In this case, this library can help you remove only the expired items in your cache. See below sections for more details.
23+
In this case, this tool can help you remove only the expired items in your cache. See below sections for more details.
2424

25-
This library is designed to be memory efficient and (for `database` caches) non-blocking, so even if there are a lot of items in the cache (e.g. you are running this for the first time to deal with an oversized cache), it can still run reasonably well.
25+
This tool is designed to be memory efficient and (for `database` caches) non-blocking, so even if there are a lot of items in the cache (e.g. you are running this for the first time to deal with an oversized cache), it can still run reasonably well.
2626

2727
## Install
2828
via Composer:
@@ -39,7 +39,7 @@ The following cache drivers from `cache.php` are currently supported:
3939
- `database`
4040
- `file`
4141

42-
Some drivers (e.g. `memcached`, `redis`) will never be supported because they have their own item eviction mechanisms; use those features instead of this library!
42+
Some drivers (e.g. `memcached`, `redis`) will never be supported because they have their own item eviction mechanisms; use those features instead of this tool!
4343

4444
Custom eviction strategies can be defined for other cache drivers that does not have their own eviction mechanisms (see FAQ section).
4545

@@ -74,7 +74,7 @@ Schedule::command(CacheEvictCommand::class, ['target' => 'file'])->daily()->runI
7474
```
7575

7676
### The relationship with `cache.php`
77-
This library checks the cache *name* (not *driver*!) inside `cache.php` to determine which cache to clear. This means, if you have the following `cache.php` ...
77+
This tool checks the cache *name* (not *driver*!) inside `cache.php` to determine which cache to clear. This means, if you have the following `cache.php` ...
7878

7979
```php
8080
[
@@ -124,8 +124,12 @@ public function boot()
124124
}
125125
```
126126

127-
### Will this library help me reclaim `database` disk spaces?
128-
No, but if you are using this library regularly to evict expired items, then you do not need to worry about reclaiming free space. For more details, talk with a system admin/database specialist.
127+
### Will this tool help me reclaim `database` disk spaces?
128+
No, but if you are using this tool regularly to evict expired items, then you do not need to worry about reclaiming free space. For more details, talk with a system admin/database specialist.
129+
130+
### Can I run this tool during high-traffic hours?
131+
Sure! This tool is designed with high-traffic situations in mind. It is memory-efficient and (for `database` caches) non-blocking.
132+
Feel free to run this tool e.g. hourly.
129133

130134
[packagist-url]: https://packagist.org/packages/vectorial1024/laravel-cache-evict
131135
[packagist-stats-url]: https://packagist.org/packages/vectorial1024/laravel-cache-evict/stats

src/Database/DatabaseEvictStrategy.php

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class DatabaseEvictStrategy extends AbstractEvictStrategy
2525

2626
protected float $elapsedTime = 0;
2727

28+
protected string $cachePrefix = "";
29+
2830
public function __construct(string $storeName)
2931
{
3032
parent::__construct($storeName);
@@ -34,14 +36,17 @@ public function __construct(string $storeName)
3436
$this->dbConn = DB::connection($storeConn);
3537
$this->dbTable = config("cache.stores.{$storeName}.table");
3638
$this->cacheStore = Cache::store($this->storeName)->getStore();
39+
40+
// write down the cache prefix; cache might have that
41+
// currently Laravel's cache prefix is only applied very easily by "{prefix}{key}"
42+
$this->cachePrefix = $this->cacheStore->getPrefix();
3743
}
3844

3945
public function execute(): void
4046
{
4147
// read the cache config and set up targets
4248
$this->deletedRecords = 0;
4349
$this->deletedRecordSizes = 0;
44-
$this->deletedDirs = 0;
4550
$this->elapsedTime = 0;
4651

4752
// we use a memory-efficient way of deleting items.
@@ -50,10 +55,9 @@ public function execute(): void
5055
// the key field is indexed, so it is not too bad
5156
Partyline::info("Finding relevant cache records...");
5257
// cache might have prefix!
53-
$cachePrefix = $this->cacheStore->getPrefix();
5458
$itemCount = $this->dbConn
5559
->table($this->dbTable)
56-
->where('key', 'LIKE', "$cachePrefix%")
60+
->where('key', 'LIKE', "{$this->cachePrefix}%")
5761
->count();
5862
Partyline::info("Found $itemCount records; processing...");
5963

@@ -63,9 +67,8 @@ public function execute(): void
6367
$progressBar->setMaxSteps($itemCount);
6468
foreach ($this->yieldCacheTableItems() as $cacheItem) {
6569
// read record details
66-
$currentUserKey = $cacheItem->key;
70+
$currentActualKey = $cacheItem->key;
6771
$currentExpiration = $cacheItem->expiration;
68-
$currentActualKey = "{$cachePrefix}{$currentUserKey}";
6972
// currently timestamps are 32-bit, so are 4 bytes
7073
$estimatedBytes = (int) ($cacheItem->key_bytes + $cacheItem->value_bytes + 4);
7174
$progressBar->advance();
@@ -82,7 +85,7 @@ public function execute(): void
8285
->where('expiration', '=', $currentExpiration)
8386
->delete();
8487
if ($rowsAffected) {
85-
// item really expired with no new values
88+
// item really expired with no new updates
8689
$this->deletedRecords += 1;
8790
$this->deletedRecordSizes += $estimatedBytes;
8891
}
@@ -102,19 +105,25 @@ public function execute(): void
102105
Partyline::info("Note: no free space reclaimed; reclaiming free space should be done manually!");
103106
}
104107

108+
/**
109+
* Yields the next item from the cache table that belongs to this cache.
110+
*
111+
* This method will return the actual key (with the cache prefix if exists) of the entry.
112+
* @return \Generator<mixed, object, mixed, void>
113+
*/
105114
protected function yieldCacheTableItems(): \Generator
106115
{
107116
// there might be a prefix for the cache store!
108-
$cachePrefix = $this->cacheStore->getPrefix();
109-
$currentUserKey = "";
117+
$cachePrefix = $this->cachePrefix;
118+
// initialize the key to be just the cache prefix as the "zero string".
119+
$currentActualKey = $cachePrefix;
110120
// loop until no more items
111121
while (true) {
112122
// find the next key
113-
$actualKey = "{$cachePrefix}{$currentUserKey}";
114123
$record = $this->dbConn
115124
->table($this->dbTable)
116125
->select(['key', 'expiration', DB::raw('LENGTH(key) AS key_bytes'), DB::raw('LENGTH(value) AS value_bytes')])
117-
->where('key', '>', $actualKey)
126+
->where('key', '>', $currentActualKey)
118127
->where('key', 'LIKE', "$cachePrefix%")
119128
->limit(1)
120129
->first();
@@ -124,7 +133,7 @@ protected function yieldCacheTableItems(): \Generator
124133
}
125134

126135
yield $record;
127-
$currentUserKey = $record->key;
136+
$currentActualKey = $record->key;
128137
}
129138
// loop exit handled inside while loop
130139
}

0 commit comments

Comments
 (0)