Cache
A sharded LRU in the process
# Cache
A sharded LRU in the process. No Redis, no Memcached.
```pascal
uses Askr.Cache;
SetCache(TCache.Create(8192, 16)); { max entries, shards }
```
```pascal
Cache.Put('customer:7', Json, 300); { seconds }
if Cache.Get(Arena, 'customer:7', V) then
...
Cache.Has('customer:7');
Cache.Forget('customer:7');
Cache.Flush;
```
```sh
askr cache:clear
```
## The arena boundary
**`Cache.Put` copies out of the caller's arena.** The value must outlive the
request that stored it.
**`Cache.Get` copies out into the caller's arena.** Do not "optimise" that
into returning a pointer into the cache: an eviction could then leave a
dangling pointer, and the value would outlive the request.
Neither copy can be skipped. If you touch that code, run the arena tests —
they zero the arena and fill it with garbage before reading back.
There is a `string` overload for code outside a request, where there is no
ambient arena.
## Sharding
The cache is sharded to keep lock contention down across workers.
> **High hash bits choose the shard, low bits choose the bucket.** The other
> way round and everything lands in the same bucket within a shard.
## Eviction
Least-recently-used, per shard, when `MaxEntries` is reached. Expiry is
checked on read.
## The FNV hash
> The FNV-1a hashes in `Askr.Cache`, `Askr.Norn.Codegen` and
> `Askr.Cli.Watch` are marked `{$push}{$R-}{$Q-}`. FNV-1a depends on the
> multiplication overflowing — that is the algorithm, not an accident.
> Without the marking the cache dies with `ERangeError` in any build with
> overflow checking, and in an ordinary build it just keeps computing
> without saying anything. If you write a new hash, mark it the same way.
## What is not here
**A shared cache across processes.** One process means one cache. Several
app processes behind a load balancer each have their own — which is fine for
derived data and wrong for anything that must be consistent. That is a real
limit, stated rather than discovered.