github.com/m3db/m3@v1.5.0/src/dbnode/persist/fs/commitlog/reader.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  	"bytes"
    25  	"encoding/binary"
    26  	"errors"
    27  	"io"
    28  	"os"
    29  
    30  	"github.com/m3db/m3/src/dbnode/persist/fs/msgpack"
    31  	"github.com/m3db/m3/src/dbnode/persist/schema"
    32  	"github.com/m3db/m3/src/dbnode/ts"
    33  	"github.com/m3db/m3/src/x/checked"
    34  	"github.com/m3db/m3/src/x/ident"
    35  	"github.com/m3db/m3/src/x/pool"
    36  	"github.com/m3db/m3/src/x/serialize"
    37  	xtime "github.com/m3db/m3/src/x/time"
    38  
    39  	"go.uber.org/atomic"
    40  )
    41  
    42  var (
    43  	commitLogFileReadCounter = atomic.NewUint64(0)
    44  
    45  	// var instead of const so we can modify them in tests.
    46  	decoderInBufChanSize  = 1000
    47  	decoderOutBufChanSize = 1000
    48  )
    49  
    50  var (
    51  	emptyLogInfo schema.LogInfo
    52  
    53  	errCommitLogReaderChunkSizeChecksumMismatch = errors.New("commit log reader encountered chunk size checksum mismatch")
    54  	errCommitLogReaderIsNotReusable             = errors.New("commit log reader is not reusable")
    55  	errCommitLogReaderMissingMetadata           = errors.New("commit log reader encountered a datapoint without corresponding metadata")
    56  )
    57  
    58  // Reader reads a commit log file.
    59  type Reader interface {
    60  	// Open opens the commit log for reading
    61  	Open(filePath string) (int64, error)
    62  
    63  	// Read returns the next id and data pair or error, will return io.EOF at end of volume
    64  	Read() (LogEntry, error)
    65  
    66  	// Close the reader
    67  	Close() error
    68  }
    69  
    70  type reader struct {
    71  	opts ReaderOptions
    72  
    73  	logEntryBytes          []byte
    74  	tagDecoder             serialize.TagDecoder
    75  	tagDecoderCheckedBytes checked.Bytes
    76  	checkedBytesPool       pool.CheckedBytesPool
    77  	chunkReader            *chunkReader
    78  	infoDecoder            *msgpack.Decoder
    79  	infoDecoderStream      msgpack.ByteDecoderStream
    80  	hasBeenOpened          bool
    81  	fileReadID             uint64
    82  
    83  	metadataLookup map[uint64]ts.Series
    84  	namespacesRead []namespaceRead
    85  	seriesIDReused *ident.ReusableBytesID
    86  }
    87  
    88  type namespaceRead struct {
    89  	namespaceIDBytes []byte
    90  	namespaceIDRef   ident.ID
    91  }
    92  
    93  // ReaderOptions are the options for Reader.
    94  type ReaderOptions struct {
    95  	commitLogOptions Options
    96  	// returnMetadataAsRef indicates to not allocate metadata results.
    97  	returnMetadataAsRef bool
    98  }
    99  
   100  // NewReaderOptions returns new ReaderOptions.
   101  func NewReaderOptions(opts Options, returnMetadataAsRef bool) ReaderOptions {
   102  	return ReaderOptions{
   103  		commitLogOptions:    opts,
   104  		returnMetadataAsRef: returnMetadataAsRef,
   105  	}
   106  }
   107  
   108  // NewReader returns a new Reader.
   109  func NewReader(opts ReaderOptions) Reader {
   110  	tagDecoderCheckedBytes := checked.NewBytes(nil, nil)
   111  	tagDecoderCheckedBytes.IncRef()
   112  	return &reader{
   113  		opts:                   opts,
   114  		logEntryBytes:          make([]byte, 0, opts.commitLogOptions.FlushSize()),
   115  		metadataLookup:         make(map[uint64]ts.Series),
   116  		tagDecoder:             opts.commitLogOptions.FilesystemOptions().TagDecoderPool().Get(),
   117  		tagDecoderCheckedBytes: tagDecoderCheckedBytes,
   118  		checkedBytesPool:       opts.commitLogOptions.BytesPool(),
   119  		chunkReader:            newChunkReader(opts.commitLogOptions.FlushSize()),
   120  		infoDecoder:            msgpack.NewDecoder(opts.commitLogOptions.FilesystemOptions().DecodingOptions()),
   121  		infoDecoderStream:      msgpack.NewByteDecoderStream(nil),
   122  		seriesIDReused:         ident.NewReusableBytesID(),
   123  	}
   124  }
   125  
   126  func (r *reader) Open(filePath string) (int64, error) {
   127  	// Commitlog reader does not currently support being reused.
   128  	if r.hasBeenOpened {
   129  		return 0, errCommitLogReaderIsNotReusable
   130  	}
   131  	r.hasBeenOpened = true
   132  
   133  	fd, err := os.Open(filePath)
   134  	if err != nil {
   135  		return 0, err
   136  	}
   137  
   138  	r.chunkReader.reset(fd)
   139  	info, err := r.readInfo()
   140  	if err != nil {
   141  		r.Close()
   142  		return 0, err
   143  	}
   144  
   145  	r.fileReadID = commitLogFileReadCounter.Inc()
   146  
   147  	index := info.Index
   148  	return index, nil
   149  }
   150  
   151  func (r *reader) readInfo() (schema.LogInfo, error) {
   152  	err := r.readLogEntry()
   153  	if err != nil {
   154  		return emptyLogInfo, err
   155  	}
   156  	r.infoDecoderStream.Reset(r.logEntryBytes)
   157  	r.infoDecoder.Reset(r.infoDecoderStream)
   158  	return r.infoDecoder.DecodeLogInfo()
   159  }
   160  
   161  // Read reads the next log entry in order.
   162  func (r *reader) Read() (LogEntry, error) {
   163  	err := r.readLogEntry()
   164  	if err != nil {
   165  		return LogEntry{}, err
   166  	}
   167  
   168  	entry, err := msgpack.DecodeLogEntryFast(r.logEntryBytes)
   169  	if err != nil {
   170  		return LogEntry{}, err
   171  	}
   172  
   173  	metadata, err := r.seriesMetadataForEntry(entry.Index, entry.Metadata)
   174  	if err != nil {
   175  		return LogEntry{}, err
   176  	}
   177  
   178  	result := LogEntry{
   179  		Series: metadata,
   180  		Datapoint: ts.Datapoint{
   181  			TimestampNanos: xtime.UnixNano(entry.Timestamp),
   182  			Value:          entry.Value,
   183  		},
   184  		Unit:       xtime.Unit(entry.Unit),
   185  		Annotation: entry.Annotation,
   186  		Metadata: LogEntryMetadata{
   187  			FileReadID:        r.fileReadID,
   188  			SeriesUniqueIndex: entry.Index,
   189  		},
   190  	}
   191  
   192  	return result, nil
   193  }
   194  
   195  func (r *reader) readLogEntry() error {
   196  	// Read size of message
   197  	size, err := binary.ReadUvarint(r.chunkReader)
   198  	if err != nil {
   199  		return err
   200  	}
   201  
   202  	// Extend buffer as necessary
   203  	r.logEntryBytes = resizeBufferOrGrowIfNeeded(r.logEntryBytes, int(size))
   204  
   205  	// Read message
   206  	if _, err := io.ReadFull(r.chunkReader, r.logEntryBytes); err != nil {
   207  		return err
   208  	}
   209  
   210  	return nil
   211  }
   212  
   213  func (r *reader) namespaceIDReused(id []byte) ident.ID {
   214  	var namespaceID ident.ID
   215  	for _, ns := range r.namespacesRead {
   216  		if bytes.Equal(ns.namespaceIDBytes, id) {
   217  			namespaceID = ns.namespaceIDRef
   218  			break
   219  		}
   220  	}
   221  	if namespaceID == nil {
   222  		// Take a copy and keep around to reuse later.
   223  		namespaceBytes := append(make([]byte, 0, len(id)), id...)
   224  		namespaceID = ident.BytesID(namespaceBytes)
   225  		r.namespacesRead = append(r.namespacesRead, namespaceRead{
   226  			namespaceIDBytes: namespaceBytes,
   227  			namespaceIDRef:   namespaceID,
   228  		})
   229  	}
   230  	return namespaceID
   231  }
   232  
   233  func (r *reader) seriesMetadataForEntry(
   234  	entryIndex uint64,
   235  	metadataBytes []byte,
   236  ) (ts.Series, error) {
   237  	if r.opts.returnMetadataAsRef {
   238  		// NB(r): This is a fast path for callers where nothing
   239  		// is cached locally in terms of metadata lookup and the
   240  		// caller is returned just references to all the bytes in
   241  		// the backing commit log file the first and only time
   242  		// we encounter the series metadata, and then the refs are
   243  		// invalid on the next call to metadata.
   244  		if len(metadataBytes) == 0 {
   245  			// Valid, nothing to return here and caller will already
   246  			// have processed metadata for this entry (based on the
   247  			// FileReadID and the SeriesUniqueIndex returned).
   248  			return ts.Series{}, nil
   249  		}
   250  
   251  		decoded, err := msgpack.DecodeLogMetadataFast(metadataBytes)
   252  		if err != nil {
   253  			return ts.Series{}, err
   254  		}
   255  
   256  		// Reset the series ID being returned.
   257  		r.seriesIDReused.Reset(decoded.ID)
   258  		// Find or allocate the namespace ID.
   259  		namespaceID := r.namespaceIDReused(decoded.Namespace)
   260  		metadata := ts.Series{
   261  			UniqueIndex: entryIndex,
   262  			ID:          r.seriesIDReused,
   263  			Namespace:   namespaceID,
   264  			Shard:       decoded.Shard,
   265  			EncodedTags: ts.EncodedTags(decoded.EncodedTags),
   266  		}
   267  		return metadata, nil
   268  	}
   269  
   270  	// We only check for previously returned metadata
   271  	// if we're allocating results and can hold onto them.
   272  	metadata, ok := r.metadataLookup[entryIndex]
   273  	if ok {
   274  		// If the metadata already exists, we can skip this step.
   275  		return metadata, nil
   276  	}
   277  
   278  	if len(metadataBytes) == 0 {
   279  		// Expected metadata but not encoded.
   280  		return ts.Series{}, errCommitLogReaderMissingMetadata
   281  	}
   282  
   283  	decoded, err := msgpack.DecodeLogMetadataFast(metadataBytes)
   284  	if err != nil {
   285  		return ts.Series{}, err
   286  	}
   287  
   288  	id := r.checkedBytesPool.Get(len(decoded.ID))
   289  	id.IncRef()
   290  	id.AppendAll(decoded.ID)
   291  
   292  	// Find or allocate the namespace ID.
   293  	namespaceID := r.namespaceIDReused(decoded.Namespace)
   294  
   295  	// Need to copy encoded tags since will be invalid when
   296  	// progressing to next record.
   297  	encodedTags := append(
   298  		make([]byte, 0, len(decoded.EncodedTags)),
   299  		decoded.EncodedTags...)
   300  
   301  	idPool := r.opts.commitLogOptions.IdentifierPool()
   302  	metadata = ts.Series{
   303  		UniqueIndex: entryIndex,
   304  		ID:          idPool.BinaryID(id),
   305  		Namespace:   namespaceID,
   306  		Shard:       decoded.Shard,
   307  		EncodedTags: encodedTags,
   308  	}
   309  
   310  	r.metadataLookup[entryIndex] = metadata
   311  
   312  	id.DecRef()
   313  
   314  	return metadata, nil
   315  }
   316  
   317  func (r *reader) Close() error {
   318  	err := r.chunkReader.fd.Close()
   319  	// NB(r): Reset to free resources, but explicitly do
   320  	// not support reopening for now.
   321  	*r = reader{}
   322  	r.hasBeenOpened = true
   323  	return err
   324  }
   325  
   326  func resizeBufferOrGrowIfNeeded(buf []byte, length int) []byte {
   327  	if cap(buf) >= length {
   328  		return buf[:length]
   329  	}
   330  
   331  	// If double is less than length requested, return that.
   332  	var newCap int
   333  	for newCap = 2 * cap(buf); newCap < length; newCap *= 2 {
   334  		// Double it again.
   335  	}
   336  
   337  	return make([]byte, length, newCap)
   338  }