bouine is structured in 8 layers, each testable in isolation.
One HTTP implementation only:
fasthttp — HTTP/1.1 only (data plane + admin)The admin API uses a manual method+path router on fasthttp.Server.
HTTP/2 is not currently available. bouine previously supported HTTP/2 (h2 over TLS, h2c over plaintext) via Go’s
net/http. The migration tofasthttpas the sole HTTP stack (ADR-0034) dropped HTTP/2 support to achieve a zero-allocation hit path. HTTP/2 reintroduction is in progress, planned as afasthttp-native implementation rather than re-adoptingnet/http.
The RFC 9111 state machine is deterministic: inputs are *http.Request, stored *Object, and now. Outputs are HIT, MISS, REVALIDATE, STALE_HIT, or BYPASS.
Primary key: 128-bit XXH128(scheme | host | path | sorted_query | method). The full 16-byte hash is used as a map key, providing 128-bit collision resistance without a separate lookup step. Zero allocations via one-shot Sum128.
Secondary key (Vary): derived from the request headers listed in the response’s Vary header, or from cache.key.include_headers.
storage.hot_mmap_slab: true)When the origin sends a CDN-Cache-Control header, it takes precedence over Cache-Control for all shared-cache decisions. This allows origins to set different TTLs for CDN caches vs browser caches:
Cache-Control: no-store # browsers don't cache
CDN-Cache-Control: max-age=3600 # bouine caches for 1hOrigins can tag responses with opaque surrogate keys for grouped invalidation:
Surrogate-Key: product-456 category-shoes
Cache-Tag: product-456, category-shoesbouine reads Surrogate-Key, Cache-Tag, and X-Cache-Tags at store time and makes them available for POST /v1/ban{surrogate_key:"..."} invalidation.
404, 405, 410, 501 responses can be cached for a configurable duration (negative_ttl).
Random ±N% applied to every TTL to prevent synchronized expiry stampedes across cached entries.
bouine supports two consistency modes (see Clustering):
Sharding: Consistent hash with 256 virtual nodes per real node. On a miss, the requesting node checks the owner node before going to origin.
Every node is independent — no sharding, no peer-fetch. Invalidations propagate via gossip only. Each node caches whatever it receives from origin.
hashicorp/memberlist for gossip. Nodes bootstrap via StatefulSet DNS.
Added latency for a peer hit: ~0.3ms (one in-cluster HTTP/1.1 hop).
When an object enters its stale-while-revalidate window, bouine:
bgRevalSem bounds concurrency to 256) that conditionally revalidates with the origin.HIT.This is what eliminates the 93% effective hit rate gap vs Varnish in mixed workloads — both caches serve stale immediately and refresh asynchronously.
| Operation | strong | eventual |
|---|---|---|
| Purge | HTTP fan-out to all peers + gossip | Gossip only (1–5 s convergence) |
| Ban | HTTP fan-out to all peers + gossip | Gossip only |
| Refresh | Forwarded to key’s owner node | Gossip only |
In strong mode, the HTTP fan-out ensures sub-second invalidation propagation. The gossip broadcast queue provides a redundant delivery path.
Pods retry joining every 2 seconds for up to 60 seconds. Success requires Members() > 1 (at least one real peer, not self-join). The headless Service must have publishNotReadyAddresses: true.
| Benchmark | Result |
|---|---|
Evaluate_Hit | 40 ns/op, 0 allocs |
HotStore_Get_Hit | 5.4 ns/op, 0 allocs |
Handler_CacheHit | 537 ns/op, 8 allocs |
BuildKey (query params) | 46 ns/op, 0 allocs |
SIEVE_Access | 5.4 ns/op, 0 allocs |
Load-test results (Docker, 3k RPS, single node vs Varnish + nginx):
| Scenario | bouine | nginx | varnish |
|---|---|---|---|
| Hit-only (warm cache) | 166 µs avg | 166 µs avg | 177 µs avg |
| Miss storm (no-store) | 157 µs avg | degraded | 166 µs avg |
| Mixed 60/15/10/5/5 | 230 µs avg | 22 ms avg† | 199 µs avg |
†nginx’s high mixed average is due to blocking revalidation; bouine and Varnish both use background SWR refresh.
All gates enforced in CI — regressions block merge.