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 }