Compare commits

...

3 Commits

Author SHA1 Message Date
Christian Schwarz aa7a13a1c1 Pruner: parallel pruning
refs #62
2018-02-27 01:27:12 +01:00
Christian Schwarz f76a0dec6d util.Semaphore: initial commit 2018-02-27 00:20:32 +01:00
Christian Schwarz b34d0e1041 autosnap: parallel snapshotting and bookmarking
refs #62
2018-02-26 22:18:09 +01:00
3 changed files with 104 additions and 54 deletions
+21 -7
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/zrepl/zrepl/zfs"
"sort"
"sync"
"time"
)
@@ -165,33 +166,46 @@ func (a *IntervalAutosnap) doSnapshots(didSnaps chan struct{}) {
// don't cache the result from previous run in case the user added
// a new dataset in the meantime
ds, stop := a.filterFilesystems()
fss, stop := a.filterFilesystems()
if stop {
return
}
a.task.Log().Info("beginning parallel snapshots")
// TODO channel programs -> allow a little jitter?
for _, d := range ds {
var wg sync.WaitGroup
for fsi := range fss {
wg.Add(1)
go func(fs *zfs.DatasetPath) {
defer wg.Done()
suffix := time.Now().In(time.UTC).Format("20060102_150405_000")
snapname := fmt.Sprintf("%s%s", a.Prefix, suffix)
l := a.task.Log().WithField(logFSField, d.ToString()).
l := a.task.Log().WithField(logFSField, fs.ToString()).
WithField("snapname", snapname)
l.Info("create snapshot")
err := zfs.ZFSSnapshot(d, snapname, false)
err := zfs.ZFSSnapshot(fs, snapname, false)
if err != nil {
a.task.Log().WithError(err).Error("cannot create snapshot")
l.WithError(err).Error("cannot create snapshot")
return
}
l.Info("create corresponding bookmark")
err = zfs.ZFSBookmark(d, snapname, snapname)
err = zfs.ZFSBookmark(fs, snapname, snapname)
if err != nil {
a.task.Log().WithError(err).Error("cannot create bookmark")
l.WithError(err).Error("cannot create bookmark")
}
}(fss[fsi])
}
a.task.Log().Info("waiting for parallel snapshots to finish")
wg.Wait()
a.task.Log().Info("snapshots finished")
select {
case didSnaps <- struct{}{}:
default:
+39 -20
View File
@@ -3,7 +3,9 @@ package cmd
import (
"context"
"fmt"
"github.com/zrepl/zrepl/util"
"github.com/zrepl/zrepl/zfs"
"sync"
"time"
)
@@ -23,9 +25,8 @@ type PruneResult struct {
Remove []zfs.FilesystemVersion
}
// FIXME must not call p.task.Enter because it runs in parallel
func (p *Pruner) filterFilesystems() (filesystems []*zfs.DatasetPath, stop bool) {
p.task.Enter("filter_fs")
defer p.task.Finish()
filesystems, err := zfs.ZFSListMapping(p.DatasetFilter)
if err != nil {
p.task.Log().WithError(err).Error("error applying filesystem filter")
@@ -38,9 +39,8 @@ func (p *Pruner) filterFilesystems() (filesystems []*zfs.DatasetPath, stop bool)
return filesystems, false
}
// FIXME must not call p.task.Enter because it runs in parallel
func (p *Pruner) filterVersions(fs *zfs.DatasetPath) (fsversions []zfs.FilesystemVersion, stop bool) {
p.task.Enter("filter_versions")
defer p.task.Finish()
log := p.task.Log().WithField(logFSField, fs.ToString())
filter := NewPrefixFilter(p.SnapshotPrefix)
@@ -56,9 +56,8 @@ func (p *Pruner) filterVersions(fs *zfs.DatasetPath) (fsversions []zfs.Filesyste
return fsversions, false
}
func (p *Pruner) pruneFilesystem(fs *zfs.DatasetPath) (r PruneResult, valid bool) {
p.task.Enter("prune_fs")
defer p.task.Finish()
// FIXME must not call p.task.Enter because it runs in parallel
func (p *Pruner) pruneFilesystem(fs *zfs.DatasetPath, destroySemaphore util.Semaphore) (r PruneResult, valid bool) {
log := p.task.Log().WithField(logFSField, fs.ToString())
fsversions, stop := p.filterVersions(fs)
@@ -66,9 +65,7 @@ func (p *Pruner) pruneFilesystem(fs *zfs.DatasetPath) (r PruneResult, valid bool
return
}
p.task.Enter("prune_policy")
keep, remove, err := p.PrunePolicy.Prune(fs, fsversions)
p.task.Finish()
if err != nil {
log.WithError(err).Error("error evaluating prune policy")
return
@@ -81,8 +78,13 @@ func (p *Pruner) pruneFilesystem(fs *zfs.DatasetPath) (r PruneResult, valid bool
r = PruneResult{fs, fsversions, keep, remove}
makeFields := func(v zfs.FilesystemVersion) (fields map[string]interface{}) {
fields = make(map[string]interface{})
var wg sync.WaitGroup
for v := range remove {
wg.Add(1)
go func(v zfs.FilesystemVersion) {
defer wg.Done()
// log fields
fields := make(map[string]interface{})
fields["version"] = v.ToAbsPath(fs)
timeSince := v.Creation.Sub(p.Now)
fields["age_ns"] = timeSince
@@ -90,24 +92,23 @@ func (p *Pruner) pruneFilesystem(fs *zfs.DatasetPath) (r PruneResult, valid bool
days := timeSince / day
remainder := timeSince % day
fields["age_str"] = fmt.Sprintf("%dd%s", days, remainder)
return
}
for _, v := range remove {
fields := makeFields(v)
log.WithFields(fields).Info("destroying version")
// echo what we'll do and exec zfs destroy if not dry run
// TODO special handling for EBUSY (zfs hold)
// TODO error handling for clones? just echo to cli, skip over, and exit with non-zero status code (we're idempotent)
if !p.DryRun {
p.task.Enter("destroy")
destroySemaphore.Down()
err := zfs.ZFSDestroyFilesystemVersion(fs, v)
p.task.Finish()
destroySemaphore.Up()
if err != nil {
log.WithFields(fields).WithError(err).Error("error destroying version")
}
}
}(remove[v])
}
wg.Wait()
return r, true
}
@@ -124,13 +125,31 @@ func (p *Pruner) Run(ctx context.Context) (r []PruneResult, err error) {
return
}
r = make([]PruneResult, 0, len(filesystems))
maxConcurrentDestroy := len(filesystems)
p.task.Log().WithField("max_concurrent_destroy", maxConcurrentDestroy).Info("begin concurrent destroy")
destroySem := util.NewSemaphore(maxConcurrentDestroy)
resChan := make(chan PruneResult, len(filesystems))
var wg sync.WaitGroup
for _, fs := range filesystems {
res, ok := p.pruneFilesystem(fs)
wg.Add(1)
go func(fs *zfs.DatasetPath) {
defer wg.Done()
res, ok := p.pruneFilesystem(fs, destroySem)
if ok {
r = append(r, res)
resChan <- res
}
}(fs)
}
wg.Wait()
close(resChan)
p.task.Log().Info("destroys done")
r = make([]PruneResult, 0, len(filesystems))
for res := range resChan {
r = append(r, res)
}
return
+17
View File
@@ -0,0 +1,17 @@
package util
type Semaphore struct {
c chan struct{}
}
func NewSemaphore(cap int) Semaphore {
return Semaphore{make(chan struct{}, cap)}
}
func (s Semaphore) Down() {
s.c <- struct{}{}
}
func (s Semaphore) Up() {
<-s.c
}