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 }