restore

package
v3.0.0-alpha.1 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: BSD-2-Clause Imports: 27 Imported by: 0

Documentation

Overview

Package restore prepares a node's work directory from backup archives so Tarantool can start on a chosen recovery point.

Index

Constants

View Source
const StateSchemaVersion = 1

StateSchemaVersion is the current restore_state.json schema version.

Variables

View Source
var (
	// ErrArchiveMissing marks an object a manifest refers to that the storage
	// does not hold. It is kept apart from a transport failure because no retry
	// brings it back: the chain is incomplete, and the operator has to pick
	// another recovery point rather than run the command again.
	ErrArchiveMissing = errors.New("restore: object is not in the backup storage")

	// ErrChecksumMismatch marks a downloaded object whose bytes do not match the
	// checksum its manifest recorded.
	ErrChecksumMismatch = errors.New("restore: checksum does not match the manifest")

	// ErrDestinationTaken marks two objects of the plan that would be downloaded
	// to the same local file.
	ErrDestinationTaken = errors.New(
		"restore: two objects would be downloaded to the same file")
)
View Source
var (
	// ErrValidation marks rejected restore input. Checksums, archive entry names
	// and archive-chain continuity are checked before clearing data directories.
	// Other checks, including the restored membership and identity, run after
	// unpacking and before a successful restore is recorded. Callers must not
	// assume that every validation error leaves previous data intact.
	ErrValidation = errors.New("restore: invalid input")

	// ErrNoTrimFile marks a recovery point that no unpacked xlog covers.
	// It is separate from a generic failure because it means the point and
	// the archives disagree, not that the node is broken.
	ErrNoTrimFile = errors.New("restore: no xlog covers the recovery point")
)

Functions

func CheckReplicasets

func CheckReplicasets(current *ClusterTopology, names []string) error

CheckReplicasets reports whether the names --replicasets was given can be resolved against the cluster configuration. Plan resolves them itself; a caller that has to open a backup storage before it can plan runs this first, so that a name it can refuse on its own costs no storage at all.

func ParseTargetTime

func ParseTargetTime(raw string) (time.Time, error)

ParseTargetTime decodes --target-time: an RFC 3339 timestamp, or a unix timestamp in seconds.

func StatePath

func StatePath(snapshotDir string) string

StatePath returns the marker path for a snapshot directory.

The snapshot directory is the anchor because the marker describes one instance and that directory belongs to one instance by construction, while the base directory a restore is called with can be shared by every instance of an application -- a marker anchored there would be overwritten by the next node restored on the same host. An instance whose directories are all one is the case where the two anchors coincide.

The directory is resolved first. A relative one names a directory just as well as an absolute one, and appending the suffix to it would put the marker somewhere else entirely: "." would name a file inside the directory the marker describes, ".." one inside its child.

Types

type ApplyOpts

type ApplyOpts struct {
	// Archives is the backup chain in order: the full backup first, then
	// each increment.
	Archives []string
	// Checksums are the sha256 of each archive, in the same order. Empty
	// skips the check.
	Checksums []string
	// Layout is where each kind of file is put. All three directories have
	// to be named; FlatLayout is the one directory case.
	Layout Layout
	// WorkDir is what the caller called the restore's base directory. It is
	// recorded in the marker and nothing else: the data goes where Layout
	// says. Empty records the snapshot directory instead.
	WorkDir string
	// InstanceName is the instance the directories were resolved for, empty
	// when they were given directly. It is recorded in the marker.
	InstanceName string
	// Point is where the final xlog is cut. Nil replays the chain whole.
	Point *Point
	// PointName is the cluster recovery point the position came from; it is
	// recorded in the marker and not otherwise used.
	PointName string
	// PatchUUID is the instance UUID to stamp into every header. Empty
	// leaves the headers as they are.
	PatchUUID string
	// Config is what the cluster configuration says about the instance the
	// directories were resolved for, nil when they were given directly. With
	// it, restored data the instance would refuse to start on is refused;
	// without it, the same finding is a warning.
	Config *ConfigIdentity
}

