Documentation
¶
Overview ¶
Package purge invalidates entries in a pkg/cache response cache.
A Table is a small in-memory record of "everything cached at or before epoch T is invalid", consulted at cache-lookup time through the cache.Options.InvalidatedAfter hook. Invalidation is LAZY: issuing a purge is O(1) (one map write stamping the wall clock) and a purged entry is physically reclaimed only on its next lookup — exactly when the hook reports it stale, like a passed FreshUntil. Correctness is immediate (a purged entry can never be served); the storage backend's LRU byte cap reclaims space regardless, and the optional Reap sweep reclaims proactively.
pt := purge.New()
c := cache.New(store, cache.Options{InvalidatedAfter: pt.InvalidatedAfter})
...
pt.PurgeURL("example.com", "/a") // invalidate one URL (all methods/schemes/variants)
pt.PurgeTag("product-42") // invalidate by surrogate key (Cache-Tag), any host
go func() { for range time.Tick(5 * time.Minute) { pt.Reap(store) } }()
A Table persists nothing on its own; Snapshot/Restore serialize its state so a caller can keep purges across restarts however it likes.
Index ¶
- type Option
- type PrefixRec
- type Snapshot
- type Stats
- type Table
- func (t *Table) FlushAll()
- func (t *Table) InvalidatedAfter(r *http.Request, m cache.Meta) int64
- func (t *Table) InvalidatedAfterMeta(m cache.Meta) int64
- func (t *Table) PurgeHost(host string)
- func (t *Table) PurgePrefix(host, prefix string)
- func (t *Table) PurgeTag(tag string)
- func (t *Table) PurgeURL(host, uri string)
- func (t *Table) Reap(storage cache.Storage) int
- func (t *Table) Restore(s Snapshot)
- func (t *Table) Snapshot() Snapshot
- func (t *Table) Stats() Stats
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Option ¶
type Option func(*Table)
Option configures a Table.
func WithMaxRecords ¶
WithMaxRecords caps each scope map before it folds into the global epoch. Values <= 0 keep the default (65536).
type PrefixRec ¶
PrefixRec is one path-prefix purge for a host: the normalized prefix (trailing slash trimmed; "" means the whole host) and its epoch. Exported so it round-trips through Snapshot.
type Snapshot ¶
type Snapshot struct {
Host map[string]int64 `json:"host,omitempty"`
URL map[string]int64 `json:"url,omitempty"`
Prefix map[string][]PrefixRec `json:"prefix,omitempty"`
Tag map[string]int64 `json:"tag,omitempty"`
Global int64 `json:"global,omitempty"`
}
Snapshot is a serializable copy of a Table's invalidation state, for persistence. It is JSON-marshalable; restore it with Table.Restore.
type Table ¶
type Table struct {
// contains filtered or unexported fields
}
Table is a cache-invalidation record set, consulted via InvalidatedAfter.
Scopes, checked as a max at lookup so a URL is also covered by its host's purge, a matching path prefix, and a global flush:
- global — FlushAll: everything (one epoch).
- host — PurgeHost: every URL under a host (keyed by normalized host).
- url — PurgeURL: one URL across all methods, schemes, and Vary variants (keyed by hash(host ⊕ uri), so a single purge of /a covers GET+HEAD, http+https, and every cached variant).
- prefix — PurgePrefix: every URL under a path prefix on a host (path-only, boundary-aware: /blog matches /blog and /blog/x but not /blogger; query strings ignored). A linear scan of the host's prefix records.
- tag — PurgeTag: every cached response carrying a surrogate key (the origin's Cache-Tag header, stored in cache.Meta.Tags); host-independent.
Epochs are monotonic non-decreasing: every stamp is clamped to be >= the largest epoch ever issued, so a wall-clock step back (NTP correction) can never lower an epoch and "un-purge" entries. Safe for concurrent use.
Example ¶
Wire a purge table into a cache, issue purges, and reclaim proactively.
package main
import (
"time"
"github.com/moonrhythm/parapet/pkg/cache"
"github.com/moonrhythm/parapet/pkg/cache/purge"
)
func main() {
pt := purge.New()
store := cache.NewMemory(256 << 20)
_ = cache.New(store, cache.Options{
InvalidatedAfter: pt.InvalidatedAfter, // the cache consults the table on every hit
})
// Issue purges when content changes — keyed on host, URL, path prefix, or
// surrogate key (Cache-Tag). They take effect immediately (lazily).
pt.PurgeURL("example.com", "/a") // one URL: all methods, schemes, Vary variants
pt.PurgePrefix("example.com", "/blog") // a whole section, boundary-aware
pt.PurgeTag("product-42") // every response carrying this surrogate key, any host
pt.FlushAll() // everything
// Optionally reclaim invalidated bytes proactively (correctness doesn't depend
// on it — the lookup gate already prevents serving a purged entry).
ticker := time.NewTicker(5 * time.Minute)
go func() {
defer ticker.Stop()
for range ticker.C {
pt.Reap(store)
}
}()
}
Output:
func (*Table) FlushAll ¶
func (t *Table) FlushAll()
FlushAll invalidates everything cached at the call instant. It also clears the per-scope maps (now redundant: the global epoch supersedes any record <= it), reclaiming their memory.
func (*Table) InvalidatedAfter ¶
InvalidatedAfter is the cache.Options.InvalidatedAfter hook: it returns the invalidation epoch (unix nanos) applying to r — the max of the global, per-host, per-url, per-prefix, and (using the stored entry's surrogate keys) per-tag epochs. The cache treats a hit whose Meta.Created is <= this value as stale.
func (*Table) InvalidatedAfterMeta ¶
InvalidatedAfterMeta is the off-request variant used by Reap: it reads the (already-normalized) host + uri from a stored entry's Meta instead of a live request. An entry with an empty Host matches only the global scope.
func (*Table) PurgeHost ¶
PurgeHost invalidates every URL cached under host. The host is normalized (lowercased, port-stripped) to match the cache key.
func (*Table) PurgePrefix ¶
PurgePrefix invalidates every URL under a path prefix on host, on a path boundary: "/blog" matches "/blog" and "/blog/x" but not "/blogger". A trailing slash is normalized away; "/" purges the whole host. Query strings are ignored.
func (*Table) PurgeTag ¶
PurgeTag invalidates every cached response carrying the surrogate key tag (from the origin's Cache-Tag header, stored in cache.Meta.Tags), across all hosts.
func (*Table) PurgeURL ¶
PurgeURL invalidates one URL on host across all methods, schemes, and Vary variants. uri is the request-uri (path+query); a different query is a different URL.
func (*Table) Reap ¶
Reap sweeps storage once, PHYSICALLY deleting every entry the table has invalidated (Meta.Created <= the entry's invalidation epoch), and returns the number deleted.
It complements the lazy lookup gate (InvalidatedAfter), which reaps an entry only when it is next looked up — so after a broad purge with little subsequent traffic the dead bytes would linger until LRU pressure evicts them. Reap reclaims them proactively. Correctness never depends on it (the gate already guarantees a purged entry is never served); it is pure reclamation, and over-deleting a still-valid entry only costs a re-fetch, never a stale serve.
Run it periodically off the serving path:
go func() { for range time.Tick(5 * time.Minute) { pt.Reap(store) } }()
Reap deliberately does NOT retire purge records — that is the one under-invalidating direction, unsafe against a backward wall-clock step between a purge and a later fill's commit. The table's memory is instead bounded by the monotonic, over-invalidating cap-fold (WithMaxRecords).
func (*Table) Restore ¶
Restore replaces the table's state with a snapshot's (deep-copied), recomputing the monotonic high-water mark and the active gate so a restored purge keeps gating after a restart. Call it before serving, not concurrently with purges.