github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/pacer.go (about)

     1  // Copyright 2019 The LevelDB-Go and Pebble and Bitalostored Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package bitalostable
     6  
     7  import (
     8  	"time"
     9  
    10  	"github.com/cockroachdb/errors"
    11  	"github.com/zuoyebang/bitalostable/internal/rate"
    12  )
    13  
    14  var nilPacer = &noopPacer{}
    15  
    16  type limiter interface {
    17  	DelayN(now time.Time, n int) time.Duration
    18  	AllowN(now time.Time, n int) bool
    19  	Burst() int
    20  }
    21  
    22  // pacer is the interface for flush and compaction rate limiters. The rate limiter
    23  // is possible applied on each iteration step of a flush or compaction. This is to
    24  // limit background IO usage so that it does not contend with foreground traffic.
    25  type pacer interface {
    26  	maybeThrottle(bytesIterated uint64) error
    27  }
    28  
    29  // deletionPacerInfo contains any info from the db necessary to make deletion
    30  // pacing decisions.
    31  type deletionPacerInfo struct {
    32  	freeBytes     uint64
    33  	obsoleteBytes uint64
    34  	liveBytes     uint64
    35  }
    36  
    37  // deletionPacer rate limits deletions of obsolete files. This is necessary to
    38  // prevent overloading the disk with too many deletions too quickly after a
    39  // large compaction, or an iterator close. On some SSDs, disk performance can be
    40  // negatively impacted if too many blocks are deleted very quickly, so this
    41  // mechanism helps mitigate that.
    42  type deletionPacer struct {
    43  	limiter               limiter
    44  	freeSpaceThreshold    uint64
    45  	obsoleteBytesMaxRatio float64
    46  
    47  	getInfo func() deletionPacerInfo
    48  }
    49  
    50  // newDeletionPacer instantiates a new deletionPacer for use when deleting
    51  // obsolete files. The limiter passed in must be a singleton shared across this
    52  // bitalostable instance.
    53  func newDeletionPacer(limiter limiter, getInfo func() deletionPacerInfo) *deletionPacer {
    54  	return &deletionPacer{
    55  		limiter: limiter,
    56  		// If there are less than freeSpaceThreshold bytes of free space on
    57  		// disk, do not pace deletions at all.
    58  		freeSpaceThreshold: 16 << 30, // 16 GB
    59  		// If the ratio of obsolete bytes to live bytes is greater than
    60  		// obsoleteBytesMaxRatio, do not pace deletions at all.
    61  		obsoleteBytesMaxRatio: 0.20,
    62  
    63  		getInfo: getInfo,
    64  	}
    65  }
    66  
    67  // limit applies rate limiting if the current free disk space is more than
    68  // freeSpaceThreshold, and the ratio of obsolete to live bytes is less than
    69  // obsoleteBytesMaxRatio.
    70  func (p *deletionPacer) limit(amount uint64, info deletionPacerInfo) error {
    71  	obsoleteBytesRatio := float64(1.0)
    72  	if info.liveBytes > 0 {
    73  		obsoleteBytesRatio = float64(info.obsoleteBytes) / float64(info.liveBytes)
    74  	}
    75  	paceDeletions := info.freeBytes > p.freeSpaceThreshold &&
    76  		obsoleteBytesRatio < p.obsoleteBytesMaxRatio
    77  	if paceDeletions {
    78  		burst := p.limiter.Burst()
    79  		for amount > uint64(burst) {
    80  			d := p.limiter.DelayN(time.Now(), burst)
    81  			if d == rate.InfDuration {
    82  				return errors.Errorf("pacing failed")
    83  			}
    84  			time.Sleep(d)
    85  			amount -= uint64(burst)
    86  		}
    87  		d := p.limiter.DelayN(time.Now(), int(amount))
    88  		if d == rate.InfDuration {
    89  			return errors.Errorf("pacing failed")
    90  		}
    91  		time.Sleep(d)
    92  	} else {
    93  		burst := p.limiter.Burst()
    94  		for amount > uint64(burst) {
    95  			// AllowN will subtract burst if there are enough tokens available,
    96  			// else leave the tokens untouched. That is, we are making a
    97  			// best-effort to account for this activity in the limiter, but by
    98  			// ignoring the return value, we do the activity instantaneously
    99  			// anyway.
   100  			p.limiter.AllowN(time.Now(), burst)
   101  			amount -= uint64(burst)
   102  		}
   103  		p.limiter.AllowN(time.Now(), int(amount))
   104  	}
   105  	return nil
   106  }
   107  
   108  // maybeThrottle slows down a deletion of this file if it's faster than
   109  // opts.Experimental.MinDeletionRate.
   110  func (p *deletionPacer) maybeThrottle(bytesToDelete uint64) error {
   111  	return p.limit(bytesToDelete, p.getInfo())
   112  }
   113  
   114  type noopPacer struct{}
   115  
   116  func (p *noopPacer) maybeThrottle(_ uint64) error {
   117  	return nil
   118  }