ApplyOpts are the parameters of tt restore apply.

type ApplyResult

type ApplyResult struct {
	// Files are the entry names landed, in the order they were unpacked and
	// relative to the directory of their own kind -- Layout.DirFor says which
	// one that is. Flat for snap/xlog, but vinyl's .run/.index carry a
	// <space_id>/<index_id>/ prefix.
	Files []string
	// Patched counts the headers restamped with the new instance UUID.
	Patched int
	// TrimmedFile is the base name of the xlog cut at the point, empty when
	// nothing was cut.
	TrimmedFile string
	// DroppedFiles are the base names removed for starting past the point.
	DroppedFiles []string
	// StatePath is where the marker was written.
	StatePath string
}

ApplyResult reports what a run produced.

func Apply

func Apply(opts ApplyOpts) (*ApplyResult, error)

Apply rebuilds the layout's directories from the archive chain: it verifies the inputs, clears the previous attempt, unpacks the chain in order -- routing every file into the directory of its kind -- stamps the instance UUID into every header, cuts the final xlog at the recovery point, and leaves a restore_state.json marker beside the snapshot directory. Archives that do not continue one another are refused rather than applied.

It is idempotent: a re-run for the same point clears what the last one left and repeats the work. Stopping the instance beforehand is the caller's job; Apply neither checks for a running instance nor starts one afterwards.

The UUID stamped into the headers is verified against the _cluster the restored snapshot carries, and the snapshot against what the instance's configuration needs to start on it, before the marker claims a completed restore.

type ClusterTopology

type ClusterTopology struct {
	// Replicasets are the replicasets the configuration declares.
	Replicasets []ConfiguredReplicaset
}

ClusterTopology is the cluster composition a recovery point is checked against.

It is read from the cluster configuration, not from the running instances. The scenario this check exists for is a cluster whose nodes are dead or in a crash loop, and a source that had to connect to them would fail long before the check ever ran. Instance UUIDs are deliberately not part of it: the sanctioned restore target is a freshly deployed cluster, where every UUID is new and the config may carry none at all, and `tt restore apply` stamps one into the node each replicaset is restored onto afterwards.

type ConfigDirs

type ConfigDirs struct {
	Snapshot       string
	WAL            string
	Vinyl          string
	ProcessWorkDir string
}

ConfigDirs are the data directories as a cluster configuration spells them, unresolved: the values of snapshot.dir, wal.dir and vinyl.dir, and the process.work_dir a relative one of them is taken against. An empty field is a key the configuration does not set.

type ConfigIdentity

type ConfigIdentity struct {
	// ReplicasetName is the replicaset the configuration puts the instance in.
	ReplicasetName string
	// InstanceUUID and ReplicasetUUID are database.instance_uuid and
	// database.replicaset_uuid, empty when the configuration pins none.
	InstanceUUID   string
	ReplicasetUUID string
	// Anon is replication.anon. An anonymous replica cannot carry a name, so
	// a snapshot without one is no reason for it not to start.
	Anon bool
	// SkipNames is compat.skip_replication_names set to 'old', under which
	// Tarantool compares no names and requires none.
	SkipNames bool
	// EnvInstanceUUID and EnvReplicasetUUID are TT_DATABASE_INSTANCE_UUID and
	// TT_DATABASE_REPLICASET_UUID as tt's own environment has them. They say
	// nothing certain about the instance, whose environment is its own: a
	// matching one only turns a refusal into a warning.
	EnvInstanceUUID   string
	EnvReplicasetUUID string
}

ConfigIdentity is what a cluster configuration says about who an instance is: the facts Tarantool's configuration holds the data directory to before it lets the instance start.

type ConfiguredReplicaset

type ConfiguredReplicaset struct {
	// Name is the replicaset name; the configuration has no other stable
	// identifier for it once the UUIDs have been re-generated.
	Name string
	// Instances are the instance names configured in the replicaset.
	Instances []string
}

