Documentation
¶
Overview ¶
Package catalog is the control plane for database-per-tenant fabriq (catalog mode; spec 2026-07-03, D1/D2): a small, strongly-consistent registry mapping each tenant to the cluster and database that own its source of truth.
The catalog exists ONLY in catalog mode. Hash-sharded and single-shard deployments keep fabriq's zero-catalog property: minting a tenant is its first write. In catalog mode that trade is reversed on purpose — dedicated databases require provisioning, and provisioning requires a place to record it.
The catalog is deliberately tiny: Get on the request path (cached by the shard directory), Put with optimistic concurrency for the provisioning state machine, List for the sweeper and the fleet migration roller.
Index ¶
- Constants
- func IsDegraded(err error) bool
- func MarkDegradedDetail(detail map[string]string) map[string]string
- func ValidateEntry(e Entry) error
- type Catalog
- type Cursor
- type Entry
- type Failover
- func (f *Failover) Get(ctx context.Context, tenantID string) (Entry, error)
- func (f *Failover) List(ctx context.Context, cursor Cursor, limit int) ([]Entry, Cursor, error)
- func (f *Failover) Put(ctx context.Context, e Entry) (Entry, error)
- func (f *Failover) ReadStats() (primary, replica, failover int64)
- type State
Constants ¶
const ( DegradedMetaKey = "catalog" DegradedMetaValue = "degraded" )
DegradedMetaKey / DegradedMetaValue are the fabriqerr Meta.Detail entry that flags a routing answer as served from a replica while the primary was unreachable. Failover stamps it on a replica NotFound, and the directory stamps it on any error it derives from a replica-sourced entry (MarkDegradedDetail); IsDegraded recognises it. The directory's cache predicate excludes degraded answers so a lagged replica can never pin a tenant for a TTL.
Variables ¶
This section is empty.
Functions ¶
func IsDegraded ¶
IsDegraded reports whether err is a replica-sourced answer served while the primary was unreachable — the directory must not negative-cache it.
func MarkDegradedDetail ¶
MarkDegradedDetail stamps the degraded marker onto a Meta.Detail map, allocating one if nil, and returns it. Callers building a routing error from a replica-sourced entry use it so the directory will not cache the answer.
func ValidateEntry ¶
ValidateEntry checks the invariants every store enforces before writing.
Types ¶
type Catalog ¶
type Catalog interface {
// Get returns a tenant's entry, or CodeNotFound.
Get(ctx context.Context, tenantID string) (Entry, error)
// Put writes an entry with optimistic concurrency on UpdatedAt:
// a zero UpdatedAt creates (CodeAlreadyExists if present); a non-zero
// UpdatedAt updates iff it matches the stored token
// (CodeVersionConflict otherwise). Invalid entries are CodeInvalidInput.
Put(ctx context.Context, e Entry) (Entry, error)
// List pages through every entry in stable (tenant id) order.
List(ctx context.Context, cursor Cursor, limit int) ([]Entry, Cursor, error)
}
Catalog is the control-plane port. Implementations: the Postgres control database (adapters/postgres) and fabriqtest.FakeCatalog. Both are gated by the contract suite in catalogtest.
type Entry ¶
type Entry struct {
TenantID string `json:"tenantId"`
ClusterID string `json:"clusterId"`
Database string `json:"database"`
State State `json:"state"`
// Version is the fabriq migration version the tenant database was last
// observed at (grove migration versions, e.g. "202607030030"). The
// router fails closed when it is below the binary's floor.
Version string `json:"version"`
// Schema is the tenant's Postgres schema in schema-per-tenant
// consolidation mode (e.g. "tenant_acme"); empty in database mode. When
// set it selects the tenant's namespace via search_path within the
// shared consolidation database named by Database.
Schema string `json:"schema"`
// UpdatedAt is the optimistic-concurrency token: Put succeeds only when
// it matches the stored row (zero UpdatedAt = create, must not exist).
// The store stamps a fresh UpdatedAt on every successful write.
UpdatedAt time.Time `json:"updatedAt"`
// FromReplica is a transient routing-provenance flag: true when this entry
// was served by a read replica during a primary outage (set only by
// Failover, never persisted or serialized). The directory treats any answer
// it derives from a replica-sourced entry — a version-gate or not-active
// CodeUnavailable — as degraded (non-cacheable), because a lagged replica
// could report a stale-old version or a superseded state.
FromReplica bool `json:"-"`
}
Entry is one tenant's catalog row.
type Failover ¶
type Failover struct {
// contains filtered or unexported fields
}
Failover is a high-availability catalog.Catalog for catalog mode's routing read path (spec 2026-07-03 HA, H1-H3): it reads the PRIMARY first and falls through to read-only REPLICAS only when the primary is unreachable. Writes and any read that the primary answers definitively never touch a replica, so steady-state staleness is zero.
It is injected into shard.CatalogDirectory in place of the bare primary. Every other catalog consumer (provisioning, adminapi, the sweeper scan, the reconciler elector) keeps the concrete primary store — those want the authoritative write node, not a possibly-lagged replica.
func NewFailover ¶
NewFailover wraps a primary with zero or more read replicas. With no replicas it is a transparent pass-through to the primary.
func (*Failover) List ¶
List falls through to replicas on any primary error. Unlike Get, List has no definitive business answer to distinguish (no NotFound), so every primary error is a transport failure — replica-fallback needs no primaryDefinitive gate here. Only Get is wired to the routing directory in v1; List fallback is kept for interface coherence.
type State ¶
type State string
State is a tenant's lifecycle state in the catalog.
const ( // StatePending — row exists, nothing provisioned yet. StatePending State = "pending" // StateCreating — CREATE DATABASE issued. StateCreating State = "creating" // StateMigrating — database exists, migration chain running. StateMigrating State = "migrating" // StateActive — routable; the only state the directory serves. StateActive State = "active" // StateSuspended — deliberately routed off (offboarding, incident). StateSuspended State = "suspended" // StateFailed — provisioning failed; listable for operators, never // routable. The provisioner may resume it. StateFailed State = "failed" )
Directories
¶
| Path | Synopsis |
|---|---|
|
Package catalogtest is the catalog contract suite: the behaviors every catalog.Catalog implementation must exhibit, run against fabriqtest.FakeCatalog (unit) and the Postgres control store (integration) — the conformance-kit discipline: drift between fake and adapter is a failing test.
|
Package catalogtest is the catalog contract suite: the behaviors every catalog.Catalog implementation must exhibit, run against fabriqtest.FakeCatalog (unit) and the Postgres control store (integration) — the conformance-kit discipline: drift between fake and adapter is a failing test. |