github.com/m3db/m3@v1.5.0/src/dbnode/ts/writes/write_batch.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 writes 22 23 import ( 24 "errors" 25 26 "github.com/m3db/m3/src/dbnode/ts" 27 "github.com/m3db/m3/src/x/ident" 28 xtime "github.com/m3db/m3/src/x/time" 29 ) 30 31 var ( 32 errTagsAndEncodedTagsRequired = errors.New("tags iterator and encoded tags must be provided") 33 ) 34 35 const ( 36 // preallocateBatchCoeff is used for allocating write batches of slightly bigger 37 // capacity than needed for the current request, in order to reduce allocations on 38 // subsequent reuse of pooled write batch (effective when writeBatch.adaptiveSize == true). 39 preallocateBatchCoeff = 1.2 40 ) 41 42 type writeBatch struct { 43 writes []BatchWrite 44 pendingIndex []PendingIndexInsert 45 ns ident.ID 46 // Enables callers to pool encoded tags by allowing them to 47 // provide a function to finalize all encoded tags once the 48 // writeBatch itself gets finalized. 49 finalizeEncodedTagsFn FinalizeEncodedTagsFn 50 // Enables callers to pool annotations by allowing them to 51 // provide a function to finalize all annotations once the 52 // writeBatch itself gets finalized. 53 finalizeAnnotationFn FinalizeAnnotationFn 54 finalizeFn func(WriteBatch) 55 56 // adaptiveSize means that we create writeBatch with nil slices originally, 57 // and then allocate/expand them based on the actual batch size (this provides 58 // more resilience when dealing with small batch sizes). 59 adaptiveSize bool 60 } 61 62 // NewWriteBatch creates a new WriteBatch. 63 func NewWriteBatch( 64 initialBatchSize int, 65 ns ident.ID, 66 finalizeFn func(WriteBatch), 67 ) WriteBatch { 68 var ( 69 adaptiveSize = initialBatchSize == 0 70 writes []BatchWrite 71 pendingIndex []PendingIndexInsert 72 ) 73 74 if !adaptiveSize { 75 writes = make([]BatchWrite, 0, initialBatchSize) 76 pendingIndex = make([]PendingIndexInsert, 0, initialBatchSize) 77 // Leaving nil slices if initialBatchSize == 0, 78 // they will be allocated when needed, based on the actual batch size. 79 } 80 81 return &writeBatch{ 82 writes: writes, 83 pendingIndex: pendingIndex, 84 ns: ns, 85 finalizeFn: finalizeFn, 86 adaptiveSize: adaptiveSize, 87 } 88 } 89 90 func (b *writeBatch) Add( 91 originalIndex int, 92 id ident.ID, 93 timestamp xtime.UnixNano, 94 value float64, 95 unit xtime.Unit, 96 annotation []byte, 97 ) error { 98 write, err := newBatchWriterWrite( 99 originalIndex, b.ns, id, nil, timestamp, value, unit, annotation) 100 if err != nil { 101 return err 102 } 103 b.writes = append(b.writes, write) 104 return nil 105 } 106 107 func (b *writeBatch) AddTagged( 108 originalIndex int, 109 id ident.ID, 110 encodedTags ts.EncodedTags, 111 timestamp xtime.UnixNano, 112 value float64, 113 unit xtime.Unit, 114 annotation []byte, 115 ) error { 116 write, err := newBatchWriterWrite( 117 originalIndex, b.ns, id, encodedTags, timestamp, value, unit, annotation) 118 if err != nil { 119 return err 120 } 121 b.writes = append(b.writes, write) 122 return nil 123 } 124 125 func (b *writeBatch) Reset( 126 batchSize int, 127 ns ident.ID, 128 ) { 129 // Preallocate slightly more when not using initialBatchSize. 130 adaptiveBatchCap := int(float32(batchSize) * preallocateBatchCoeff) 131 132 if batchSize > cap(b.writes) { 133 batchCap := batchSize 134 if b.adaptiveSize { 135 batchCap = adaptiveBatchCap 136 } 137 b.writes = make([]BatchWrite, 0, batchCap) 138 } else { 139 b.writes = b.writes[:0] 140 } 141 142 if batchSize > cap(b.pendingIndex) { 143 batchCap := batchSize 144 if b.adaptiveSize { 145 batchCap = adaptiveBatchCap 146 } 147 b.pendingIndex = make([]PendingIndexInsert, 0, batchCap) 148 } else { 149 b.pendingIndex = b.pendingIndex[:0] 150 } 151 152 b.ns = ns 153 b.finalizeEncodedTagsFn = nil 154 b.finalizeAnnotationFn = nil 155 } 156 157 func (b *writeBatch) Iter() []BatchWrite { 158 return b.writes 159 } 160 161 func (b *writeBatch) SetSeries(idx int, series ts.Series) { 162 b.writes[idx].SkipWrite = false 163 b.writes[idx].Write.Series = series 164 // Make sure that the EncodedTags does not get clobbered 165 b.writes[idx].Write.Series.EncodedTags = b.writes[idx].EncodedTags 166 } 167 168 func (b *writeBatch) SetError(idx int, err error) { 169 b.writes[idx].SkipWrite = true 170 b.writes[idx].Err = err 171 } 172 173 func (b *writeBatch) SetSkipWrite(idx int) { 174 b.writes[idx].SkipWrite = true 175 } 176 177 func (b *writeBatch) SetPendingIndex(idx int, pending PendingIndexInsert) { 178 b.writes[idx].PendingIndex = true 179 b.pendingIndex = append(b.pendingIndex, pending) 180 } 181 182 func (b *writeBatch) PendingIndex() []PendingIndexInsert { 183 return b.pendingIndex 184 } 185 186 // SetFinalizeEncodedTagsFn sets the function that will be called to finalize encodedTags 187 // when a WriteBatch is finalized, allowing the caller to pool them. 188 func (b *writeBatch) SetFinalizeEncodedTagsFn(f FinalizeEncodedTagsFn) { 189 b.finalizeEncodedTagsFn = f 190 } 191 192 // SetFinalizeAnnotationFn sets the function that will be called to finalize annotations 193 // when a WriteBatch is finalized, allowing the caller to pool them. 194 func (b *writeBatch) SetFinalizeAnnotationFn(f FinalizeAnnotationFn) { 195 b.finalizeAnnotationFn = f 196 } 197 198 func (b *writeBatch) Finalize() { 199 if b.finalizeEncodedTagsFn != nil { 200 for _, write := range b.writes { 201 encodedTags := write.EncodedTags 202 if encodedTags == nil { 203 continue 204 } 205 206 b.finalizeEncodedTagsFn(encodedTags) 207 } 208 } 209 b.finalizeEncodedTagsFn = nil 210 211 if b.finalizeAnnotationFn != nil { 212 for _, write := range b.writes { 213 annotation := write.Write.Annotation 214 if annotation == nil { 215 continue 216 } 217 218 b.finalizeAnnotationFn(annotation) 219 } 220 } 221 b.finalizeAnnotationFn = nil 222 223 b.ns = nil 224 225 var zeroedWrite BatchWrite 226 for i := range b.writes { 227 // Remove any remaining pointers for G.C reasons. 228 b.writes[i] = zeroedWrite 229 } 230 b.writes = b.writes[:0] 231 232 var zeroedIndex PendingIndexInsert 233 for i := range b.pendingIndex { 234 // Remove any remaining pointers for G.C reasons. 235 b.pendingIndex[i] = zeroedIndex 236 } 237 b.pendingIndex = b.pendingIndex[:0] 238 239 b.finalizeFn(b) 240 } 241 242 func (b *writeBatch) cap() int { 243 return cap(b.writes) 244 } 245 246 func newBatchWriterWrite( 247 originalIndex int, 248 namespace ident.ID, 249 id ident.ID, 250 encodedTags ts.EncodedTags, 251 timestamp xtime.UnixNano, 252 value float64, 253 unit xtime.Unit, 254 annotation []byte, 255 ) (BatchWrite, error) { 256 write := encodedTags == nil 257 writeTagged := encodedTags != nil 258 if !write && !writeTagged { 259 return BatchWrite{}, errTagsAndEncodedTagsRequired 260 } 261 return BatchWrite{ 262 Write: Write{ 263 Series: ts.Series{ 264 ID: id, 265 EncodedTags: encodedTags, 266 Namespace: namespace, 267 }, 268 Datapoint: ts.Datapoint{ 269 TimestampNanos: timestamp, 270 Value: value, 271 }, 272 Unit: unit, 273 Annotation: annotation, 274 }, 275 EncodedTags: encodedTags, 276 OriginalIndex: originalIndex, 277 }, nil 278 }