Help us shape the future of Go! Take the 2026 Go Developer Survey

snapshotoffload

package
v0.0.0-...-429f5ee Latest Latest
Warning

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

Go to latest
Published: Oct 21, 2026 License: AGPL-3.0 Imports: 27 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DefaultSchedulerInterval    = 15 * time.Minute
	DefaultSchedulerConcurrency = 1
)

Default scheduling parameters from §4.

View Source
const (
	ManifestSchemaVersion = 1
)

Variables

View Source
var (
	ErrInvalidOptions = errors.New("snapshot offload: invalid options")
	ErrIntegrity      = errors.New("snapshot offload: integrity check failed")
	ErrObjectConflict = errors.New("snapshot offload: object conflict")
	ErrObjectNotFound = errors.New("snapshot offload: object not found")

	// ErrNoPersistedSnapshot reports that the LOCAL data dir has no
	// persisted snapshot yet. It is deliberately distinct from
	// ErrObjectNotFound: a young group that has not snapshotted is a
	// normal scan outcome, whereas an object vanishing from the store
	// mid-publish is a real failure, and collapsing the two would
	// silence the second.
	ErrNoPersistedSnapshot = errors.New("snapshot offload: no persisted snapshot available")

	// ErrSnapshotNotNewer reports that the persisted snapshot is not
	// newer than the caller's high-water mark, so nothing was
	// published. It is a normal outcome for a scheduler tick over an
	// unchanged snapshot, not a failure.
	ErrSnapshotNotNewer = errors.New("snapshot offload: persisted snapshot is not newer than the last published index")
)

Functions

func ValidateKMSKeyID

func ValidateKMSKeyID(value string) error

ValidateKMSKeyID accepts canonical key ARNs and bare key IDs. Aliases are rejected because S3 reports the resolved key identity, not the alias string.

func ValidateS3StoreEncryption

func ValidateS3StoreEncryption(encryption, kmsKeyID string) error

Types

type LocalStore

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

func NewLocalStore

func NewLocalStore(root string) (*LocalStore, error)

func (*LocalStore) GetObject

func (s *LocalStore) GetObject(ctx context.Context, key string) (io.ReadCloser, ObjectInfo, error)

func (*LocalStore) HeadObject

func (s *LocalStore) HeadObject(ctx context.Context, key string) (ObjectInfo, bool, error)

func (*LocalStore) PutObject

func (s *LocalStore) PutObject(ctx context.Context, key string, body io.Reader, opts PutOptions) (ObjectInfo, error)

type Manifest

type Manifest struct {
	SchemaVersion  int               `json:"schema_version"`
	CreatedAt      time.Time         `json:"created_at"`
	SourceCluster  string            `json:"source_cluster,omitempty"`
	GroupID        uint64            `json:"group_id"`
	SnapshotIndex  uint64            `json:"snapshot_index"`
	SnapshotTerm   uint64            `json:"snapshot_term"`
	ConfState      ManifestConfState `json:"conf_state"`
	Payload        PayloadDescriptor `json:"payload"`
	BinaryVersion  string            `json:"binary_version,omitempty"`
	ManifestKey    string            `json:"manifest_key"`
	ManifestSHA256 string            `json:"manifest_sha256,omitempty"`
}

func DecodeManifest

func DecodeManifest(data []byte) (Manifest, error)

func LoadManifest

func LoadManifest(ctx context.Context, store ObjectStore, key string) (Manifest, error)

func PublishPersistedSnapshot

func PublishPersistedSnapshot(ctx context.Context, opts PublishOptions) (*Manifest, error)

func (Manifest) MarshalCanonical

func (m Manifest) MarshalCanonical() ([]byte, string, error)

type ManifestConfState

type ManifestConfState struct {
	Voters         []uint64 `json:"voters,omitempty"`
	Learners       []uint64 `json:"learners,omitempty"`
	VotersOutgoing []uint64 `json:"voters_outgoing,omitempty"`
	LearnersNext   []uint64 `json:"learners_next,omitempty"`
	AutoLeave      bool     `json:"auto_leave,omitempty"`
}

type ObjectInfo

type ObjectInfo struct {
	Key  string
	Size int64
	// SHA256 is optional for metadata-only Head/Get paths; PutObject returns it
	// when the writer verified the committed content.
	SHA256               string
	ServerSideEncryption string
	SSEKMSKeyID          string
}

type ObjectStore

type ObjectStore interface {
	PutObject(ctx context.Context, key string, body io.Reader, opts PutOptions) (ObjectInfo, error)
	GetObject(ctx context.Context, key string) (io.ReadCloser, ObjectInfo, error)
	HeadObject(ctx context.Context, key string) (ObjectInfo, bool, error)
}

type OffloadGroup

type OffloadGroup struct {
	GroupID uint64
	DataDir string
	// IsLeader is the cheap pre-check made before opening the snapshot.
	IsLeader func() bool
	// VerifyLeader is the authoritative check, re-run immediately before the
	// manifest is committed. See PublishOptions.VerifyLeader.
	VerifyLeader func(context.Context) error
}

OffloadGroup is one local Raft group the scheduler may publish for.

type PayloadDescriptor

type PayloadDescriptor struct {
	Key          string `json:"key"`
	Bytes        int64  `json:"bytes"`
	SHA256       string `json:"sha256"`
	SourceCRC32C uint32 `json:"source_crc32c"`
}

type PublishOptions

