github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/persist/fs/merger.go (about) 1 // Copyright (c) 2019 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 fs 22 23 import ( 24 "errors" 25 "io" 26 "time" 27 28 "github.com/m3db/m3/src/dbnode/encoding" 29 "github.com/m3db/m3/src/dbnode/namespace" 30 "github.com/m3db/m3/src/dbnode/persist" 31 "github.com/m3db/m3/src/dbnode/storage/block" 32 "github.com/m3db/m3/src/dbnode/ts" 33 "github.com/m3db/m3/src/dbnode/x/xio" 34 "github.com/m3db/m3/src/m3ninx/doc" 35 "github.com/m3db/m3/src/x/checked" 36 "github.com/m3db/m3/src/x/context" 37 "github.com/m3db/m3/src/x/ident" 38 xtime "github.com/m3db/m3/src/x/time" 39 ) 40 41 var errMergeAndCleanupNotSupported = errors.New("function MergeAndCleanup not supported outside of bootstrapping") 42 43 type merger struct { 44 reader DataFileSetReader 45 blockAllocSize int 46 srPool xio.SegmentReaderPool 47 multiIterPool encoding.MultiReaderIteratorPool 48 identPool ident.Pool 49 encoderPool encoding.EncoderPool 50 contextPool context.Pool 51 nsOpts namespace.Options 52 filePathPrefix string 53 } 54 55 // NewMerger returns a new Merger. This implementation is in charge of merging 56 // the data from an existing fileset with a merge target. If data for a series 57 // at a timestamp exists both on disk and the merge target, data from the merge 58 // target will be used. This merged data is then persisted. 59 // 60 // Note that the merger does not know how or where this merged data is 61 // persisted since it just uses the flushPreparer that is passed in. Further, 62 // it does not signal to the database of the existence of the newly persisted 63 // data, nor does it clean up the original fileset. 64 func NewMerger( 65 reader DataFileSetReader, 66 blockAllocSize int, 67 srPool xio.SegmentReaderPool, 68 multiIterPool encoding.MultiReaderIteratorPool, 69 identPool ident.Pool, 70 encoderPool encoding.EncoderPool, 71 contextPool context.Pool, 72 filePathPrefix string, 73 nsOpts namespace.Options, 74 ) Merger { 75 return &merger{ 76 reader: reader, 77 blockAllocSize: blockAllocSize, 78 srPool: srPool, 79 multiIterPool: multiIterPool, 80 identPool: identPool, 81 encoderPool: encoderPool, 82 contextPool: contextPool, 83 nsOpts: nsOpts, 84 filePathPrefix: filePathPrefix, 85 } 86 } 87 88 // Merge merges data from a fileset with a merge target and persists it. 89 // The caller is responsible for finalizing all resources used for the 90 // MergeWith passed here. 91 func (m *merger) Merge( 92 fileID FileSetFileIdentifier, 93 mergeWith MergeWith, 94 nextVolumeIndex int, 95 flushPreparer persist.FlushPreparer, 96 nsCtx namespace.Context, 97 onFlush persist.OnFlushSeries, 98 ) (persist.DataCloser, error) { 99 var ( 100 reader = m.reader 101 blockAllocSize = m.blockAllocSize 102 srPool = m.srPool 103 multiIterPool = m.multiIterPool 104 encoderPool = m.encoderPool 105 nsOpts = m.nsOpts 106 107 nsID = fileID.Namespace 108 shard = fileID.Shard 109 startTime = fileID.BlockStart 110 volume = fileID.VolumeIndex 111 blockSize = nsOpts.RetentionOptions().BlockSize() 112 blockStart = startTime 113 openOpts = DataReaderOpenOptions{ 114 Identifier: FileSetFileIdentifier{ 115 Namespace: nsID, 116 Shard: shard, 117 BlockStart: startTime, 118 VolumeIndex: volume, 119 }, 120 FileSetType: persist.FileSetFlushType, 121 } 122 closer persist.DataCloser 123 err error 124 ) 125 126 if err := reader.Open(openOpts); err != nil { 127 return closer, err 128 } 129 defer reader.Close() // nolint 130 131 nsMd, err := namespace.NewMetadata(nsID, nsOpts) 132 if err != nil { 133 return closer, err 134 } 135 prepareOpts := persist.DataPrepareOptions{ 136 NamespaceMetadata: nsMd, 137 Shard: shard, 138 BlockStart: startTime, 139 VolumeIndex: nextVolumeIndex, 140 FileSetType: persist.FileSetFlushType, 141 DeleteIfExists: false, 142 } 143 prepared, err := flushPreparer.PrepareData(prepareOpts) 144 if err != nil { 145 return closer, err 146 } 147 148 var ( 149 // There will likely be at least two SegmentReaders - one for disk data and 150 // one for data from the merge target. 151 segmentReaders = make([]xio.SegmentReader, 0, 2) 152 153 // It's safe to share these between iterations and just reset them each 154 // time because the series gets persisted each loop, so the previous 155 // iterations' reader and iterator will never be needed. 156 segReader = srPool.Get() 157 multiIter = multiIterPool.Get() 158 ctx = m.contextPool.Get() 159 160 // Shared between iterations. 161 iterResources = newIterResources( 162 multiIter, 163 blockStart, 164 blockSize, 165 blockAllocSize, 166 nsCtx.Schema, 167 encoderPool) 168 ) 169 defer func() { 170 segReader.Finalize() 171 multiIter.Close() 172 }() 173 174 // The merge is performed in two stages. The first stage is to loop through 175 // series on disk and merge it with what's in the merge target. Looping 176 // through disk in the first stage is prepared intentionally to read disk 177 // sequentially to optimize for spinning disk access. The second stage is to 178 // persist the rest of the series in the merge target that were not 179 // persisted in the first stage. 180 181 // First stage: loop through series on disk. 182 for id, tagsIter, data, checksum, err := reader.Read(); err != io.EOF; id, tagsIter, data, checksum, err = reader.Read() { 183 if err != nil { 184 return closer, err 185 } 186 187 segmentReaders = segmentReaders[:0] 188 seg := segmentReaderFromData(data, checksum, segReader) 189 segmentReaders = append(segmentReaders, seg) 190 191 // Check if this series is in memory (and thus requires merging). 192 ctx.Reset() 193 mergeWithData, hasInMemoryData, err := mergeWith.Read(ctx, id, blockStart, nsCtx) 194 if err != nil { 195 return closer, err 196 } 197 if hasInMemoryData { 198 segmentReaders = appendBlockReadersToSegmentReaders(segmentReaders, mergeWithData) 199 } 200 201 // Inform the writer to finalize the ID and tag iterator once 202 // the volume is written. 203 metadata := persist.NewMetadataFromIDAndTagIterator(id, tagsIter, 204 persist.MetadataOptions{ 205 FinalizeID: true, 206 FinalizeTagIterator: true, 207 }) 208 209 // In the special (but common) case that we're just copying the series data from the old file 210 // into the new one without merging or adding any additional data we can avoid recalculating 211 // the checksum. 212 if len(segmentReaders) == 1 && hasInMemoryData == false { 213 segment, err := segmentReaders[0].Segment() 214 if err != nil { 215 return closer, err 216 } 217 218 if err := persistSegmentWithChecksum(metadata, segment, checksum, prepared.Persist); err != nil { 219 return closer, err 220 } 221 } else { 222 if err := persistSegmentReaders(metadata, segmentReaders, iterResources, prepared.Persist); err != nil { 223 return closer, err 224 } 225 } 226 // Closing the context will finalize the data returned from 227 // mergeWith.Read(), but is safe because it has already been persisted 228 // to disk. 229 // NB(r): Make sure to use BlockingCloseReset so can reuse the context. 230 ctx.BlockingCloseReset() 231 } 232 // Second stage: loop through any series in the merge target that were not 233 // captured in the first stage. 234 ctx.Reset() 235 err = mergeWith.ForEachRemaining( 236 ctx, blockStart, 237 func(seriesMetadata doc.Metadata, mergeWithData block.FetchBlockResult) error { 238 segmentReaders = segmentReaders[:0] 239 segmentReaders = appendBlockReadersToSegmentReaders(segmentReaders, mergeWithData.Blocks) 240 241 metadata := persist.NewMetadata(seriesMetadata) 242 err := persistSegmentReaders(metadata, segmentReaders, iterResources, prepared.Persist) 243 244 if err == nil { 245 err = onFlush.OnFlushNewSeries(persist.OnFlushNewSeriesEvent{ 246 Shard: shard, 247 BlockStart: startTime, 248 FirstWrite: mergeWithData.FirstWrite, 249 SeriesMetadata: persist.SeriesMetadata{ 250 Type: persist.SeriesDocumentType, 251 Document: seriesMetadata, 252 // The lifetime of the shard series metadata is longly lived. 253 LifeTime: persist.SeriesLifeTimeLong, 254 }, 255 }) 256 } 257 258 // Context is safe to close after persisting data to disk. 259 // Reset context here within the passed in function so that the 260 // context gets reset for each remaining series instead of getting 261 // finalized at the end of the ForEachRemaining call. 262 // NB(r): Make sure to use BlockingCloseReset so can reuse the context. 263 ctx.BlockingCloseReset() 264 return err 265 }, nsCtx) 266 if err != nil { 267 return closer, err 268 } 269 270 // NB(bodu): Return a deferred closer so that we can guarantee that cold index writes are persisted first. 271 return prepared.DeferClose() 272 } 273 274 func (m *merger) MergeAndCleanup( 275 fileID FileSetFileIdentifier, 276 mergeWith MergeWith, 277 nextVolumeIndex int, 278 flushPreparer persist.FlushPreparer, 279 nsCtx namespace.Context, 280 onFlush persist.OnFlushSeries, 281 isBootstrapped bool, 282 ) error { 283 if isBootstrapped { 284 return errMergeAndCleanupNotSupported 285 } 286 287 close, err := m.Merge(fileID, mergeWith, nextVolumeIndex, flushPreparer, nsCtx, onFlush) 288 if err != nil { 289 return err 290 } 291 292 if err = close(); err != nil { 293 return err 294 } 295 296 return DeleteFileSetAt(m.filePathPrefix, fileID.Namespace, fileID.Shard, fileID.BlockStart, fileID.VolumeIndex) 297 } 298 299 func appendBlockReadersToSegmentReaders(segReaders []xio.SegmentReader, brs []xio.BlockReader) []xio.SegmentReader { 300 for _, br := range brs { 301 segReaders = append(segReaders, br.SegmentReader) 302 } 303 return segReaders 304 } 305 306 func segmentReaderFromData( 307 data checked.Bytes, 308 checksum uint32, 309 segReader xio.SegmentReader, 310 ) xio.SegmentReader { 311 seg := ts.NewSegment(data, nil, checksum, ts.FinalizeHead) 312 segReader.Reset(seg) 313 return segReader 314 } 315 316 func persistSegmentReaders( 317 metadata persist.Metadata, 318 segReaders []xio.SegmentReader, 319 ir iterResources, 320 persistFn persist.DataFn, 321 ) error { 322 if len(segReaders) == 0 { 323 return nil 324 } 325 326 if len(segReaders) == 1 { 327 return persistSegmentReader(metadata, segReaders[0], persistFn) 328 } 329 330 return persistIter(metadata, segReaders, ir, persistFn) 331 } 332 333 func persistIter( 334 metadata persist.Metadata, 335 segReaders []xio.SegmentReader, 336 ir iterResources, 337 persistFn persist.DataFn, 338 ) error { 339 it := ir.multiIter 340 it.Reset(segReaders, ir.blockStart, ir.blockSize, ir.schema) 341 encoder := ir.encoderPool.Get() 342 encoder.Reset(ir.blockStart, ir.blockAllocSize, ir.schema) 343 for it.Next() { 344 if err := encoder.Encode(it.Current()); err != nil { 345 encoder.Close() 346 return err 347 } 348 } 349 if err := it.Err(); err != nil { 350 encoder.Close() 351 return err 352 } 353 354 segment := encoder.Discard() 355 return persistSegment(metadata, segment, persistFn) 356 } 357 358 func persistSegmentReader( 359 metadata persist.Metadata, 360 segmentReader xio.SegmentReader, 361 persistFn persist.DataFn, 362 ) error { 363 segment, err := segmentReader.Segment() 364 if err != nil { 365 return err 366 } 367 return persistSegment(metadata, segment, persistFn) 368 } 369 370 func persistSegment( 371 metadata persist.Metadata, 372 segment ts.Segment, 373 persistFn persist.DataFn, 374 ) error { 375 checksum := segment.CalculateChecksum() 376 return persistFn(metadata, segment, checksum) 377 } 378 379 func persistSegmentWithChecksum( 380 metadata persist.Metadata, 381 segment ts.Segment, 382 checksum uint32, 383 persistFn persist.DataFn, 384 ) error { 385 return persistFn(metadata, segment, checksum) 386 } 387 388 type iterResources struct { 389 multiIter encoding.MultiReaderIterator 390 blockStart xtime.UnixNano 391 blockSize time.Duration 392 blockAllocSize int 393 schema namespace.SchemaDescr 394 encoderPool encoding.EncoderPool 395 } 396 397 func newIterResources( 398 multiIter encoding.MultiReaderIterator, 399 blockStart xtime.UnixNano, 400 blockSize time.Duration, 401 blockAllocSize int, 402 schema namespace.SchemaDescr, 403 encoderPool encoding.EncoderPool, 404 ) iterResources { 405 return iterResources{ 406 multiIter: multiIter, 407 blockStart: blockStart, 408 blockSize: blockSize, 409 blockAllocSize: blockAllocSize, 410 schema: schema, 411 encoderPool: encoderPool, 412 } 413 }