github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/fs.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 "sync" 25 26 "github.com/m3db/m3/src/dbnode/persist/fs/commitlog" 27 "github.com/m3db/m3/src/x/instrument" 28 xtime "github.com/m3db/m3/src/x/time" 29 30 "go.uber.org/zap" 31 ) 32 33 type fileOpStatus int 34 35 const ( 36 fileOpNotStarted fileOpStatus = iota 37 fileOpInProgress 38 fileOpSuccess 39 fileOpFailed 40 ) 41 42 type warmStatus struct { 43 DataFlushed fileOpStatus 44 IndexFlushed fileOpStatus 45 } 46 47 type fileOpState struct { 48 // WarmStatus is the status of data persistence for WarmWrites only. 49 // Each block will only be warm-flushed once, so not keeping track of a 50 // version here is okay. This is used in the buffer Tick to determine when 51 // a warm bucket is evictable from memory. 52 WarmStatus warmStatus 53 // ColdVersionRetrievable keeps track of data persistence for ColdWrites only. 54 // Each block can be cold-flushed multiple times, so this tracks which 55 // version of the flush completed successfully. This is ultimately used in 56 // the buffer Tick to determine which buckets are evictable. Note the distinction 57 // between ColdVersionRetrievable and ColdVersionFlushed described below. 58 ColdVersionRetrievable int 59 // ColdVersionFlushed is the same as ColdVersionRetrievable except in some cases it will be 60 // higher than ColdVersionRetrievable. ColdVersionFlushed will be higher than 61 // ColdVersionRetrievable in the situation that a cold flush has completed successfully but 62 // signaling the update to the BlockLeaseManager hasn't completed yet. As a result, the 63 // ColdVersionRetrievable can't be incremented yet because a concurrent tick could evict the 64 // block before it becomes queryable via the block retriever / seeker manager, however, the 65 // BlockLeaseVerifier needs to know that a higher cold flush version exists on disk so that 66 // it can approve the SeekerManager's request to open a lease on the latest version. 67 // 68 // In other words ColdVersionRetrievable is used to keep track of the latest cold version that has 69 // been successfully flushed and can be queried via the block retriever / seeker manager and 70 // as a result is safe to evict, while ColdVersionFlushed is used to keep track of the latest 71 // cold version that has been flushed and to validate lease requests from the SeekerManager when it 72 // receives a signal to open a new lease. 73 ColdVersionFlushed int 74 NumFailures int 75 } 76 77 type forceType int 78 79 const ( 80 noForce forceType = iota 81 force 82 ) 83 84 type fileSystemManager struct { 85 databaseFlushManager 86 databaseCleanupManager 87 sync.RWMutex 88 89 log *zap.Logger 90 database database 91 opts Options 92 status fileOpStatus 93 enabled bool 94 } 95 96 func newFileSystemManager( 97 database database, 98 commitLog commitlog.CommitLog, 99 opts Options, 100 ) databaseFileSystemManager { 101 instrumentOpts := opts.InstrumentOptions() 102 scope := instrumentOpts.MetricsScope().SubScope("fs") 103 fm := newFlushManager(database, commitLog, scope) 104 cm := newCleanupManager(database, commitLog, scope) 105 106 return &fileSystemManager{ 107 databaseFlushManager: fm, 108 databaseCleanupManager: cm, 109 log: instrumentOpts.Logger(), 110 database: database, 111 opts: opts, 112 status: fileOpNotStarted, 113 enabled: true, 114 } 115 } 116 117 func (m *fileSystemManager) Disable() fileOpStatus { 118 m.Lock() 119 status := m.status 120 m.enabled = false 121 m.Unlock() 122 return status 123 } 124 125 func (m *fileSystemManager) Enable() fileOpStatus { 126 m.Lock() 127 status := m.status 128 m.enabled = true 129 m.Unlock() 130 return status 131 } 132 133 func (m *fileSystemManager) Status() fileOpStatus { 134 m.RLock() 135 status := m.status 136 m.RUnlock() 137 return status 138 } 139 140 func (m *fileSystemManager) Run(t xtime.UnixNano) bool { 141 m.Lock() 142 if !m.shouldRunWithLock() { 143 m.Unlock() 144 return false 145 } 146 m.status = fileOpInProgress 147 m.Unlock() 148 149 defer func() { 150 m.Lock() 151 m.status = fileOpNotStarted 152 m.Unlock() 153 }() 154 155 m.log.Debug("starting warm flush", zap.Time("time", t.ToTime())) 156 157 // NB(xichen): perform data cleanup and flushing sequentially to minimize the impact of disk seeks. 158 if err := m.WarmFlushCleanup(t); err != nil { 159 // NB(r): Use invariant here since flush errors were introduced 160 // and not caught in CI or integration tests. 161 // When an invariant occurs in CI tests it panics so as to fail 162 // the build. 163 instrument.EmitAndLogInvariantViolation(m.opts.InstrumentOptions(), 164 func(l *zap.Logger) { 165 l.Error("error when cleaning up data", zap.Time("time", t.ToTime()), zap.Error(err)) 166 }) 167 } 168 if err := m.Flush(t); err != nil { 169 instrument.EmitAndLogInvariantViolation(m.opts.InstrumentOptions(), 170 func(l *zap.Logger) { 171 l.Error("error when flushing data", zap.Time("time", t.ToTime()), zap.Error(err)) 172 }) 173 } 174 m.log.Debug("completed warm flush", zap.Time("time", t.ToTime())) 175 176 return true 177 } 178 179 func (m *fileSystemManager) Report() { 180 m.databaseCleanupManager.Report() 181 m.databaseFlushManager.Report() 182 } 183 184 func (m *fileSystemManager) shouldRunWithLock() bool { 185 return m.enabled && m.status != fileOpInProgress && m.database.IsBootstrapped() 186 }