Shared HTTP caching in front of an origin that has to stay up.
Weir forwards the request it keyed, so a header the cache ignores cannot poison an entry. And a stampede cannot reach the origin faster than a budget you set.
go get github.com/AshwinSathian/weir@v1.0.0$ weirproxy -listen :8080 -origin http://localhost:9000 $ curl -si localhost:8080/ HTTP/1.1 200 OK Cache-Control: max-age=60 Cache-Status: Weir; fwd=uri-miss; fwd-status=200; stored hello from the origin # 13 seconds later $ curl -si localhost:8080/ HTTP/1.1 200 OK Age: 13 Cache-Control: max-age=60 Cache-Status: Weir; hit; ttl=43 hello from the origin
Real output. The second request never left the process. Its ttl is 43 and not 47 because jitter took four seconds off this entry, so entries stored together do not expire together.
Ten thousand entries expire within seconds of each other. The origin sees 32 requests at a time.
A weir is a low dam that regulates flow without stopping it. Every origin fetch holds a slot from a limiter, whatever caused it: a miss, a refresh, a purge, a cold start.
make load on an Apple M4 Pro with Go 1.27.1, where the busiest second saw 1,485 origin calls. Method and limitsFive ways a cache takes its origin down
Each technique exists elsewhere. Weir puts them in one Go library with a written threat model and a test for each.
- 01
Synchronized expiry
Entries stored together expire together, and the origin gets the whole batch back at once.
What Weir does. Jitter shortens each lifetime by up to 10%, and a hot entry is refreshed in the background shortly before it expires. Freshness
- 02
Hot-key stampede
One popular entry expires and every request waiting on it asks the origin.
What Weir does. Requests for one key share one fetch. Followers wait at most 10 s, and a leader slower than 10 s stops collecting new followers. Coalescing
- 03
Cross-key stampede
A purge, a restart or a cold start makes thousands of different keys miss together. Coalescing cannot help, because no two requests share a key.
What Weir does. Every fetch holds one of 64 slots, at most 16 per path. 1,024 more wait up to 2 s; the rest get stale content or a 503 with
Retry-After. Origin protection - 04
Origin or storage outage
The origin slows down, and every request pays the full timeout before failing.
What Weir does. Stale-while-revalidate and stale-if-error serve what is stored, a circuit breaker stops calling a failing origin, and a 2 s negative entry answers repeats of a failure. Origin protection
- 05
Poisoning and cache busting
A header the key ignores changes the stored response for everyone, or random query strings turn every request into a miss.
What Weir does. The origin receives only keyed, allowed and protocol headers, so there is no unkeyed input to poison with. A miss-rate detector flags a path whose requests have stopped hitting. The cache key
A main function, or a Caddyfile block
The zero weir.Config is valid, and name is the only setting the Caddy directive requires.
Go, with net/http
target, _ := url.Parse("http://localhost:9000") // scheme and host; a path is ignored
e, err := weir.New(weir.Config{}) // the zero Config is valid
if err != nil {
log.Fatal(err)
}
srv := &http.Server{
Addr: ":8080",
Handler: weirhttp.Handler(e, &weirhttp.TransportOrigin{Target: target}),
ReadHeaderTimeout: 10 * time.Second,
}
log.Fatal(srv.ListenAndServe()) // on shutdown: srv.Shutdown(ctx), then e.Close(ctx)Caddy
example.com {
weir {
name site-a
}
reverse_proxy app:8080
}Build Caddy with the module first. The quickstart has the xcaddy line and the output to expect at each step.
What Weir is not
- A CDN. One process, one logical origin per engine.
- A WAF or rate limiter. It bounds origin load. It does not decide which clients deserve service, so put a rate limiter in front for floods of distinct paths.
- A private cache. It is always a shared cache in the RFC 9111 sense.
- A cache for application data. It caches HTTP responses. For function results use singleflight or sturdyc.
- A Varnish replacement with VCL. Configuration is a Go struct or a Caddyfile block. There is no scripting language.
- A store for 206 responses. It answers single byte ranges from complete stored objects.
- A prefix purge. Purge by URL, by RFC 9875 group, or everything.
- A multi-node memory cache. Each node has its own memory store. Several nodes share a cache through the Valkey store.
If one of these is what you need, the comparison page says which project to use instead.