- Cache: Get, Set, Has, Forget, Remember (degrades on cache errors), Increment, Flush with per-entry TTLs under <prefix>cache: - memory driver on otter v2.3.0 bounded by cache.memory.max_entries; Redis driver with SET PX, INCRBY and a Flush limited to SCAN MATCH <prefix>cache:* plus UNLINK - Install publishes the Cache (sharing one Redis client with the rate limiter); FromApp serves jobs and commands without a dial - new docs/services/caching.md, configuration rows, cooler README
97 lines
5.5 KiB
Markdown
97 lines
5.5 KiB
Markdown
---
|
|
title: Caching
|
|
description: Cache values with TTLs through cooler.Cache, in process or shared through Redis, under a per-application key prefix.
|
|
section: services
|
|
order: 45
|
|
---
|
|
# Caching
|
|
|
|
WinterCMS and Laravel cache through the `Cache` facade: `Cache::get`, `Cache::put`, `Cache::remember` and friends, backed by the store `config/cache.php` selects. SummerCMS has the same core through [cooler](../../modules/cooler/README.md): one `cooler.Cache` per application, with an in-process driver by default and a Redis driver shared by every instance.
|
|
|
|
## The Cache API
|
|
|
|
| Laravel | cooler |
|
|
|---------|--------|
|
|
| `Cache::get($key)` | `cooler.Cache.Get` returns the bytes and whether the key was found. |
|
|
| `Cache::put($key, $value, $ttl)` | `cooler.Cache.Set` stores bytes for a TTL. |
|
|
| `Cache::forever($key, $value)` | `cooler.Cache.Set` with a TTL of 0. |
|
|
| `Cache::has($key)` | `cooler.Cache.Has`. |
|
|
| `Cache::forget($key)` | `cooler.Cache.Forget`. |
|
|
| `Cache::remember($key, $ttl, $callback)` | `cooler.Cache.Remember`. |
|
|
| `Cache::increment($key, $by)` | `cooler.Cache.Increment`. |
|
|
| `Cache::flush()` | `cooler.Cache.Flush`, limited to this application's cache keys. |
|
|
|
|
Values are bytes, so encode structured values yourself, for example with `encoding/json`. There are no tags and no locks.
|
|
|
|
A plugin looks the cache up on the application with `app.Lookup[cooler.Cache]()`, or asks `cooler.FromApp` for it. Here the second `cooler.Cache.Remember` call is answered from the cache:
|
|
|
|
```go src=modules/cooler/example_test.go#ExampleFromApp
|
|
// serve publishes the Cache at start-up; a job or console command asks
|
|
// FromApp, which builds the configured driver (memory here) once.
|
|
app := backpack.New(nil)
|
|
cache, err := cooler.FromApp(app)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
ctx := context.Background()
|
|
loads := 0
|
|
popular := func(context.Context) ([]byte, error) {
|
|
loads++
|
|
return json.Marshal([]string{"Hello", "Summer"})
|
|
}
|
|
var titles []string
|
|
for range 2 {
|
|
raw, err := cache.Remember(ctx, "blog:popular", 10*time.Minute, popular)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
if err := json.Unmarshal(raw, &titles); err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
}
|
|
fmt.Println(titles, loads)
|
|
// Output: [Hello Summer] 1
|
|
```
|
|
|
|
## Drivers
|
|
|
|
`cache.driver` selects the driver:
|
|
|
|
- `memory` (the default) keeps values in the process, in an otter cache bounded by `cache.memory.max_entries` (default 10000; `cooler.DefaultMaxEntries`). Beyond that, the least valuable entries are evicted, so keys derived from request data cannot grow memory without limit. Each instance has its own memory cache.
|
|
- `redis` keeps values in Redis at `redis.url`, shared by every instance. When the rate limiter also uses Redis (`http.rate_limit.store: redis`, see [Rate limiting](rate-limiting.md#sharing-limits-across-instances)), both use one connection pool.
|
|
|
|
```yaml
|
|
# config/cache.yaml
|
|
driver: redis
|
|
```
|
|
|
|
```yaml
|
|
# config/redis.yaml (or SUMMER_REDIS__URL, which keeps a password out of YAML)
|
|
url: redis://10.0.0.7:6379/0
|
|
```
|
|
|
|
A missing or malformed `redis.url`, an unknown driver name or a `cache.memory.max_entries` below 1 fails the start-up with an error that names the key; an error never shows the Redis URL. Redis being down at start-up is a warning, not a failed start.
|
|
|
|
## Keys and the prefix
|
|
|
|
Every key is stored as `<prefix>cache:<key>`. The prefix is `cache.prefix` when set; by default it is `app.name` lower-cased, with every run of characters other than letters and digits replaced by `_`, followed by `:`, so `app.name: Acme Blog` gives `acme_blog:` and the key `blog:popular` is stored as `acme_blog:cache:blog:popular`. With `app.name` empty the prefix is `summer:`. Rate-limit counters live under `<prefix>rl:`, so several applications, and the cache and the limiter of one application, can share a Redis without touching each other's keys.
|
|
|
|
## TTLs
|
|
|
|
A TTL of 0 or less keeps a value until it is forgotten, flushed or, in the memory driver, evicted. A positive TTL expires the value after that long; Redis stores it with millisecond precision. `cooler.Cache.Increment` starts a missing key at 0 without a TTL and keeps the expiry of an existing key, as Redis `INCRBY` does.
|
|
|
|
## Remember degrades instead of failing
|
|
|
|
`cooler.Cache.Remember` reads the key and, on a miss, calls the callback, stores its value for the TTL and returns it. When the cache itself fails, for example while Redis is down, it does not fail the page: a failed read still calls the callback and returns its value, a failed write is skipped, and both are logged as a warning at most once per minute. Only an error from the callback is returned. The other methods return cache errors to the caller.
|
|
|
|
## Jobs and console commands
|
|
|
|
The `serve` command installs cooler at start-up (`cooler.Install`) and publishes the cache on the application, so plugins and the in-process job worker find it with `app.Lookup[cooler.Cache]()`. Other commands, such as a dedicated `queue:work` process or a console command, do not run `serve`; they call `cooler.FromApp`, which returns the published cache or builds the configured one and publishes it. It does not connect at construction: the Redis driver dials on the first call, so a command that never touches the cache never needs Redis.
|
|
|
|
## Flush
|
|
|
|
`cooler.Cache.Flush` removes this application's cache keys and nothing else. The Redis driver walks `SCAN MATCH <prefix>cache:*` in batches of 500 and removes each batch with `UNLINK`; it never empties the whole Redis database, which would also drop rate-limit counters and other applications' keys. The memory driver empties the process cache.
|