github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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  }