type PublishOptions struct {
	Store         ObjectStore
	DataDir       string
	Prefix        string
	GroupID       uint64
	SourceCluster string
	BinaryVersion string
	CreatedAt     time.Time
	SpoolDir      string
	// VerifyLeader, when set, is re-checked immediately before the manifest is
	// committed. §4 requires leadership to hold at that instant, not merely when
	// the snapshot was opened: spooling a multi-gigabyte payload takes long
	// enough to lose an election. Failing here can leave an unreferenced
	// content-addressed payload, which GC reclaims, but never a committed
	// manifest naming a snapshot this node no longer had the right to publish.
	VerifyLeader func(context.Context) error
	// SkipIfNotNewerThan suppresses the publish when the persisted
	// snapshot's index is not greater than this value. Zero disables
	// the check.
	//
	// The comparison happens after the export is opened but BEFORE
	// the payload is spooled, which is the whole point: a scheduler
	// that ticks every 15 minutes over an unchanged snapshot would
	// otherwise re-read, re-hash and re-fsync a multi-gigabyte
	// payload every tick just to discover the object store already
	// has it.
	SkipIfNotNewerThan uint64
}

type PutOptions

type PutOptions struct {
	Size        int64
	SHA256      string
	ContentType string
}

type RestoreOptions

type RestoreOptions struct {
	Store       ObjectStore
	ManifestKey string
	Manifest    *Manifest
	DataDir     string
	Peers       []etcdraftengine.Peer
}

type S3Store

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

func NewS3Store

func NewS3Store(ctx context.Context, cfg S3StoreConfig) (*S3Store, error)

func (*S3Store) GetObject

func (s *S3Store) GetObject(ctx context.Context, key string) (io.ReadCloser, ObjectInfo, error)

func (*S3Store) HeadObject

func (s *S3Store) HeadObject(ctx context.Context, key string) (ObjectInfo, bool, error)

func (*S3Store) PutObject

func (s *S3Store) PutObject(ctx context.Context, key string, body io.Reader, opts PutOptions) (ObjectInfo, error)

type S3StoreConfig

type S3StoreConfig struct {
	Client                 S3ObjectClient
	Bucket                 string
	Region                 string
	Endpoint               string
	Profile                string
	ForcePathStyle         bool
	AccessKeyID            string
	SecretAccessKey        string
	SessionToken           string
	ServerSideEncryption   string
	SSEKMSKeyID            string
	DisableChecksumHeaders bool
}

type Scheduler

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

Scheduler is the §4 leader-only publisher: each process scans its own Raft groups on an interval and offloads a persisted snapshot when one exists that has not been published yet.

It never asks the state machine for a snapshot. Snapshot cadence stays owned by the Raft engine, so this milestone can only publish what the engine has already persisted -- an offload that forced its own snapshot would change compaction behaviour, which §10 lists as a non-goal.

func NewScheduler

func NewScheduler(store ObjectStore, groups []OffloadGroup, prefix, sourceCluster, binaryVersion string, opts ...SchedulerOption) (*Scheduler, error)

NewScheduler builds the offload scheduler. It is opt-in: callers construct it only when object offload is configured. NewScheduler validates its configuration eagerly so an invalid scheduler cannot be constructed at all. In particular every group must supply both leadership callbacks: SyncOnce is exported and does not re-validate, so a nil callback that survived construction would be a follower publishing a manifest.

func (*Scheduler) LastPublishedIndex

func (s *Scheduler) LastPublishedIndex(groupID uint64) uint64

LastPublishedIndex reports the highest index this process has published for a group. Zero means "nothing published by this process", not "nothing published": another node or a previous run may hold newer manifests.

func (*Scheduler) Run

func (s *Scheduler) Run(ctx context.Context) error

Run scans on the configured interval until ctx is cancelled. Cancellation is the only stop condition; a failing group is retried on the next tick rather than tearing the loop down, because an object store outage must not stop the process.

func (*Scheduler) SyncOnce

func (s *Scheduler) SyncOnce(ctx context.Context)

SyncOnce runs one scan across every local group, bounded by the upload concurrency limit. Exported so tests and operators can force a pass.

type SchedulerObserver

type SchedulerObserver interface {
	ObserveSnapshotOffloadPublished(groupID, index uint64, payloadBytes int64, elapsed time.Duration)
	ObserveSnapshotOffloadSkipped(groupID uint64, reason string)
	ObserveSnapshotOffloadFailed(groupID uint64, err error)
}

SchedulerObserver receives per-attempt outcomes for metrics.

type SchedulerOption

type SchedulerOption func(*Scheduler)

func WithSchedulerClock

func WithSchedulerClock(now func() time.Time) SchedulerOption

func WithSchedulerConcurrency

func WithSchedulerConcurrency(n int) SchedulerOption

func WithSchedulerInterval

func WithSchedulerInterval(d time.Duration) SchedulerOption

func WithSchedulerJitter

func WithSchedulerJitter(d time.Duration) SchedulerOption

WithSchedulerJitter spreads multi-group work so every group in a process does not contend for the upload slot on the same tick.

func WithSchedulerLogger

func WithSchedulerLogger(l *slog.Logger) SchedulerOption

func WithSchedulerObserver

func WithSchedulerObserver(o SchedulerObserver) SchedulerOption

func WithSchedulerSpoolDir

func WithSchedulerSpoolDir(dir string) SchedulerOption

Jump to

Keyboard shortcuts

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