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  }