github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/tick.go (about)

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package storage
    22  
    23  import (
    24  	"errors"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/dbnode/runtime"
    29  	"github.com/m3db/m3/src/x/clock"
    30  	"github.com/m3db/m3/src/x/context"
    31  	xerrors "github.com/m3db/m3/src/x/errors"
    32  	xtime "github.com/m3db/m3/src/x/time"
    33  
    34  	"github.com/uber-go/tally"
    35  )
    36  
    37  const (
    38  	tokenCheckInterval = time.Second
    39  )
    40  
    41  var (
    42  	errEmptyNamespaces = errors.New("empty namespaces")
    43  	errTickInProgress  = errors.New("another tick is in progress")
    44  	errTickCancelled   = errors.New("tick is cancelled")
    45  )
    46  
    47  type tickManagerMetrics struct {
    48  	tickDuration       tally.Timer
    49  	tickWorkDuration   tally.Timer
    50  	tickCancelled      tally.Counter
    51  	tickDeadlineMissed tally.Counter
    52  	tickDeadlineMet    tally.Counter
    53  }
    54  
    55  func newTickManagerMetrics(scope tally.Scope) tickManagerMetrics {
    56  	return tickManagerMetrics{
    57  		tickDuration:       scope.Timer("duration"),
    58  		tickWorkDuration:   scope.Timer("work-duration"),
    59  		tickCancelled:      scope.Counter("cancelled"),
    60  		tickDeadlineMissed: scope.Counter("deadline.missed"),
    61  		tickDeadlineMet:    scope.Counter("deadline.met"),
    62  	}
    63  }
    64  
    65  type tickManager struct {
    66  	sync.Mutex
    67  	database database
    68  	opts     Options
    69  	nowFn    clock.NowFn
    70  	sleepFn  sleepFn
    71  
    72  	metrics tickManagerMetrics
    73  	c       context.Cancellable
    74  	tokenCh chan struct{}
    75  
    76  	runtimeOpts tickManagerRuntimeOptions
    77  }
    78  
    79  type tickManagerRuntimeOptions struct {
    80  	sync.RWMutex
    81  	vals tickManagerRuntimeOptionsValues
    82  }
    83  
    84  func (o *tickManagerRuntimeOptions) set(v tickManagerRuntimeOptionsValues) {
    85  	o.Lock()
    86  	o.vals = v
    87  	o.Unlock()
    88  }
    89  
    90  func (o *tickManagerRuntimeOptions) values() tickManagerRuntimeOptionsValues {
    91  	o.RLock()
    92  	v := o.vals
    93  	o.RUnlock()
    94  	return v
    95  }
    96  
    97  type tickManagerRuntimeOptionsValues struct {
    98  	tickMinInterval               time.Duration
    99  	tickCancellationCheckInterval time.Duration
   100  }
   101  
   102  func newTickManager(database database, opts Options) databaseTickManager {
   103  	scope := opts.InstrumentOptions().MetricsScope().SubScope("tick")
   104  	tokenCh := make(chan struct{}, 1)
   105  	tokenCh <- struct{}{}
   106  
   107  	mgr := &tickManager{
   108  		database: database,
   109  		opts:     opts,
   110  		nowFn:    opts.ClockOptions().NowFn(),
   111  		sleepFn:  time.Sleep,
   112  		metrics:  newTickManagerMetrics(scope),
   113  		c:        context.NewCancellable(),
   114  		tokenCh:  tokenCh,
   115  	}
   116  
   117  	runtimeOptsMgr := opts.RuntimeOptionsManager()
   118  	runtimeOptsMgr.RegisterListener(mgr)
   119  	return mgr
   120  }
   121  
   122  func (mgr *tickManager) SetRuntimeOptions(opts runtime.Options) {
   123  	mgr.runtimeOpts.set(tickManagerRuntimeOptionsValues{
   124  		tickMinInterval:               opts.TickMinimumInterval(),
   125  		tickCancellationCheckInterval: opts.TickCancellationCheckInterval(),
   126  	})
   127  }
   128  
   129  func (mgr *tickManager) Tick(forceType forceType, startTime xtime.UnixNano) error {
   130  	if forceType == force {
   131  		acquired := false
   132  		waiter := time.NewTicker(tokenCheckInterval)
   133  		// NB(xichen): cancellation is done in a loop so if there are multiple
   134  		// forced ticks, their cancellations don't get reset when token is acquired.
   135  		for !acquired {
   136  			select {
   137  			case <-mgr.tokenCh:
   138  				acquired = true
   139  			case <-waiter.C:
   140  				mgr.c.Cancel()
   141  			}
   142  		}
   143  		waiter.Stop()
   144  	} else {
   145  		select {
   146  		case <-mgr.tokenCh:
   147  		default:
   148  			return errTickInProgress
   149  		}
   150  	}
   151  
   152  	// Release the token
   153  	defer func() { mgr.tokenCh <- struct{}{} }()
   154  
   155  	// Now we acquired the token, reset the cancellable
   156  	mgr.c.Reset()
   157  	namespaces, err := mgr.database.OwnedNamespaces()
   158  	if err != nil {
   159  		return err
   160  	}
   161  	if len(namespaces) == 0 {
   162  		return errEmptyNamespaces
   163  	}
   164  
   165  	// Begin ticking
   166  	var (
   167  		start    = mgr.nowFn()
   168  		multiErr xerrors.MultiError
   169  	)
   170  	for _, n := range namespaces {
   171  		multiErr = multiErr.Add(n.Tick(mgr.c, startTime))
   172  	}
   173  
   174  	// NB(r): Always sleep for some constant period since ticking
   175  	// is variable with num series. With a really small amount of series
   176  	// the per shard amount of constant sleeping is only:
   177  	// = num shards * sleep per series (100 microseconds default)
   178  	// This number can be quite small if configuring to have small amount of
   179  	// shards (say 10 shards) with 32 series per shard (total of 320 series):
   180  	// = 10 num shards * ~32 series per shard * 100 microseconds default sleep per series
   181  	// = 10*32*0.1 milliseconds
   182  	// = 32ms
   183  	// Because of this we always sleep at least some fixed constant amount.
   184  	took := mgr.nowFn().Sub(start)
   185  	mgr.metrics.tickWorkDuration.Record(took)
   186  
   187  	vals := mgr.runtimeOpts.values()
   188  	min := vals.tickMinInterval
   189  
   190  	// Sleep in a loop so that cancellations propagate if need to
   191  	// wait to fulfill the tick min interval
   192  	interval := vals.tickCancellationCheckInterval
   193  	for d := time.Duration(0); d < min-took; d += interval {
   194  		if mgr.c.IsCancelled() {
   195  			break
   196  		}
   197  		mgr.sleepFn(interval)
   198  		// Check again at the end of each sleep to see if it
   199  		// has changed. Particularly useful for integration tests.
   200  		min = vals.tickMinInterval
   201  	}
   202  
   203  	end := mgr.nowFn()
   204  	duration := end.Sub(start)
   205  	mgr.metrics.tickDuration.Record(duration)
   206  
   207  	if mgr.c.IsCancelled() {
   208  		mgr.metrics.tickCancelled.Inc(1)
   209  		return errTickCancelled
   210  	}
   211  
   212  	return multiErr.FinalError()
   213  }