WIP runtime-controllable concurrency for replication

Changes done so far:
- signal to route concurrency request up to the stepQueue
    - pretty hacky, no status reporting yet
- stepQueue upsizing (confirmed that it works, no intermediary commit)

Stuck at: stepQueue downsizing
- idea was to have the stepQueue signal to the activated step that it
  should suspend
- ideally, we'd just kill everything associated with the step and track
  it as restartable
    - the driver model doesn't allow for that though within an attempt
    - would need to start a separate run for the step
- less perfect: tell the downsized steps to stop copying, but leave
  all zfs sends + rpc conns open
    - - doesn't give back the resoures that a step aquired before
      being selected as downsize victim (open zfs processe + TCP conn,
      some memory in the pipe)
    - - looks weird to user if the ps aux
    - + re-waking a step is easy: just tell it to proceed with copying
    - (the impl would likely pass a check function down into Step.do
      and have it check that functino periodically. the suspend should
      be acknowledged, and stepQueue should only remove the step from
      the active queue _after_ that step has acknowledged that is
      suspended)
This commit is contained in:
Christian Schwarz
2020-02-01 15:28:52 +01:00
parent b8d9f4ba92
commit 17add553d3
14 changed files with 244 additions and 75 deletions
+12
View File
@@ -8,6 +8,7 @@ import (
"io"
"net"
"net/http"
"strconv"
"time"
"github.com/pkg/errors"
@@ -47,6 +48,8 @@ func (j *controlJob) OwnedDatasetSubtreeRoot() (p *zfs.DatasetPath, ok bool) { r
func (j *controlJob) SenderConfig() *endpoint.SenderConfig { return nil }
func (j *controlJob) SetConcurrency(concurrency int) error { return errors.Errorf("not supported") }
var promControl struct {
requestBegin *prometheus.CounterVec
requestFinished *prometheus.HistogramVec
@@ -126,6 +129,7 @@ func (j *controlJob) Run(ctx context.Context) {
type reqT struct {
Name string
Op string
Data string
}
var req reqT
if decoder(&req) != nil {
@@ -138,6 +142,14 @@ func (j *controlJob) Run(ctx context.Context) {
err = j.jobs.wakeup(req.Name)
case "reset":
err = j.jobs.reset(req.Name)
case "set-concurrency":
var concurrency int
concurrency, err = strconv.Atoi(req.Data) // shadow
if err != nil {
// fallthrough outer
} else {
err = j.jobs.setConcurrency(req.Name, concurrency)
}
default:
err = fmt.Errorf("operation %q is invalid", req.Op)
}
+12
View File
@@ -176,6 +176,18 @@ func (s *jobs) reset(job string) error {
return wu()
}
func (s *jobs) setConcurrency(jobName string, concurrency int) error {
s.m.RLock()
defer s.m.RUnlock()
job, ok := s.jobs[jobName]
if !ok {
return errors.Errorf("Job %q does not exist", job)
}
return job.SetConcurrency(concurrency)
}
const (
jobNamePrometheus = "_prometheus"
jobNameControl = "_control"
+30 -6
View File
@@ -55,9 +55,12 @@ const (
type activeSideTasks struct {
state ActiveSideState
concurrency int
// valid for state ActiveSideReplicating, ActiveSidePruneSender, ActiveSidePruneReceiver, ActiveSideDone
replicationReport driver.ReportFunc
replicationCancel context.CancelFunc
replicationReport driver.ReportFunc
replicationCancel context.CancelFunc
replicationSetConcurrency driver.SetConcurrencyFunc
// valid for state ActiveSidePruneSender, ActiveSidePruneReceiver, ActiveSideDone
prunerSender, prunerReceiver *pruner.Pruner
@@ -278,6 +281,8 @@ func activeSide(g *config.Global, in *config.ActiveJob, configJob interface{}) (
return nil, errors.Wrap(err, "invalid job name")
}
j.tasks.concurrency = 1 // FIXME
switch v := configJob.(type) {
case *config.PushJob:
j.mode, err = modePushFromConfig(g, v, j.name) // shadow
@@ -375,6 +380,22 @@ func (j *ActiveSide) SenderConfig() *endpoint.SenderConfig {
return push.senderConfig
}
func (j *ActiveSide) SetConcurrency(concurrency int) (err error) {
j.updateTasks(func(tasks *activeSideTasks) {
if tasks.replicationSetConcurrency != nil {
err = tasks.replicationSetConcurrency(concurrency) // no shadow
if err == nil {
tasks.concurrency = concurrency
}
} else {
// FIXME this is not great, should always be able to set it
err = errors.Errorf("cannot set while not replicating")
}
})
return err
}
func (j *ActiveSide) Run(ctx context.Context) {
log := GetLogger(ctx)
ctx = logging.WithSubsystemLoggers(ctx, log)
@@ -436,11 +457,14 @@ func (j *ActiveSide) do(ctx context.Context) {
ctx, repCancel := context.WithCancel(ctx)
var repWait driver.WaitFunc
j.updateTasks(func(tasks *activeSideTasks) {
// reset it
*tasks = activeSideTasks{}
// reset it (almost)
old := *tasks
*tasks = activeSideTasks{
concurrency: old.concurrency,
}
tasks.replicationCancel = repCancel
tasks.replicationReport, repWait = replication.Do(
ctx, logic.NewPlanner(j.promRepStateSecs, j.promBytesReplicated, sender, receiver, j.mode.PlannerPolicy()),
tasks.replicationReport, repWait, tasks.replicationSetConcurrency = replication.Do(
ctx, tasks.concurrency, logic.NewPlanner(j.promRepStateSecs, j.promBytesReplicated, sender, receiver, j.mode.PlannerPolicy()),
)
tasks.state = ActiveSideReplicating
})
+1
View File
@@ -40,6 +40,7 @@ type Job interface {
// must return the root of that subtree as rfs and ok = true
OwnedDatasetSubtreeRoot() (rfs *zfs.DatasetPath, ok bool)
SenderConfig() *endpoint.SenderConfig
SetConcurrency(concurrency int) error
}
type Type string
+2
View File
@@ -163,6 +163,8 @@ func (j *PassiveSide) SenderConfig() *endpoint.SenderConfig {
func (*PassiveSide) RegisterMetrics(registerer prometheus.Registerer) {}
func (*PassiveSide) SetConcurrency(concurrency int) error { return errors.Errorf("not supported") }
func (j *PassiveSide) Run(ctx context.Context) {
log := GetLogger(ctx)
+2
View File
@@ -117,6 +117,8 @@ outer:
}
}
func (*SnapJob) SetConcurrency(concurrency int) error { return errors.Errorf("not supported") }
// Adaptor that implements pruner.History around a pruner.Target.
// The ReplicationCursor method is Get-op only and always returns
// the filesystem's most recent version's GUID.
+3
View File
@@ -5,6 +5,7 @@ import (
"net"
"net/http"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
@@ -51,6 +52,8 @@ func (j *prometheusJob) OwnedDatasetSubtreeRoot() (p *zfs.DatasetPath, ok bool)
func (j *prometheusJob) SenderConfig() *endpoint.SenderConfig { return nil }
func (j *prometheusJob) SetConcurrency(concurrency int) error { return errors.Errorf("not supported") }
func (j *prometheusJob) RegisterMetrics(registerer prometheus.Registerer) {}
func (j *prometheusJob) Run(ctx context.Context) {