github.com/m3db/m3@v1.5.0/src/dbnode/persist/fs/commitlog/iterator.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 commitlog 22 23 import ( 24 "errors" 25 "fmt" 26 "io" 27 "os" 28 29 "github.com/m3db/m3/src/dbnode/persist" 30 31 "github.com/uber-go/tally" 32 "go.uber.org/zap" 33 ) 34 35 var ( 36 errIndexDoesNotMatch = errors.New("commit log file index does not match filename") 37 ) 38 39 type iteratorMetrics struct { 40 readsErrors tally.Counter 41 } 42 43 type iterator struct { 44 iterOpts IteratorOpts 45 opts Options 46 scope tally.Scope 47 metrics iteratorMetrics 48 log *zap.Logger 49 files []persist.CommitLogFile 50 reader Reader 51 read LogEntry 52 err error 53 setRead bool 54 closed bool 55 } 56 57 // ReadAllPredicate can be passed as the ReadCommitLogPredicate for callers 58 // that want a convenient way to read all the commitlogs 59 func ReadAllPredicate() FileFilterPredicate { 60 return func(f FileFilterInfo) bool { return true } 61 } 62 63 // NewIterator creates a new commit log iterator 64 func NewIterator(iterOpts IteratorOpts) (iter Iterator, corruptFiles []ErrorWithPath, err error) { 65 opts := iterOpts.CommitLogOptions 66 iops := opts.InstrumentOptions() 67 iops = iops.SetMetricsScope(iops.MetricsScope().SubScope("iterator")) 68 69 files, corruptFiles, err := Files(opts) 70 if err != nil { 71 return nil, nil, err 72 } 73 filteredFiles := filterFiles(files, iterOpts.FileFilterPredicate) 74 filteredCorruptFiles := filterCorruptFiles(corruptFiles, iterOpts.FileFilterPredicate) 75 76 scope := iops.MetricsScope() 77 log := iops.Logger() 78 log.Info("found commit log files to read", 79 zap.Int("fileCount", len(filteredFiles)), 80 zap.Strings("commitlog-files", commitLogFilesForLogging(filteredFiles))) 81 return &iterator{ 82 iterOpts: iterOpts, 83 opts: opts, 84 scope: scope, 85 metrics: iteratorMetrics{ 86 readsErrors: scope.Counter("reads.errors"), 87 }, 88 log: log, 89 files: filteredFiles, 90 }, filteredCorruptFiles, nil 91 } 92 93 func (i *iterator) Next() bool { 94 if i.hasError() || i.closed { 95 return false 96 } 97 if i.reader == nil { 98 if !i.nextReader() { 99 return false 100 } 101 } 102 var err error 103 i.read, err = i.reader.Read() 104 if err == io.EOF { 105 closeErr := i.closeAndResetReader() 106 if closeErr != nil { 107 i.err = closeErr 108 } 109 // Try the next reader 110 return i.Next() 111 } 112 if err != nil { 113 // Try the next reader, this enables restoring with best effort from commit logs 114 i.metrics.readsErrors.Inc(1) 115 i.log.Error("commit log reader returned error, iterator moving to next file", zap.Error(err)) 116 i.err = err 117 closeErr := i.closeAndResetReader() 118 if closeErr != nil { 119 i.err = closeErr 120 } 121 return i.Next() 122 } 123 i.setRead = true 124 return true 125 } 126 127 func (i *iterator) Current() LogEntry { 128 read := i.read 129 if i.hasError() || i.closed || !i.setRead { 130 read = LogEntry{} 131 } 132 return read 133 } 134 135 func (i *iterator) Err() error { 136 return i.err 137 } 138 139 // TODO: Refactor codebase so that it can handle Close() returning an error 140 func (i *iterator) Close() { 141 if i.closed { 142 return 143 } 144 i.closed = true 145 i.closeAndResetReader() 146 } 147 148 func (i *iterator) hasError() bool { 149 return i.err != nil 150 } 151 152 func (i *iterator) nextReader() bool { 153 if len(i.files) == 0 { 154 return false 155 } 156 157 err := i.closeAndResetReader() 158 if err != nil { 159 i.err = err 160 return false 161 } 162 163 file := i.files[0] 164 i.files = i.files[1:] 165 166 reader := NewReader(ReaderOptions{ 167 commitLogOptions: i.opts, 168 returnMetadataAsRef: i.iterOpts.ReturnMetadataAsRef, 169 }) 170 index, err := reader.Open(file.FilePath) 171 if err != nil { 172 i.err = err 173 return false 174 } 175 if index != file.Index { 176 i.err = errIndexDoesNotMatch 177 return false 178 } 179 180 i.log.Info("reading commit log file", zap.String("file", file.FilePath)) 181 i.reader = reader 182 return true 183 } 184 185 func filterFiles(files []persist.CommitLogFile, predicate FileFilterPredicate) []persist.CommitLogFile { 186 filtered := make([]persist.CommitLogFile, 0, len(files)) 187 for _, f := range files { 188 info := FileFilterInfo{File: f} 189 if predicate(info) { 190 filtered = append(filtered, f) 191 } 192 } 193 return filtered 194 } 195 196 func filterCorruptFiles(corruptFiles []ErrorWithPath, predicate FileFilterPredicate) []ErrorWithPath { 197 filtered := make([]ErrorWithPath, 0, len(corruptFiles)) 198 for _, errWithPath := range corruptFiles { 199 info := FileFilterInfo{ 200 Err: errWithPath, 201 IsCorrupt: true, 202 } 203 if predicate(info) { 204 filtered = append(filtered, errWithPath) 205 } 206 } 207 return filtered 208 } 209 210 func commitLogFilesForLogging(files []persist.CommitLogFile) []string { 211 result := make([]string, 0, len(files)) 212 for _, f := range files { 213 var fileSize int64 214 if fi, err := os.Stat(f.FilePath); err == nil { 215 fileSize = fi.Size() 216 } 217 result = append(result, fmt.Sprintf("path=%s, len=%d", f.FilePath, fileSize)) 218 } 219 return result 220 } 221 222 func (i *iterator) closeAndResetReader() error { 223 if i.reader == nil { 224 return nil 225 } 226 reader := i.reader 227 i.reader = nil 228 return reader.Close() 229 }