Add a rememberLazily method to the cache repository. #50005
Replies: 2 comments
-
I was thinking about this idea a little further. I still think this is a lot of overhead to store in the cache, while it is really not needed. Although it removes the risk of a faulty scheduler, we would risk it never getting updated because of a faulty queue. I came up with a better solution, which lowers both risks, by utilizing the queue AND the scheduler. It is probably more what @timacdonald had in mind. and it is not a challenge to build in natively when it comes to preheating/warming up (edit, I saw to late you did queue) <?php
namespace App\Console;
use Cache;
use DateTimeInterface;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\PendingDispatch;
use Illuminate\Queue\InteractsWithQueue;
abstract class Warmable implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
use Queueable, InteractsWithQueue;
protected bool $preheated = false;
public function __construct(
private ?string $key = null,
private DateTimeInterface|int|null $ttl = null,
private ?string $store = null,
private bool $preheat = true
)
{
if ($this->preheat && !$this->exists()) {
$this->warmup();
}
}
public function __invoke(): void
{
$this->warmup();
}
protected function key(): string
{
return 'warmable.' . static::class;
}
protected function store(): ?string
{
return $this->store;
}
public function ttl(): DateTimeInterface|int|null
{
return $this->ttl;
}
abstract protected function warmable(): mixed;
public static function preheat(
string $key = null,
DateTimeInterface|int|null $ttl = null,
string $store = null,
): PendingDispatch
{
return dispatch(new static($key, $ttl, $store, preheat: false));
}
public static function withoutPreheating(
string $key = null,
DateTimeInterface|int|null $ttl = null,
string $store = null,
): self
{
return new static($key, $ttl, $store, preheat: false);
}
public static function witPreheating(
string $key = null,
DateTimeInterface|int|null $ttl = null,
string $store = null,
): self
{
return new static($key, $ttl, $store);
}
public static function get(
string $key = null,
DateTimeInterface|int|null $ttl = null,
string $store = null,
bool $preheat = true,
): mixed
{
$warmable = new static($key, $ttl, $store, $preheat);
return Cache::store($warmable->getStore())
->get(
$warmable->getKey(),
fn() => $this->warmable()
);
}
public static function cooldown(
string $key = null,
string $store = null
): void
{
$warmable = new static(
$key,
store: $store,
preheat: false
);
Cache::store($warmable->getStore())
->delete($warmable->getKey());
}
protected function exists(): bool
{
return Cache::store($this->getStore())
->has($this->getKey());
}
private function getStore(): ?string
{
return $this->store ?? $this->store();
}
private function getKey(): string
{
return $this->key ?? $this->key();
}
private function getTtl(): DateTimeInterface|int|null
{
return $this->ttl ?? $this->ttl();
}
protected function warmup(): void
{
if ($this->preheated) {
return;
}
Cache::store($this->getStore())
->put(
$this->getKey(),
$this->warmable(),
$this->getTtl()
);
$this->preheated = true;
}
} You are still in control of what needs to be done when it does not exist, as the constructor does below an example implementation. It just sleeps 3 seconds and then returns 'hello world'. class HeavyDatabaseQuery extends Warmable
{
protected function warmable(): string
{
sleep(3);
return 'hello world';
}
} The Warmable is __invokable, you can just throw it in Due to preheating by default, the cache is created in the process of the scheduler instead of the job. use Whenever you need the item, you can call If this is something we want to be part of Laravel, I am up for whipping up a PR. If not, I am willing to package this up. |
Beta Was this translation helpful? Give feedback.
-
Couldn't wait, created a package for it. https://github.com/henzeb/warmable-laravel It also supports grace periods. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi, my name is Pascal 👋 and during this LaraconEU @timacdonald got me thinking about a lazy cache remember function. A function which for example caches a value for an hour and and when it expires it does not immediately refreshes the value, but will do it afterwards and return the expired value just once. As most of the time a certain cache is used when there is lots of demand for the value and it can be cached. So if in this cache it's not stored the actual hour, but lets say an hour and one minute.
So I came up with this idea and I'm really curious about the opinion of you, the community, so here it is:
The LazyCacheObject will look like this:
Let me know if you have any suggestions, rephrasing some variables / methods.
I did think of some edge cases, so In case where there could be a longer period of inactivity between the last request and the new one, there could be an option build in which has a kind of threshold. So a cache may expire 10% of the actual TTL, so for a TTL of an hour, the threshold would be like 6 min. Within that timeframe the old value is shown and if it's after the 6 minutes the cache will be refreshed during the request. Another option would be pre-warming the cache, but that will bring quite a lot of challenges to build natively in.
Thank you in advanced ✌🏼
Beta Was this translation helpful? Give feedback.
All reactions