ConfiguredReplicaset is one replicaset of the current configuration.

type DownloadItem

type DownloadItem struct {
	BackupID string            `json:"backup_id"`
	Type     backup.BackupType `json:"type"`
	// Artifact is the local file, not the storage key: this is the path the
	// orchestrator copies to the node.
	Artifact string `json:"artifact"`
	// ChecksumSHA256 is the sum of the bytes that were downloaded. It travels
	// with the archive so that `tt restore apply --checksums` can re-check it
	// on the node, after a copy this command knows nothing about.
	ChecksumSHA256 string `json:"checksum_sha256"`
	// TrimXlog marks the archive holding the recovery point: everything the
	// journal carries past the point must not be replayed.
	TrimXlog bool `json:"trim_xlog,omitempty"`
	// TrimTo is where that archive's journal is cut.
	TrimTo *Point `json:"trim_to,omitempty"`
}

DownloadItem is one archive of one replicaset's chain, in replay order.

type Layout

type Layout struct {
	// Snapshot is snapshot.dir: the .snap files.
	Snapshot string
	// WAL is wal.dir: the .xlog files.
	WAL string
	// Vinyl is vinyl.dir: the .vylog and the run/index files under their
	// <space_id>/<index_id>/ subdirectories.
	Vinyl string
}

Layout is the set of directories a restore rebuilds: one per kind of file Tarantool keeps. A 3.x deployment routinely puts the journal on a different device from the snapshots and the vinyl data, and every file has to come back into the directory the instance's configuration names for it -- otherwise the instance starts on whichever part of the restore it can see, which is a state nobody asked for and which looks healthy.

func FlatLayout

func FlatLayout(dir string) Layout

FlatLayout puts every kind of file in one directory. It is the layout of an instance whose configuration sets none of the three keys, and the one a caller that only knows a single directory asks for.

func LayoutFromConfig

func LayoutFromConfig(
	dirs ConfigDirs, instanceName, workDir string, override Layout,
) (Layout, error)

LayoutFromConfig turns what a cluster configuration says about an instance into the directories a restore writes into.

It follows Tarantool's own resolution, so that the restored files land where the instance will look for them: a key the configuration omits defaults to var/lib/{{ instance_name }}, {{ instance_name }} is substituted, a relative directory is taken against process.work_dir, and a relative (or absent) process.work_dir against workDir -- the directory Tarantool is launched from, which no configuration can state and the caller has to supply. Anything left relative with no workDir to resolve it against is refused rather than guessed at, naming the key that could not be resolved.

A non-empty field of override is a directory the caller has already decided, and is taken as it stands: the configuration's value for that key is neither read nor required to resolve. A caller who named a directory outright has answered the question the configuration would have answered, and refusing the run because another spelling of that same answer cannot be resolved would refuse a call that is complete. process.work_dir follows from that: it is only ever what a relative directory is taken against, so a call whose surviving directories are all absolute never reads it.

func (Layout) DirFor

func (l Layout) DirFor(entryName string) string

DirFor returns the directory an archive entry belongs in. The kind of the file decides it, because Tarantool writes each kind into one directory only: snapshots and the sort data beside them into snapshot.dir, journals into wal.dir, and the .vylog together with the numbered run/index files into vinyl.dir. An entry Tarantool did not write -- an archive can carry anything -- goes with the vinyl data rather than being dropped, so that a restore never silently loses a file the backup kept.

The classification is the one the backup names entries by, so that a file packed as one kind is unpacked as the same one.

func (Layout) Resolved

func (l Layout) Resolved() Layout

Resolved returns the layout with every directory made absolute and cleaned.

A restore resolves once and then works on the result, so that the directory it routes into, the directory it sweeps, the directory it anchors the marker on and the directory it reports are the same string. Two spellings of one directory would otherwise be swept twice, reported as two, and recorded in the marker in a form that means nothing away from the process's working directory.

Symbolic links are left alone: the marker and the report name the directory the operator named, which is what they will compare the restore against, and a path resolved through its links is a different answer to that question. Where two names have to be told apart as one place -- the sweep, which must not prune a directory of the layout it reached under another name -- the filesystem is asked instead.

type MasterDiff

type MasterDiff struct {
	ReplicasetUUID string `json:"replicaset_uuid"`
	Name           string `json:"name"`
	MasterInstance string `json:"master_instance_name"`
}

MasterDiff names a backed-up master that the configuration has no instance for. The archive of a replicaset is the master's, so a restore that cannot place it drops that replicaset's data without saying so.

type NearestSafe

type NearestSafe struct {
	Before *time.Time `json:"before,omitempty"`
	After  *time.Time `json:"after,omitempty"`
}

NearestSafe are the recovery times bracketing an unreachable target. They are times rather than points because that is what goes back into --target-time.

type PlanOpts

type PlanOpts struct {
	// Storage is the opened backup storage; the plan lists it itself rather
	// than being handed a set of manifests.
	Storage storage.Storage
	// TargetTime is the moment the cluster is to be brought back to.
	TargetTime time.Time
	// Dir is the local directory the manifests and archives land in.
	Dir string
	// Current is the cluster the restore is aimed at. Nil skips the topology
	// check and says so in the warnings: the check is worth having, but the
	// storage is worth reading without a cluster config at hand.
	Current *ClusterTopology
	// Replicasets restricts the restore to the named replicasets, the rest
	// being left to be bootstrapped fresh. The names are the configuration's,
	// so it requires Current; empty restores every replicaset the backup holds.
	Replicasets []string
}

PlanOpts are the parameters of tt restore plan.

type PlanResult

type PlanResult struct {
	// Status decides the exit code and whether the archives were downloaded.
	Status Status `json:"status"`
	// TargetTime echoes the requested moment.
	TargetTime time.Time `json:"target_time"`
	// Reason names what stopped the plan; empty on success.
	Reason string `json:"reason,omitempty"`
	// RecoveryPoint is the cluster point the restore is built around.
	RecoveryPoint *RecoveryPoint `json:"recovery_point,omitempty"`
	// DownloadPlan maps a replicaset UUID to its ordered backup chain.
	DownloadPlan map[string][]DownloadItem `json:"download_plan,omitempty"`
	// RestoreTargets maps a replicaset UUID to the node its chain is restored
	// onto. Keyed like DownloadPlan, so one key answers both what a node
	// replays and what UUID it must own afterwards.
	RestoreTargets map[string]RestoreTarget `json:"restore_targets,omitempty"`
	// NearestSafe are the recovery times on either side of an unreachable
	// target, so that a caller can offer them and retry.
	NearestSafe *NearestSafe `json:"nearest_safe,omitempty"`
	// TopologyDiff explains a topology_mismatch.
	TopologyDiff *TopologyDiff `json:"topology_diff,omitempty"`
	// Warnings are what a restore survives but an operator should know.
	Warnings []string `json:"warnings"`
}

PlanResult is the plan as it is printed. Everything an operator needs to see why a restore is or is not possible survives into it, so that a caller reads one document instead of an exit code plus a log.

func Plan

func Plan(ctx context.Context, opts PlanOpts) (*PlanResult, error)

Plan resolves a target time against the manifests in the storage, checks the resulting point against the cluster it is to be restored into, downloads the archives the point needs into Dir and returns the plan built around them.

A status other than StatusOK is a result, not an error: it says the restore cannot go ahead and why, and nothing is downloaded for it. An error means the storage could not be read at all, or an archive the plan needs is missing or corrupt - in which case there is no plan to hand out, and the cluster being restored has still not been touched.

type Point

type Point struct {
	ReplicaID uint32 `json:"replica_id"`
	LSN       uint64 `json:"lsn"`
}

Point is a recovery point position: the LSN of one replica's row in the journal. It is the shape `tt restore plan` prints under recovery_point.trim_to_by_replicaset[<replicaset_uuid>].

func ParsePoint

func ParsePoint(raw string) (*Point, error)

ParsePoint decodes the --target-point argument.

Unknown keys are rejected rather than ignored: a misspelled "replica_id" would otherwise decode to 0 and silently trim on the wrong axis, and picking the wrong axis is exactly the failure this command has to avoid.

func (Point) String

func (p Point) String() string

String renders the point the way it is written on the command line.

type RecoveryPoint

type RecoveryPoint struct {
	// Label is the point's cluster-wide label, what the point manager wrote on
	// every shard and what `tt restore apply --point-name` records.
	Label string `json:"label"`
	// Timestamp is the point's effective time: the earliest among its shards.
	Timestamp time.Time `json:"timestamp"`
	// TrimToByReplicaset is where each replicaset's journal is cut, keyed by
	// replicaset UUID. It is what `tt restore apply --target-point` takes.
	TrimToByReplicaset map[string]Point `json:"trim_to_by_replicaset"`
}

RecoveryPoint is the chosen cluster point and its per-replicaset positions.

type ReplicasetDiff

type ReplicasetDiff struct {
	ReplicasetUUID string `json:"replicaset_uuid,omitempty"`
	Name           string `json:"name,omitempty"`
	// Instances are the instance names of this replicaset.
	Instances []string `json:"instances"`
	// Candidates are the replicasets on the other side it could equally be.
	Candidates []string `json:"candidates,omitempty"`
}

ReplicasetDiff names one replicaset by whatever identifies it on its side: the backup knows a replicaset UUID, the configuration a name, and both know the instance names the matching is done on.

type RestoreInstance

type RestoreInstance struct {
	// InstanceName is the node this chain goes to.
	InstanceName string `json:"instance_name"`
	// PatchUUID is the instance UUID the restored node must carry: the one it
	// held in the backed-up cluster, read off the manifest's topology entry
	// for it. Absent when the manifest recorded none for the member.
	PatchUUID string `json:"patch_uuid,omitempty"`
	// Role is the member's place in the replicaset: master for the instance
	// the archives were taken on, replica for every other member.
	Role string `json:"role"`
}

RestoreInstance is one node a replicaset's chain is replayed onto.

type RestoreTarget

type RestoreTarget struct {
	// InstanceName is the node this replicaset's chain goes to: the master,
	// the instance the backup was taken on. It is the master's own entry of
	// Instances, kept at the top level for a reader of the older document.
	InstanceName string `json:"instance_name"`
	// PatchUUID is the instance UUID the restored master must carry: the one
	// it held in the backed-up cluster, which is what `tt restore apply
	// --patch-uuid` stamps into every header. Restoring onto the instance the
	// backup was taken on makes it the UUID already in the files, and the patch
	// a confirmation. It is absent only when the manifest recorded no UUID at
	// all, and then a warning says so rather than an empty value going out as
	// if it meant "do not patch".
	PatchUUID string `json:"patch_uuid,omitempty"`
	// Instances names every node the chain is replayed onto, the master first
	// and the rest by name, each with the instance UUID that node owned in
	// the backed-up cluster - `tt restore apply` runs per instance with its
	// own --patch-uuid. A manifest that recorded no members beside the master
	// lists it alone, and a warning says so: single-target restore.
	Instances []RestoreInstance `json:"instances"`
	// Rejoin are the configured instances of the replicaset the backup holds
	// no member for: wiped before the restore, joining the replicaset from
	// nothing once it is up. Filled only when a cluster config was given,
	// because the backup does not know the composition of the cluster being
	// restored into.
	Rejoin []string `json:"rejoin,omitempty"`
}

RestoreTarget names the instances a replicaset's chain is restored onto, and the instance UUID each of them has to own once it is.

Every member the backup recorded is restored in place: each node replays the replicaset's chain and comes up as itself, so a member with a uuid of its own in the manifest keeps it. The members the configuration carries but the backup never saw are the ones wiped and bootstrapped fresh: Tarantool 3.x identifies an instance by name, so such a member boots empty, reclaims its _cluster record under a fresh UUID and needs none of its own.

type State

type State struct {
	SchemaVersion int `json:"schema_version"`
	// WorkDir is the base directory the restore was called with, so a marker
	// found on its own still says what it belongs to.
	WorkDir string `json:"work_dir"`
	// SnapshotDir, WALDir and VinylDir are the directories the files were
	// actually put in. They are what makes the marker readable on a split
	// layout, where WorkDir names none of them.
	SnapshotDir string `json:"snapshot_dir"`
	WALDir      string `json:"wal_dir"`
	VinylDir    string `json:"vinyl_dir"`
	// InstanceName is the instance whose configuration the directories were
	// resolved from, empty when they were given directly.
	InstanceName string `json:"instance_name,omitempty"`
	// PointName is the cluster recovery point (recovery_point.name of the
	// restore plan). It is what makes the cross-node comparison meaningful:
	// TargetPoint differs per replicaset, the name does not. Empty when the
	// operator did not pass --point-name.
	PointName string `json:"point_name,omitempty"`
	// TargetPoint is the per-replicaset position the final xlog was cut at,
	// nil when the chain was replayed whole.
	TargetPoint *Point `json:"target_point"`
	// InstanceUUID is the UUID stamped into the headers, empty when
	// --patch-uuid was omitted.
	InstanceUUID string `json:"instance_uuid,omitempty"`
	// Archives lists the applied chain in order, by base name.
	Archives  []string  `json:"archives"`
	AppliedAt time.Time `json:"applied_at"`
}

State is the marker a successful apply leaves next to the work directory.

It is the seam between apply and starting the cluster. A partial restore -- one replicaset of N skipped or failed -- brings up a cluster whose shards sit on different states: each is self-consistent and replication is healthy, so nothing looks wrong until the buckets disagree. Comparing the markers of every restored node before the cluster is started is the orchestrator's job; apply only leaves the machine-readable evidence it needs. The members that were wiped instead of restored leave no marker, and are not missing one.

func ReadState

func ReadState(snapshotDir string) (*State, error)

ReadState decodes the marker next to a snapshot directory.

type Status

type Status string

Status is the verdict of a planning run. Five of the six come from resolving the target time against the chain; topology_mismatch is this command's own, because the chain is pure over manifests and never sees the cluster a restore is aimed at.

const (
	StatusOK               Status = "ok"
	StatusTopologyBoundary Status = "topology_boundary"
	StatusNoRecoveryPoint  Status = "no_recovery_point"
	StatusChainBroken      Status = "chain_broken"
	StatusOutOfRange       Status = "out_of_range"
	StatusTopologyMismatch Status = "topology_mismatch"
)

type TopologyDiff

type TopologyDiff struct {
	// MissingReplicasets were backed up but no configured replicaset shares an
	// instance name with them: their data has nowhere to go.
	MissingReplicasets []ReplicasetDiff `json:"missing_replicasets,omitempty"`
	// ExtraReplicasets are configured but nothing in the backup maps onto them.
	ExtraReplicasets []ReplicasetDiff `json:"extra_replicasets,omitempty"`
	// AmbiguousReplicasets share instance names with more than one replicaset
	// on the other side, so no mapping can be picked without guessing.
	AmbiguousReplicasets []ReplicasetDiff `json:"ambiguous_replicasets,omitempty"`
	// UncoveredMasters are backed-up masters the matched replicaset of the
	// configuration does not carry.
	UncoveredMasters []MasterDiff `json:"uncovered_masters,omitempty"`
}

TopologyDiff explains why the topology a recovery point was taken on cannot be laid onto the cluster the configuration describes. Only replicaset-level differences land here; a replaced replica inside a matched replicaset is a normal companion of a restore and is reported as a warning instead.

func (TopologyDiff) Empty

func (d TopologyDiff) Empty() bool

Empty reports whether the topologies can be laid onto one another.

Jump to

Keyboard shortcuts

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