cluster

package
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package cluster turns N cadish nodes in a region into a sharded / cooperative cache. It implements two interlocking features driven by one `cluster { … }` Cadishfile membership block:

  • PEER READ-THROUGH (#7): on a LOCAL cache miss, consult peer cadish nodes (same region) before going to origin; if a peer has the object, stream it locally (same tee contract as origin). Modeled as an origin.Origin (internal/origin/peerorigin) composed BEFORE the real origin in a chain.

  • OWNERSHIP ROUTING (#8): one node OWNS each cache key via a consistent-hash ring over the cluster peers (the SAME lb ring used for upstream sharding). A request landing on a non-owner is reverse-proxied to the owner so the object is cached once per region, not N times.

Both share cluster membership + peer health (reused from internal/lb) and a hop guard (the X-Cadish-Peer header) that prevents forward loops/storms: a request already forwarded to a peer is never re-forwarded.

ZERO COST WHEN ABSENT. Everything here is gated by the presence of a `cluster` membership block; a non-clustered cadish constructs no Membership and behaves exactly as before.

Index

Constants

View Source
const HopHeader = "X-Cadish-Peer"

HopHeader marks a request as already forwarded to a peer cadish node. A node that sees it MUST NOT forward again (read-through or owner-route), which is what makes the cluster loop-safe and storm-safe. Its value is the originating region, used to ignore cross-region hops.

Variables

This section is empty.

Functions

func IsMembershipBlock

func IsMembershipBlock(d *cadishfile.Directive) bool

IsMembershipBlock reports whether a `cluster` directive is the membership form (this package) rather than the pre-existing `cluster NAME { to … }` LB-pool form (internal/lb). The membership block has NO name argument and carries a `peers` directive; the LB-pool form has a name and `to` backends. Distinguishing by shape lets both coexist without a keyword clash.

Types

type Config

type Config struct {
	// Self is this node's own peer URL (must appear in Peers). It identifies which
	// ring node is "us" for ownership decisions and is excluded from peer fetches.
	Self string
	// Peers are the peer cadish node targets (static URLs and/or dns://, k8s://
	// discovery), parsed with the same lb.Target syntax as upstream `to`. At least
	// one required; Self must be among them.
	Peers []lb.Target
	// Region scopes the cluster; it is the value of the X-Cadish-Peer hop header so
	// a node ignores hops stamped by a different region.
	Region string
	// Mode selects #7/#8 coexistence (default read_through).
	Mode Mode
	// Fallback selects owner-mode behavior on owner-down (default degraded).
	Fallback Fallback
	// Health is the active peer-health probe spec (reused verbatim from lb). Nil
	// disables active probing (peers are then always eligible).
	Health *lb.HealthSpec
	// Replicas is the consistent-hash virtual-node count per peer (0 ⇒ lb default).
	Replicas int
	// Pos is the directive's source position.
	Pos cadishfile.Pos
}

Config is a parsed `cluster { … }` membership block.

func Parse

func Parse(d *cadishfile.Directive) (Config, error)

Parse builds a Config from a `cluster { … }` membership directive. The block has NO name argument (that form is the lb pool) and accepts:

self    URL                                              (this node; ∈ peers)
peers   URL...                                           (repeatable; ≥1)
region  NAME
mode    read_through | owner                             (default read_through)
fallback strict | degraded                               (default degraded)
health  METHOD PATH expect CODE interval D window N threshold T
replicas N                                               (ring vnodes; tests)

Errors are positioned *cadishfile.ParseError (file:line:col: message).

type Fallback

type Fallback int

Fallback selects owner-mode behavior when the computed owner is unavailable.

const (
	// FallbackDegraded (the default) serves the request from the next eligible
	// ring node, then peer read-through, then local origin — availability over
	// strict single-ownership. A flapping owner degrades to "cached on a neighbor"
	// rather than failing.
	FallbackDegraded Fallback = iota
	// FallbackStrict serves the request locally (this node's cache→origin) when
	// the owner is down, accepting a transient duplicate cache entry rather than
	// chaining proxies. No second hop.
	FallbackStrict
)

func (Fallback) String

func (f Fallback) String() string

String renders the fallback keyword.

type Membership

type Membership struct {
	// contains filtered or unexported fields
}

Membership is the live cluster state for one node: the peer pool (an lb.Upstream that consistent-hash-shards a cache key to its owning peer, with reused health / failover / discovery), this node's identity, and the #7/#8 policy. It is built once from a Config and is safe for concurrent use. A non-clustered cadish never constructs one (zero cost when absent).

func New

func New(cfg Config) (*Membership, error)

New builds a Membership from cfg. The peer pool is a shard-by-key lb.Upstream so a cache key routes to its owning peer; the same pool answers Owner() for #8 and backs the read-through PeerOrigin for #7. Background health probing / dns re-resolution start on Start(ctx). New itself does no network I/O beyond lb's initial resolution.

func (*Membership) Close

func (m *Membership) Close()

Close is a no-op placeholder for symmetry (the peer pool stops with its Start context). Present so callers can `defer m.Close()` uniformly.

func (*Membership) Fallback

func (m *Membership) Fallback() Fallback

Fallback reports the owner-mode fallback policy.

func (*Membership) IntendedOwner

func (m *Membership) IntendedOwner(key string) (string, bool)

IntendedOwner returns the ring owner IGNORING health — the topological owner even when it is down. Used to detect "the owner is unavailable" so the caller can apply the strict-vs-degraded fallback.

func (*Membership) IsForwardedHop

func (m *Membership) IsForwardedHop(h http.Header) bool

IsForwardedHop reports whether an inbound request was already forwarded to us by a peer in OUR region (the X-Cadish-Peer loop guard). Such a request must NOT be re-forwarded (read-through or owner-route) — it is served locally. A hop stamped by a DIFFERENT region is foreign (a separate cluster) and is treated as a fresh client request.

func (*Membership) IsSelf

func (m *Membership) IsSelf(peerURL string) bool

IsSelf reports whether a peer base URL is this node.

func (*Membership) Mode

func (m *Membership) Mode() Mode

Mode reports the configured coexistence mode.

func (*Membership) Owner

func (m *Membership) Owner(key string) (string, bool)

Owner returns the base URL of the HEALTHY peer that owns key on the ring, and whether one exists. Walks past unhealthy/ejected peers (lb's health-aware ring walk), so the result is the node a sharded key currently lives on. Used by #8 to decide owner-vs-self.

func (*Membership) OwnsKey

func (m *Membership) OwnsKey(key string) bool

OwnsKey reports whether THIS node is the (healthy) owner of key. When no healthy owner exists it returns false (the caller then applies fallback).

func (*Membership) PeerCount

func (m *Membership) PeerCount() int

PeerCount reports the number of currently-known peer endpoints (post-resolution).

func (*Membership) PeerOrigin

func (m *Membership) PeerOrigin() origin.Origin

PeerOrigin returns the read-through origin (#7) to compose BEFORE the real origin in a chain. Never nil.

func (*Membership) Region

func (m *Membership) Region() string

Region reports the cluster region (the hop-header value).

func (*Membership) Self

func (m *Membership) Self() string

Self reports this node's normalized peer URL.

func (*Membership) Start

func (m *Membership) Start(ctx context.Context)

Start launches the peer pool's background workers (active health probing + dynamic re-resolution), bound to ctx. Idempotent.

type Mode

type Mode int

Mode selects how #7 (read-through) and #8 (ownership routing) coexist.

const (
	// ModeReadThrough (the default) is #7 ONLY: opportunistic peer read-through.
	// On a local miss, the owning peer is asked for the key (hop-guarded); a peer
	// hit is streamed-and-stored locally, a peer miss falls through to origin.
	// Requests are never re-routed — every node may serve any key.
	ModeReadThrough Mode = iota
	// ModeOwner is #8 PRIMARY with #7 as the fallback: each key has one owner on
	// the ring. A request landing on a non-owner is reverse-proxied to the owner
	// so the object is cached once per region. If the owner is down, Fallback
	// decides between serving locally (strict) or the next ring node (degraded);
	// the degraded path also tries peer read-through before origin.
	ModeOwner
)

func (Mode) String

func (m Mode) String() string

String renders the mode keyword.

Jump to

Keyboard shortcuts

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