Documentation
¶
Index ¶
- Constants
- Variables
- func RestorePhysicalSnapshot(ctx context.Context, opts RestoreOptions) (*etcdraftengine.ExternalSnapshotRestoreResult, error)
- func ValidateKMSKeyID(value string) error
- func ValidateS3StoreEncryption(encryption, kmsKeyID string) error
- type LocalStore
- func (s *LocalStore) GetObject(ctx context.Context, key string) (io.ReadCloser, ObjectInfo, error)
- func (s *LocalStore) HeadObject(ctx context.Context, key string) (ObjectInfo, bool, error)
- func (s *LocalStore) PutObject(ctx context.Context, key string, body io.Reader, opts PutOptions) (ObjectInfo, error)
- type Manifest
- type ManifestConfState
- type ObjectInfo
- type ObjectStore
- type OffloadGroup
- type PayloadDescriptor
- type PublishOptions
- type PutOptions
- type RestoreOptions
- type S3ObjectClient
- type S3Store
- type S3StoreConfig
- type Scheduler
- type SchedulerObserver
- type SchedulerOption
- func WithSchedulerClock(now func() time.Time) SchedulerOption
- func WithSchedulerConcurrency(n int) SchedulerOption
- func WithSchedulerInterval(d time.Duration) SchedulerOption
- func WithSchedulerJitter(d time.Duration) SchedulerOption
- func WithSchedulerLogger(l *slog.Logger) SchedulerOption
- func WithSchedulerObserver(o SchedulerObserver) SchedulerOption
- func WithSchedulerSpoolDir(dir string) SchedulerOption
Constants ¶
const ( DefaultSchedulerInterval = 15 * time.Minute DefaultSchedulerConcurrency = 1 )
Default scheduling parameters from §4.
const (
ManifestSchemaVersion = 1
)
Variables ¶
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 RestorePhysicalSnapshot ¶
func RestorePhysicalSnapshot(ctx context.Context, opts RestoreOptions) (*etcdraftengine.ExternalSnapshotRestoreResult, error)
func ValidateKMSKeyID ¶
ValidateKMSKeyID accepts canonical key ARNs and bare key IDs. Aliases are rejected because S3 reports the resolved key identity, not the alias string.
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 LoadManifest ¶
func PublishPersistedSnapshot ¶
func PublishPersistedSnapshot(ctx context.Context, opts PublishOptions) (*Manifest, error)
type ManifestConfState ¶
type ObjectInfo ¶
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 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 RestoreOptions ¶
type RestoreOptions struct {
Store ObjectStore
ManifestKey string
Manifest *Manifest
DataDir string
Peers []etcdraftengine.Peer
}
type S3ObjectClient ¶
type S3ObjectClient interface {
PutObject(context.Context, *s3.PutObjectInput, ...func(*s3.Options)) (*s3.PutObjectOutput, error)
GetObject(context.Context, *s3.GetObjectInput, ...func(*s3.Options)) (*s3.GetObjectOutput, error)
HeadObject(context.Context, *s3.HeadObjectInput, ...func(*s3.Options)) (*s3.HeadObjectOutput, error)
CreateMultipartUpload(context.Context, *s3.CreateMultipartUploadInput, ...func(*s3.Options)) (*s3.CreateMultipartUploadOutput, error)
UploadPart(context.Context, *s3.UploadPartInput, ...func(*s3.Options)) (*s3.UploadPartOutput, error)
CompleteMultipartUpload(context.Context, *s3.CompleteMultipartUploadInput, ...func(*s3.Options)) (*s3.CompleteMultipartUploadOutput, error)
AbortMultipartUpload(context.Context, *s3.AbortMultipartUploadInput, ...func(*s3.Options)) (*s3.AbortMultipartUploadOutput, error)
}
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 ¶
type S3StoreConfig ¶
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 ¶
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.
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