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  }