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 }