Documentation
¶
Overview ¶
Package storage is the persistence seam under halo-server's stores: an append-only record log each store replays into memory, and a small expiring key-value space for short-lived single-use state. The file backend (Dir, MemKV) is the single-replica default; internal/pgstore implements the same interfaces on Postgres so several replicas share state.
Index ¶
- Constants
- Variables
- func CloseLog(mu sync.Locker, l Log) error
- func Compact(ctx context.Context, l Log, drop func(rec []byte) bool) (int, error)
- func Ctx() (context.Context, context.CancelFunc)
- func SingleWriter(l Log) bool
- func ValidName(name string) bool
- type Coalescer
- type Dir
- type KV
- type Log
- type MemKV
- func (m *MemKV) Add(_ context.Context, ns, key string, val []byte, exp time.Time) (bool, error)
- func (m *MemKV) Delete(_ context.Context, ns, key string) error
- func (m *MemKV) Get(_ context.Context, ns, key string) ([]byte, bool, error)
- func (m *MemKV) Put(_ context.Context, ns, key string, val []byte, exp time.Time) error
- func (m *MemKV) Raise(_ context.Context, ns, key string, at int64, exp time.Time) error
- func (m *MemKV) Take(_ context.Context, ns, key string) ([]byte, bool, error)
- func (m *MemKV) TryLock(_ context.Context, ns, key, owner string, ttl time.Duration) (bool, error)
- func (m *MemKV) Unlock(_ context.Context, ns, key, owner string) error
- type Opener
Constants ¶
const OpTimeout = 5 * time.Second
OpTimeout bounds one store operation against a shared backend when the caller has no request context to pass down.
Variables ¶
var ErrFenced = errors.New("lease lost: write fenced off")
ErrFenced means a write was refused because the writer no longer holds the lease it was fenced by (another replica leads now). Shared backends wrap it; callers skip the side effect the write was guarding.
var ErrNoCompact = errors.New("storage: this log cannot drop records")
ErrNoCompact is Compact's answer for a log that cannot drop records.
ErrUnavailable wraps shared-backend failures that a handler reports as 503 rather than as a client error.
Functions ¶
func Compact ¶ added in v0.6.0
Compact removes every record of l that drop selects and returns how many it removed (ADR-0040: retention and erasure). A nil Log (memory only) has nothing to remove. Callers serialize it with Append and Catchup.
func Ctx ¶
func Ctx() (context.Context, context.CancelFunc)
Ctx returns a context bounded by OpTimeout.
func SingleWriter ¶ added in v0.5.0
SingleWriter reports whether l is a log no other process appends to, so Catchup is a no-op and a caller can skip it (and the lock it would take).
Types ¶
type Coalescer ¶
type Coalescer struct {
// contains filtered or unexported fields
}
Coalescer merges concurrent catch-ups of one shared log: a caller is satisfied by a catch-up that started after it arrived (so it sees every write committed before it asked), and every caller waiting meanwhile shares the same one. N concurrent requests cost about two database round trips, not N, and no store lock is held while they wait. Zero value ready.
type Dir ¶
type Dir string
Dir opens <dir>/<name>.jsonl. The empty Dir opens nothing: OpenLog returns a nil Log and the store stays in memory only.
type KV ¶
type KV interface {
// Put sets ns/key, replacing any existing entry.
Put(ctx context.Context, ns, key string, val []byte, exp time.Time) error
// Add sets ns/key only if it is absent or expired; added reports whether it did.
Add(ctx context.Context, ns, key string, val []byte, exp time.Time) (added bool, err error)
// Get returns an unexpired entry.
Get(ctx context.Context, ns, key string) ([]byte, bool, error)
// Take deletes ns/key and returns its value if it had not expired. Of
// concurrent Takes of one key, at most one gets ok.
Take(ctx context.Context, ns, key string) ([]byte, bool, error)
// Delete removes ns/key (no error if absent).
Delete(ctx context.Context, ns, key string) error
// TryLock takes ns/key for owner until ttl from now on the store's own
// clock (for Postgres the database's, never a replica's). It succeeds when
// the key is absent, expired, or already owner's (which extends it).
TryLock(ctx context.Context, ns, key, owner string, ttl time.Duration) (bool, error)
// Unlock releases ns/key only if owner still holds it: a holder whose
// lock expired and was taken over cannot release the new holder's.
Unlock(ctx context.Context, ns, key, owner string) error
// Raise sets ns/key to the decimal at unless a live entry already holds a
// larger value (an epoch that never moves backwards); the entry lives
// until exp, or longer if it already did.
Raise(ctx context.Context, ns, key string, at int64, exp time.Time) error
}
KV is a namespaced key-value space whose entries expire. Expired entries are invisible to Get, Add and Take. It holds short-lived, single-use state (enrollment tokens, consumed login states, approval claims) that every replica must see.
type Log ¶
type Log interface {
// Append durably adds rec (one JSON value, no trailing newline). A
// non-empty key lets a shared backend drop older records with the same
// key: use it only where the last record per key is all replay needs.
Append(ctx context.Context, key string, rec []byte) error
// Catchup replays records other writers appended since the last call.
Catchup(ctx context.Context) error
Close() error
}
Log is an append-only record log. A store replays it into memory when it opens (the replay func passed to Opener.OpenLog) and appends one record per change. On a shared backend other replicas append too: Catchup applies their records, in log order, through the same replay func, and Append applies every foreign record that precedes the new one before it returns, so the caller applying its own record afterwards keeps the log order. Callers serialize Append and Catchup (they hold the store's mutex), which is also what makes calling replay from inside them safe.
type MemKV ¶
MemKV is the in-process KV (single replica). Now defaults to time.Now.