s3-orchestrator

readpath

import "github.com/afreidah/s3-orchestrator/internal/proxy/readpath"

Index

Variables

ErrUsageLimitSkip is the sentinel a Probe returns when it declined to attempt a backend purely because its usage limit would be exceeded. Failover counts these separately from genuine failures so the final “all backends declined for usage limits” outcome can be reported as core.ErrUsageLimitExceeded rather than as the last underlying error.

var ErrUsageLimitSkip = errors.New("backend skipped: usage limits exceeded")

func NoopCleanup

func NoopCleanup()

NoopCleanup is the ProbeResult.Cleanup a Probe returns when a losing result holds nothing to release - e.g. a HEAD, which carries no body and has already cancelled its own timeout before returning.

func Read

func Read[T any](ctx context.Context, f *Failover, operation, key string, probe Probe[T]) (T, string, error)

type Broadcaster

Broadcaster fans a degraded-mode read out across every configured backend and returns the first success. It holds no metadata-store dependency - degraded mode exists precisely because the store is unreachable - and caches the winning backend for subsequent degraded reads.

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

type Failover

Failover orchestrates per-key read failover across backends. One instance per object.Manager; safe for concurrent reads.

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

func New

func New(deps *FailoverDeps) *Failover

New constructs a Failover. When DegradedReadsEnabled is false, a DB outage surfaces as ErrServiceUnavailable instead of broadcasting.

type FailoverDeps

FailoverDeps groups the read-failover constructor parameters. The three degraded-mode flags are named fields so call sites can’t transpose them.

type FailoverDeps struct {
    Core                         ReadRuntime
    Stores                       FailoverStores
    Cache                        LocationCache
    ParallelBroadcast            bool
    DegradedBroadcastParallelism int
    DegradedReadsEnabled         bool
    // BackendTimeout bounds the loser-drain goroutine after a winner is
    // declared so a hung backend can't strand it. Zero falls back to a
    // safe default.
    BackendTimeout time.Duration
}

type FailoverStores

FailoverStores is the narrow persistence surface read failover needs: object location lookups for the normal DB-backed path. Declared locally so readpath does not pull in the full MetadataStore.

type FailoverStores interface {
    core.ObjectStore
}

type LocationCache

LocationCache is the subset of the object-package location cache the orchestrator needs to remember and reuse degraded-mode winners. Declared as an interface here (not *object.LocationCache) to avoid the import cycle that would otherwise form (object imports readpath for Failover).

type LocationCache interface {
    Get(key string) (backendName string, ok bool)
    Set(key, backendName string)
}

type Probe

Probe is the per-backend callback the caller provides. loc carries the matching ObjectLocation row so callbacks that need encryption metadata can read it directly without a side-channel lookup; loc is nil in degraded-mode broadcasts where the DB is unreachable. beName is always populated (loc.BackendName during failover, or the degraded-mode caller’s chosen name) so callbacks have a single source for span / usage attribution.

The callback owns its own timeout context: it must release it on the error path, and for a winning result it transfers ownership to Value (a GET attaches the cancel to the body’s Close; a HEAD has no body and releases the timeout before returning). A losing result is released via ProbeResult.Cleanup.

type Probe[T any] func(ctx context.Context, beName string, loc *core.ObjectLocation, b backend.ObjectBackend) (ProbeResult[T], error)

type ProbeResult

ProbeResult is the outcome of one successful backend probe. Value is the operation’s result (e.g. *backend.GetObjectResult) and flows back to the caller when this probe wins. Cleanup releases the result’s transient resources when the probe loses the race (e.g. closing a discarded GET body); it is never invoked on the winner, whose Value owns its own lifecycle. Cleanup may be NoopCleanup when there is nothing to release.

type ProbeResult[T any] struct {
    Value   T
    Size    int64
    Cleanup func()
}

type ProbeScheduler

ProbeScheduler runs a bounded rolling window of degraded-mode probes and returns the first success. It is built per broadcast (cheap, single-use) and holds only the mechanics inputs; policy, telemetry, caching, and accounting stay with the Broadcaster.

type ProbeScheduler[T any] struct {
    // contains filtered or unexported fields
}

func (*ProbeScheduler[T]) FirstSuccess

func (s *ProbeScheduler[T]) FirstSuccess(ctx context.Context, probe Probe[T]) (res broadcastResult[T], errs []error, found bool)

FirstSuccess launches the rolling window and returns the first successful probe. found is false when every probe failed; errs holds each failure in arrival order so the caller can classify the outcome. With no cap the first call launches every backend at once (historical behaviour); with a positive cap the first cap probes launch immediately and each failure replenishes the next pending backend, so at most cap goroutines are ever in flight.

On success the losing probes’ contexts are cancelled so their in-flight backend round trips, decryption, and integrity work stop promptly, and any already-launched results are drained and cleaned up in the background. The winning result is returned untouched: its Value owns its own lifecycle (a GET body keeps the cancel; a HEAD has already released its timeout), so the winner’s cleanup is never invoked.

type ReadRuntime

ReadRuntime is the subset of *infra.BackendRuntime the Failover orchestrator needs: backend registry + lookup, and the accounting Recorder that owns the per-backend usage / per-operation metric semantics.

type ReadRuntime interface {
    Backends() map[string]backend.ObjectBackend
    BackendOrder() []string
    Acct() *accounting.Recorder
}

Generated by gomarkdoc