vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/vstreamer/packet_size.go (about) 1 /* 2 Copyright 2021 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package vstreamer 18 19 import ( 20 "time" 21 22 "github.com/spf13/pflag" 23 24 "vitess.io/vitess/go/vt/servenv" 25 26 "vitess.io/vitess/go/mathstats" 27 ) 28 29 var ( 30 defaultPacketSize = 250000 31 useDynamicPacketSize = true 32 ) 33 34 func init() { 35 servenv.OnParseFor("vtcombo", registerPacketSizeFlags) 36 servenv.OnParseFor("vttablet", registerPacketSizeFlags) 37 } 38 39 func registerPacketSizeFlags(fs *pflag.FlagSet) { 40 // defaultPacketSize is the suggested packet size for VReplication streamer. 41 fs.IntVar(&defaultPacketSize, "vstream_packet_size", defaultPacketSize, "Suggested packet size for VReplication streamer. This is used only as a recommendation. The actual packet size may be more or less than this amount.") 42 // useDynamicPacketSize controls whether to use dynamic packet size adjustments to increase performance while streaming 43 fs.BoolVar(&useDynamicPacketSize, "vstream_dynamic_packet_size", useDynamicPacketSize, "Enable dynamic packet sizing for VReplication. This will adjust the packet size during replication to improve performance.") 44 } 45 46 // PacketSizer is a controller that adjusts the size of the packets being sent by the vstreamer at runtime 47 type PacketSizer interface { 48 ShouldSend(byteCount int) bool 49 Record(byteCount int, duration time.Duration) 50 Limit() int 51 } 52 53 // DefaultPacketSizer creates a new PacketSizer using the default settings. 54 // If dynamic packet sizing is enabled, this will return a dynamicPacketSizer. 55 func DefaultPacketSizer() PacketSizer { 56 if useDynamicPacketSize { 57 return newDynamicPacketSizer(defaultPacketSize) 58 } 59 return newFixedPacketSize(defaultPacketSize) 60 } 61 62 // AdjustPacketSize temporarily adjusts the default packet sizes to the given value. 63 // Calling the returned cleanup function resets them to their original value. 64 // This function is only used for testing. 65 func AdjustPacketSize(size int) func() { 66 originalSize := defaultPacketSize 67 originalDyn := useDynamicPacketSize 68 69 defaultPacketSize = size 70 useDynamicPacketSize = false 71 72 return func() { 73 defaultPacketSize = originalSize 74 useDynamicPacketSize = originalDyn 75 } 76 } 77 78 type fixedPacketSizer struct { 79 baseSize int 80 } 81 82 func newFixedPacketSize(baseSize int) PacketSizer { 83 return &fixedPacketSizer{baseSize: baseSize} 84 } 85 86 func (ps *fixedPacketSizer) Limit() int { 87 return ps.baseSize 88 } 89 90 // ShouldSend checks whether the given byte count is large enough to be sent as a packet while streaming 91 func (ps *fixedPacketSizer) ShouldSend(byteCount int) bool { 92 return byteCount >= ps.baseSize 93 } 94 95 // Record records the total duration it took to send the given byte count while streaming 96 func (ps *fixedPacketSizer) Record(_ int, _ time.Duration) {} 97 98 type dynamicPacketSizer struct { 99 // currentSize is the last size for the packet that is safe to use 100 currentSize int 101 102 // currentMetrics are the performance metrics for the current size 103 currentMetrics *mathstats.Sample 104 105 // candidateSize is the target size for packets being tested 106 candidateSize int 107 108 // candidateMetrics are the performance metrics for this new metric 109 candidateMetrics *mathstats.Sample 110 111 // grow is the growth rate with which we adjust the packet size 112 grow int 113 114 // calls is the amount of calls to the packet sizer 115 calls int 116 117 // settled is true when the last experiment has finished and arrived at a new target packet size 118 settled bool 119 120 // elapsed is the time elapsed since the last experiment was settled 121 elapsed time.Duration 122 } 123 124 func newDynamicPacketSizer(baseSize int) PacketSizer { 125 return &dynamicPacketSizer{ 126 currentSize: baseSize, 127 currentMetrics: &mathstats.Sample{}, 128 candidateMetrics: &mathstats.Sample{}, 129 candidateSize: baseSize, 130 grow: baseSize / 4, 131 } 132 } 133 134 func (ps *dynamicPacketSizer) Limit() int { 135 return ps.candidateSize 136 } 137 138 // ShouldSend checks whether the given byte count is large enough to be sent as a packet while streaming 139 func (ps *dynamicPacketSizer) ShouldSend(byteCount int) bool { 140 return byteCount >= ps.candidateSize 141 } 142 143 type change int8 144 145 const ( 146 notChanging change = iota 147 gettingFaster 148 gettingSlower 149 ) 150 151 func (ps *dynamicPacketSizer) changeInThroughput() change { 152 const PValueMargin = 0.1 153 154 t, err := mathstats.TwoSampleWelchTTest(ps.currentMetrics, ps.candidateMetrics, mathstats.LocationDiffers) 155 if err != nil { 156 return notChanging 157 } 158 if t.P < PValueMargin { 159 if ps.candidateMetrics.Mean() > ps.currentMetrics.Mean() { 160 return gettingFaster 161 } 162 return gettingSlower 163 } 164 return notChanging 165 } 166 167 func (ps *dynamicPacketSizer) reset() { 168 ps.currentMetrics.Clear() 169 ps.candidateMetrics.Clear() 170 ps.calls = 0 171 ps.settled = false 172 ps.elapsed = 0 173 } 174 175 // Record records the total duration it took to send the given byte count while streaming 176 func (ps *dynamicPacketSizer) Record(byteCount int, d time.Duration) { 177 const ExperimentDelay = 5 * time.Second 178 const CheckFrequency = 16 179 const GrowthFrequency = 32 180 const InitialCandidateLen = 32 181 const SettleCandidateLen = 64 182 183 if ps.settled { 184 ps.elapsed += d 185 if ps.elapsed < ExperimentDelay { 186 return 187 } 188 ps.reset() 189 } 190 191 ps.calls++ 192 ps.candidateMetrics.Xs = append(ps.candidateMetrics.Xs, float64(byteCount)/float64(d)) 193 if ps.calls%CheckFrequency == 0 { 194 ps.candidateMetrics.Sorted = false 195 ps.candidateMetrics.FilterOutliers() 196 197 if len(ps.currentMetrics.Xs) == 0 { 198 if len(ps.candidateMetrics.Xs) >= InitialCandidateLen { 199 ps.currentMetrics, ps.candidateMetrics = ps.candidateMetrics, ps.currentMetrics 200 } 201 return 202 } 203 204 change := ps.changeInThroughput() 205 switch change { 206 case notChanging, gettingSlower: 207 if len(ps.candidateMetrics.Xs) >= SettleCandidateLen { 208 ps.candidateSize = ps.currentSize 209 ps.settled = true 210 } else { 211 if change == notChanging && ps.calls%GrowthFrequency == 0 { 212 ps.candidateSize += ps.grow 213 } 214 } 215 216 case gettingFaster: 217 ps.candidateMetrics, ps.currentMetrics = ps.currentMetrics, ps.candidateMetrics 218 ps.candidateMetrics.Clear() 219 220 ps.candidateSize += ps.grow 221 ps.currentSize = ps.candidateSize 222 } 223 } 224 }