purge

package
v0.18.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 12, 2026 License: MIT Imports: 9 Imported by: 0

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

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 WithClock

func WithClock(now func() time.Time) Option

WithClock overrides the clock used to stamp epochs (mainly for tests).

func WithMaxRecords

func WithMaxRecords(n int) Option

WithMaxRecords caps each scope map before it folds into the global epoch. Values <= 0 keep the default (65536).

type PrefixRec

type PrefixRec struct {
	Prefix string `json:"prefix"`
	Epoch  int64  `json:"epoch"`
}

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 Stats

type Stats struct {
	Global     int64
	HostRecs   int
	URLRecs    int
	PrefixRecs int
	TagRecs    int
	Folds      uint64
}

Stats is a concurrent-safe snapshot of the table size, for metrics/diagnostics.

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)
		}
	}()
}

func New

func New(opts ...Option) *Table

New creates an empty Table.

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

func (t *Table) InvalidatedAfter(r *http.Request, m cache.Meta) int64

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

func (t *Table) InvalidatedAfterMeta(m cache.Meta) int64

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

func (t *Table) PurgeHost(host string)

PurgeHost invalidates every URL cached under host. The host is normalized (lowercased, port-stripped) to match the cache key.

func (*Table) PurgePrefix

func (t *Table) PurgePrefix(host, prefix string)

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

func (t *Table) PurgeTag(tag string)

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

func (t *Table) PurgeURL(host, uri string)

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

func (t *Table) Reap(storage cache.Storage) int

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

func (t *Table) Restore(s Snapshot)

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.

func (*Table) Snapshot

func (t *Table) Snapshot() Snapshot

Snapshot returns an independent, marshalable copy of the table's state, so the caller can persist it (e.g. fsync to disk) off the serving path.

func (*Table) Stats

func (t *Table) Stats() Stats

Stats returns a snapshot of the table's record counts.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL