new implementation of limiting repository size during backup

Changed saveAndEncrypt(...) to include tracking of current size and
raising an error "MaxCapacityExceeded" when the limit has been exceeded.

Added CurrentRepositorySize(ctx) to report current size

Added MaxCapacityExceeded() to query if limit has been execced

Added IsRepositoryLimitActive() to query if size monitoring is active

In addition an interface definition added to fulfill the needs of internal/archiver
for accessing repository functions.

The issue descrition has been updated.
This commit is contained in:
Winfried Plappert 2025-03-02 08:41:12 +00:00
parent de9a040d27
commit 4ffb6f2c55
2 changed files with 87 additions and 1 deletions

View File

@ -0,0 +1,29 @@
Enhancement: Limit repository size to a predefined maximum size during vackup
Restic backup can now limit the repository size to a given maximum value. The
repository size limit can be specified as option `--max-repo-size`, with
the usual meaning for size related options.
During backup, the current size is monitored by calculating its actual repository
size plus the size of any new blob added during the backup process. Once the
defined limit is exceeded, backup is prevented from creating new backup entries
and the 'in progress' files are finalized, without adding large amounts of new data.
The size limit is a rough limit and cannot be taken as a precise limit,
since indexes and snapshot file have to be finalized.
Due to the highly parallel processing of a backup process
the limit can be overshot by `packfile size` multiple times.
With a current default of 16m for a packfile,
you might experience differences of multiple megabytes between the expected limit
and the actual size when the backup process terminates.
The new packfiles are created by the current backup process and a new snapshot
will be created.
Currently the implemenation is incomplete. There are currently two indications
that there is a incomplete backup:
field `PartialSnapshot` exist and is set to true and
the snapshot is tagged as `partial-snapshot`.
https://github.com/restic/restic/issues/4583
https://github.com/restic/restic/pull/5215

View File

@ -51,6 +51,8 @@ type Repository struct {
allocDec sync.Once
enc *zstd.Encoder
dec *zstd.Decoder
MaxRepoCapReached bool
}
// internalRepository allows using SaveUnpacked and RemoveUnpacked with all FileTypes
@ -62,6 +64,8 @@ type Options struct {
Compression CompressionMode
PackSize uint
NoExtraVerify bool
RepoSizeMax uint64
repoCurSize uint64
}
// CompressionMode configures if data should be compressed.
@ -391,7 +395,60 @@ func (r *Repository) saveAndEncrypt(ctx context.Context, t restic.BlobType, data
panic(fmt.Sprintf("invalid type: %v", t))
}
return pm.SaveBlob(ctx, t, id, ciphertext, uncompressedLength)
length, err := pm.SaveBlob(ctx, t, id, ciphertext, uncompressedLength)
var m sync.Mutex
// maximum repository capacity exceeded?
m.Lock()
defer m.Unlock()
if r.opts.RepoSizeMax > 0 {
r.opts.repoCurSize += uint64(length)
if r.opts.repoCurSize > r.opts.RepoSizeMax {
r.MaxRepoCapReached = true
debug.Log("MaxCapacityExceeded")
return length, errors.New("MaxCapacityExceeded")
}
}
return length, err
}
// CurrentRepositorySize counts the sizes of the filetypes snapshot, index and packs
func (r *Repository) CurrentRepositorySize(ctx context.Context) (uint64, error) {
curSize := uint64(0)
if r.opts.RepoSizeMax > 0 {
for _, ft := range []restic.FileType{restic.SnapshotFile, restic.IndexFile, restic.PackFile} {
err := r.List(ctx, ft, func(_ restic.ID, size int64) error {
curSize += uint64(size)
return nil
})
if err != nil {
return 0, err
}
}
r.opts.repoCurSize = curSize
return curSize, nil
}
return 0, errors.New("repository maximum size has not been set")
}
// MaxCapacityExceeded reports if repository has a limit and if it is exceeded
func (r *Repository) MaxCapacityExceeded() bool {
if r.opts.RepoSizeMax == 0 {
return false
}
return r.MaxRepoCapReached
}
func (r *Repository) IsRepositoryLimitActive() bool {
return r.opts.RepoSizeMax > 0
}
// CapacityChecker has to satisfy restic.Repository interface needs
type CapacityChecker interface {
MaxCapacityExceeded() bool
IsRepositoryLimitActive() bool
}
func (r *Repository) verifyCiphertext(buf []byte, uncompressedLength int, id restic.ID) error {