github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ts/memory.go (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package ts
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"math"
    17  	"unsafe"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    20  	"github.com/cockroachdb/cockroach/pkg/ts/tspb"
    21  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    22  	"github.com/cockroachdb/cockroach/pkg/util/mon"
    23  )
    24  
    25  // Compute the size of various structures to use when tracking memory usage.
    26  var (
    27  	sizeOfTimeSeriesData = int64(unsafe.Sizeof(roachpb.InternalTimeSeriesData{}))
    28  	sizeOfSample         = int64(unsafe.Sizeof(roachpb.InternalTimeSeriesSample{}))
    29  	sizeOfDataPoint      = int64(unsafe.Sizeof(tspb.TimeSeriesDatapoint{}))
    30  	sizeOfInt32          = int64(unsafe.Sizeof(int32(0)))
    31  	sizeOfUint32         = int64(unsafe.Sizeof(uint32(0)))
    32  	sizeOfFloat64        = int64(unsafe.Sizeof(float64(0)))
    33  	sizeOfTimestamp      = int64(unsafe.Sizeof(hlc.Timestamp{}))
    34  )
    35  
    36  // QueryMemoryOptions represents the adjustable options of a QueryMemoryContext.
    37  type QueryMemoryOptions struct {
    38  	// BudgetBytes is the maximum number of bytes that should be reserved by this
    39  	// query at any one time.
    40  	BudgetBytes int64
    41  	// EstimatedSources is an estimate of the number of distinct sources that this
    42  	// query will encounter on disk. This is needed to better estimate how much
    43  	// memory a query will actually consume.
    44  	EstimatedSources int64
    45  	// InterpolationLimitNanos determines the maximum gap size for which missing
    46  	// values will be interpolated. By making this limit explicit, we can put a
    47  	// hard limit on the timespan that needs to be read from disk to satisfy
    48  	// a query.
    49  	InterpolationLimitNanos int64
    50  	// If true, memory will be computed assuming the columnar layout.
    51  	Columnar bool
    52  }
    53  
    54  // QueryMemoryContext encapsulates the memory-related parameters of a time
    55  // series query. These same parameters are often repeated across numerous
    56  // queries.
    57  type QueryMemoryContext struct {
    58  	workerMonitor *mon.BytesMonitor
    59  	resultAccount *mon.BoundAccount
    60  	QueryMemoryOptions
    61  }
    62  
    63  // MakeQueryMemoryContext constructs a new query memory context from the
    64  // given parameters.
    65  func MakeQueryMemoryContext(
    66  	workerMonitor, resultMonitor *mon.BytesMonitor, opts QueryMemoryOptions,
    67  ) QueryMemoryContext {
    68  	resultAccount := resultMonitor.MakeBoundAccount()
    69  	return QueryMemoryContext{
    70  		workerMonitor:      workerMonitor,
    71  		resultAccount:      &resultAccount,
    72  		QueryMemoryOptions: opts,
    73  	}
    74  }
    75  
    76  // Close closes any resources held by the queryMemoryContext.
    77  func (qmc QueryMemoryContext) Close(ctx context.Context) {
    78  	if qmc.resultAccount != nil {
    79  		qmc.resultAccount.Close(ctx)
    80  	}
    81  }
    82  
    83  // overflowSafeMultiply64 is a check for signed integer multiplication taken
    84  // from https://github.com/JohnCGriffin/overflow/blob/master/overflow_impl.go
    85  func overflowSafeMultiply64(a, b int64) (int64, bool) {
    86  	if a == 0 || b == 0 {
    87  		return 0, true
    88  	}
    89  	c := a * b
    90  	if (c < 0) == ((a < 0) != (b < 0)) {
    91  		if c/b == a {
    92  			return c, true
    93  		}
    94  	}
    95  	return c, false
    96  }
    97  
    98  // GetMaxTimespan computes the longest timespan that can be safely queried while
    99  // remaining within the given memory budget. Inputs are the resolution of data
   100  // being queried, the budget, the estimated number of sources, and the
   101  // interpolation limit being used for the query.
   102  func (qmc QueryMemoryContext) GetMaxTimespan(r Resolution) (int64, error) {
   103  	slabDuration := r.SlabDuration()
   104  
   105  	// Compute the size of a slab.
   106  	sizeOfSlab := qmc.computeSizeOfSlab(r)
   107  
   108  	// InterpolationBuffer is the number of slabs outside of the query range
   109  	// needed to satisfy the interpolation limit. Extra slabs may be queried
   110  	// on both sides of the target range.
   111  	interpolationBufferOneSide :=
   112  		int64(math.Ceil(float64(qmc.InterpolationLimitNanos) / float64(slabDuration)))
   113  
   114  	interpolationBuffer := interpolationBufferOneSide * 2
   115  
   116  	// If the (interpolation buffer timespan - interpolation limit) is less than
   117  	// half of a slab, then it is possible for one additional slab to be queried
   118  	// that would not have otherwise been queried. This can occur when the queried
   119  	// timespan does not start on an even slab boundary.
   120  	if (interpolationBufferOneSide*slabDuration)-qmc.InterpolationLimitNanos < slabDuration/2 {
   121  		interpolationBuffer++
   122  	}
   123  
   124  	// The number of slabs that can be queried safely is perSeriesMem/sizeOfSlab,
   125  	// less the interpolation buffer.
   126  	perSourceMem := qmc.BudgetBytes / qmc.EstimatedSources
   127  	numSlabs := perSourceMem/sizeOfSlab - interpolationBuffer
   128  	if numSlabs <= 0 {
   129  		return 0, fmt.Errorf("insufficient memory budget to attempt query")
   130  	}
   131  
   132  	maxDuration, valid := overflowSafeMultiply64(numSlabs, slabDuration)
   133  	if valid {
   134  		return maxDuration, nil
   135  	}
   136  	return math.MaxInt64, nil
   137  }
   138  
   139  // GetMaxRollupSlabs returns the maximum number of rows that should be processed
   140  // at one time when rolling up the given resolution.
   141  func (qmc QueryMemoryContext) GetMaxRollupSlabs(r Resolution) int64 {
   142  	// Rollup computations only occur when columnar is true.
   143  	return qmc.BudgetBytes / qmc.computeSizeOfSlab(r)
   144  }
   145  
   146  // computeSizeOfSlab returns the size of a completely full data slab for the supplied
   147  // data resolution.
   148  func (qmc QueryMemoryContext) computeSizeOfSlab(r Resolution) int64 {
   149  	slabDuration := r.SlabDuration()
   150  
   151  	var sizeOfSlab int64
   152  	if qmc.Columnar {
   153  		// Contains an Offset (int32) and Last (float64) for each sample.
   154  		sizeOfColumns := (sizeOfInt32 + sizeOfFloat64)
   155  		if r.IsRollup() {
   156  			// Five additional float64 (First, Min, Max, Sum, Variance) and one uint32
   157  			// (count) per sample
   158  			sizeOfColumns += 5*sizeOfFloat64 + sizeOfUint32
   159  		}
   160  		sizeOfSlab = sizeOfTimeSeriesData + (slabDuration/r.SampleDuration())*sizeOfColumns
   161  	} else {
   162  		// Contains a sample structure for each sample.
   163  		sizeOfSlab = sizeOfTimeSeriesData + (slabDuration/r.SampleDuration())*sizeOfSample
   164  	}
   165  	return sizeOfSlab
   166  }