github.com/m3db/m3@v1.5.0/src/query/storage/m3/convert.go (about) 1 // Copyright (c) 2018 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 m3 22 23 import ( 24 "fmt" 25 "math" 26 "sort" 27 "time" 28 29 "github.com/m3db/m3/src/dbnode/encoding" 30 "github.com/m3db/m3/src/dbnode/x/xio" 31 "github.com/m3db/m3/src/query/block" 32 "github.com/m3db/m3/src/query/models" 33 "github.com/m3db/m3/src/query/storage/m3/consolidators" 34 "github.com/m3db/m3/src/x/ident" 35 "github.com/m3db/m3/src/x/pool" 36 xtime "github.com/m3db/m3/src/x/time" 37 ) 38 39 const ( 40 initBlockReplicaLength = 10 41 // This outputs time as 11:12:03AM 42 blockTimeFormat = "3:04:05PM" 43 ) 44 45 // blockReplica contains the replicas for a single m3db block. 46 type seriesBlock struct { 47 // internal start time for the block. 48 blockStart xtime.UnixNano 49 // time at which the first point in the block will appear. 50 readStart xtime.UnixNano 51 blockSize time.Duration 52 replicas []encoding.MultiReaderIterator 53 } 54 55 type seriesBlocks []seriesBlock 56 57 func (b seriesBlock) String() string { 58 return fmt.Sprint("BlockSize:", b.blockSize.Hours(), " blockStart:", 59 b.blockStart.Format(blockTimeFormat), " readStart:", 60 b.readStart.Format(blockTimeFormat), " num replicas", len(b.replicas)) 61 } 62 63 func (b seriesBlocks) Len() int { 64 return len(b) 65 } 66 67 func (b seriesBlocks) Swap(i, j int) { 68 b[i], b[j] = b[j], b[i] 69 } 70 71 func (b seriesBlocks) Less(i, j int) bool { 72 return b[i].blockStart.Before(b[j].blockStart) 73 } 74 75 func seriesIteratorsToEncodedBlockIterators( 76 result consolidators.SeriesFetchResult, 77 bounds models.Bounds, 78 opts Options, 79 ) ([]block.Block, error) { 80 bl, err := NewEncodedBlock(result, bounds, true, opts) 81 if err != nil { 82 return nil, err 83 } 84 85 return []block.Block{bl}, nil 86 } 87 88 // ConvertM3DBSeriesIterators converts series iterators to iterator blocks. If 89 // lookback is greater than 0, converts the entire series into a single block, 90 // otherwise, splits the series into blocks. 91 func ConvertM3DBSeriesIterators( 92 result consolidators.SeriesFetchResult, 93 bounds models.Bounds, 94 opts Options, 95 ) ([]block.Block, error) { 96 if err := opts.Validate(); err != nil { 97 return nil, err 98 } 99 100 if opts.SplittingSeriesByBlock() { 101 return convertM3DBSegmentedBlockIterators(result, bounds, opts) 102 } 103 104 return seriesIteratorsToEncodedBlockIterators(result, bounds, opts) 105 } 106 107 // convertM3DBSegmentedBlockIterators converts series iterators to a list of blocks 108 func convertM3DBSegmentedBlockIterators( 109 result consolidators.SeriesFetchResult, 110 bounds models.Bounds, 111 opts Options, 112 ) ([]block.Block, error) { 113 defer result.Close() 114 blockBuilder := newEncodedBlockBuilder(result, opts) 115 var ( 116 pools = opts.IteratorPools() 117 checkedPools = opts.CheckedBytesPool() 118 ) 119 120 count := result.Count() 121 for i := 0; i < count; i++ { 122 iter, tags, err := result.IterTagsAtIndex(i, opts.TagOptions()) 123 if err != nil { 124 return nil, err 125 } 126 127 blockReplicas, err := blockReplicasFromSeriesIterator( 128 iter, 129 bounds, 130 pools, 131 checkedPools, 132 ) 133 134 if err != nil { 135 return nil, err 136 } 137 138 blockReplicas = updateSeriesBlockStarts( 139 blockReplicas, 140 bounds.StepSize, 141 iter.Start(), 142 ) 143 144 err = seriesBlocksFromBlockReplicas( 145 blockBuilder, 146 tags, 147 result.Metadata, 148 blockReplicas, 149 bounds.StepSize, 150 iter, 151 pools, 152 ) 153 154 if err != nil { 155 return nil, err 156 } 157 } 158 159 return blockBuilder.build() 160 } 161 162 func blockReplicasFromSeriesIterator( 163 seriesIterator encoding.SeriesIterator, 164 bounds models.Bounds, 165 pools encoding.IteratorPools, 166 checkedPools pool.CheckedBytesPool, 167 ) (seriesBlocks, error) { 168 blocks := make(seriesBlocks, 0, bounds.Steps()) 169 pool := pools.MultiReaderIterator() 170 171 replicas, err := seriesIterator.Replicas() 172 if err != nil { 173 return nil, err 174 } 175 176 for _, replica := range replicas { 177 perBlockSliceReaders := replica.Readers() 178 for next := true; next; next = perBlockSliceReaders.Next() { 179 l, start, bs := perBlockSliceReaders.CurrentReaders() 180 readers := make([]xio.SegmentReader, l) 181 for i := 0; i < l; i++ { 182 reader := perBlockSliceReaders.CurrentReaderAt(i) 183 // NB(braskin): important to clone the reader as we need its position reset before 184 // we use the contents of it again 185 clonedReader, err := reader.Clone(checkedPools) 186 if err != nil { 187 return nil, err 188 } 189 190 readers[i] = clonedReader 191 } 192 193 iter := pool.Get() 194 // TODO [haijun] query assumes schemaless iterators. 195 iter.Reset(readers, start, bs, nil) 196 inserted := false 197 for _, bl := range blocks { 198 if bl.blockStart == start { 199 inserted = true 200 bl.replicas = append(bl.replicas, iter) 201 break 202 } 203 } 204 205 if !inserted { 206 blocks = append(blocks, seriesBlock{ 207 blockStart: start, 208 blockSize: bs, 209 replicas: []encoding.MultiReaderIterator{iter}, 210 }) 211 } 212 } 213 } 214 215 // sort series blocks by start time 216 sort.Sort(blocks) 217 return blocks, nil 218 } 219 220 func blockDuration(blockSize, stepSize time.Duration) time.Duration { 221 numSteps := math.Ceil(float64(blockSize) / float64(stepSize)) 222 return stepSize * time.Duration(numSteps) 223 } 224 225 // calculates duration required to fill the gap of fillSize in stepSize sized 226 // increments. 227 func calculateFillDuration(fillSize, stepSize time.Duration) time.Duration { 228 numberToFill := int(fillSize / stepSize) 229 if fillSize%stepSize != 0 { 230 numberToFill++ 231 } 232 233 return stepSize * time.Duration(numberToFill) 234 } 235 236 // pads series blocks. 237 func updateSeriesBlockStarts( 238 blocks seriesBlocks, 239 stepSize time.Duration, 240 iterStart xtime.UnixNano, 241 ) seriesBlocks { 242 if len(blocks) == 0 { 243 return blocks 244 } 245 246 firstStart := blocks[0].blockStart 247 if iterStart.Before(firstStart) { 248 fillSize := firstStart.Sub(iterStart) 249 iterStart = iterStart.Add(calculateFillDuration(fillSize, stepSize)) 250 } 251 252 // Update read starts for existing blocks. 253 for i, bl := range blocks { 254 blocks[i].readStart = iterStart 255 fillSize := bl.blockStart.Add(bl.blockSize).Sub(iterStart) 256 iterStart = iterStart.Add(calculateFillDuration(fillSize, stepSize)) 257 } 258 259 return blocks 260 } 261 262 func seriesBlocksFromBlockReplicas( 263 blockBuilder *encodedBlockBuilder, 264 tags models.Tags, 265 resultMetadata block.ResultMetadata, 266 blockReplicas seriesBlocks, 267 stepSize time.Duration, 268 seriesIterator encoding.SeriesIterator, 269 pools encoding.IteratorPools, 270 ) error { 271 // NB: clone ID and Namespace since they must be owned by the series blocks. 272 var ( 273 // todo(braskin): use ident pool 274 clonedID = ident.StringID(seriesIterator.ID().String()) 275 clonedNamespace = ident.StringID(seriesIterator.Namespace().String()) 276 ) 277 278 replicaLength := len(blockReplicas) - 1 279 // TODO: use pooling 280 for i, block := range blockReplicas { 281 filterValuesStart := seriesIterator.Start() 282 if block.blockStart.After(filterValuesStart) { 283 filterValuesStart = block.blockStart 284 } 285 286 end := block.blockStart.Add(block.blockSize) 287 filterValuesEnd := seriesIterator.End() 288 if end.Before(filterValuesEnd) { 289 filterValuesEnd = end 290 } 291 292 iter := encoding.NewSeriesIterator(encoding.SeriesIteratorOptions{ 293 ID: clonedID, 294 Namespace: clonedNamespace, 295 StartInclusive: filterValuesStart, 296 EndExclusive: filterValuesEnd, 297 Replicas: block.replicas, 298 }, nil) 299 300 // NB: if querying a small range, such that blockSize is greater than the 301 // iterator duration, use the smaller range instead. 302 duration := filterValuesEnd.Sub(filterValuesStart) 303 if duration > block.blockSize { 304 duration = block.blockSize 305 } 306 307 // NB(braskin): we should be careful when directly accessing the series iterators. 308 // Instead, we should access them through the SeriesBlock. 309 isLastBlock := i == replicaLength 310 blockBuilder.add( 311 iter, 312 tags, 313 resultMetadata, 314 models.Bounds{ 315 Start: block.readStart, 316 Duration: duration, 317 StepSize: stepSize, 318 }, 319 isLastBlock, 320 ) 321 } 322 323 return nil 324 }