github.com/petermattis/pebble@v0.0.0-20190905164901-ab51a2166067/internal/pacertoy/pebble/main.go (about)

     1  // Copyright 2019 The LevelDB-Go and Pebble 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 main
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"math"
    11  	"os"
    12  	"sync"
    13  	"sync/atomic"
    14  	"time"
    15  
    16  	"github.com/petermattis/pebble/internal/rate"
    17  
    18  	"golang.org/x/exp/rand"
    19  )
    20  
    21  const (
    22  	// If measureLatency is true, the simulator outputs p50, p95, and p99
    23  	// latencies after all writes are completed. In this mode, all writes
    24  	// are immediately queued. If this is disabled, writes come in continually
    25  	// at different rates.
    26  	measureLatency        = false
    27  
    28  	writeAmount           = 2000 << 20 // 2 GB
    29  
    30  	// Max rate for all compactions. This is intentionally set low enough that
    31  	// user writes will have to be delayed.
    32  	maxCompactionRate     = 100 << 20 // 100 MB/s
    33  	minCompactionRate     = 20 << 20 // 20 MB/s
    34  
    35  	memtableSize          = 64 << 20 // 64 MB
    36  	maxMemtableCount      = 5
    37  	drainDelayThreshold   = 1.05 * memtableSize
    38  	maxFlushRate          = 30 << 20 // 30 MB/s
    39  	minFlushRate          = 4 << 20 // 4 MB/s
    40  
    41  	l0CompactionThreshold = 1
    42  
    43  	levelRatio            = 10
    44  	numLevels             = 7
    45  
    46  	compactionDebtSlowdownThreshold = 2 * memtableSize
    47  )
    48  
    49  type compactionPacer struct {
    50  	level      int64
    51  	maxDrainer *rate.Limiter
    52  	minDrainer *rate.Limiter
    53  }
    54  
    55  func newCompactionPacer() *compactionPacer {
    56  	p := &compactionPacer{
    57  		maxDrainer: rate.NewLimiter(maxCompactionRate, maxCompactionRate),
    58  		minDrainer: rate.NewLimiter(minCompactionRate, minCompactionRate),
    59  	}
    60  	return p
    61  }
    62  
    63  func (p *compactionPacer) fill(n int64) {
    64  	atomic.AddInt64(&p.level, n)
    65  }
    66  
    67  func (p *compactionPacer) drain(n int64, delay bool) bool {
    68  	p.maxDrainer.WaitN(context.Background(), int(n))
    69  
    70  	if delay {
    71  		p.minDrainer.WaitN(context.Background(), int(n))
    72  	}
    73  	level := atomic.AddInt64(&p.level, -n)
    74  	return level <= compactionDebtSlowdownThreshold
    75  }
    76  
    77  type flushPacer struct {
    78  	level             int64
    79  	drainDelayLevel   float64
    80  	fillCond          sync.Cond
    81  	// minDrainer is the drainer which sets the minimum speed of draining.
    82  	minDrainer        *rate.Limiter
    83  	// maxDrainer is the drainer which sets the maximum speed of draining.
    84  	maxDrainer        *rate.Limiter
    85  }
    86  
    87  func newFlushPacer(mu *sync.Mutex) *flushPacer {
    88  	p := &flushPacer{
    89  		drainDelayLevel: drainDelayThreshold,
    90  		minDrainer:      rate.NewLimiter(minFlushRate, minFlushRate),
    91  		maxDrainer:      rate.NewLimiter(maxFlushRate, maxFlushRate),
    92  	}
    93  	p.fillCond.L = mu
    94  	return p
    95  }
    96  
    97  func (p *flushPacer) fill(n int64) {
    98  	atomic.AddInt64(&p.level, n)
    99  	p.fillCond.Signal()
   100  }
   101  
   102  func (p *flushPacer) drain(n int64, delay bool) bool {
   103  	p.maxDrainer.WaitN(context.Background(), int(n))
   104  
   105  	if delay {
   106  		p.minDrainer.WaitN(context.Background(), int(n))
   107  	}
   108  	level := atomic.AddInt64(&p.level, -n)
   109  	p.fillCond.Signal()
   110  	return float64(level) <= p.drainDelayLevel
   111  }
   112  
   113  type DB struct {
   114  	mu         sync.Mutex
   115  	flushPacer *flushPacer
   116  	flushCond  sync.Cond
   117  	memtables  []*int64
   118  	fill       int64
   119  	drain      int64
   120  
   121  	compactionMu        sync.Mutex
   122  	compactionPacer     *compactionPacer
   123  	// L0 is represented as an array of integers whereas every other level
   124  	// is represented as a single integer.
   125  	L0                  []*int64
   126  	// Non-L0 sstables. sstables[0] == L1.
   127  	sstables            []int64
   128  	maxSSTableSizes     []int64
   129  	compactionFlushCond sync.Cond
   130  	prevCompactionDebt  float64
   131  }
   132  
   133  func newDB() *DB {
   134  	db := &DB{}
   135  	db.flushPacer = newFlushPacer(&db.mu)
   136  	db.flushCond.L = &db.mu
   137  	db.memtables = append(db.memtables, new(int64))
   138  
   139  	db.compactionFlushCond.L = &db.compactionMu
   140  	db.L0 = append(db.L0, new(int64))
   141  	db.compactionPacer = newCompactionPacer()
   142  
   143  	db.maxSSTableSizes = make([]int64, numLevels-1)
   144  	base := int64(levelRatio)
   145  	for i := uint64(0); i < numLevels-2; i++ {
   146  		// Each level is 10 times larger than the one above it.
   147  		db.maxSSTableSizes[i] = memtableSize * l0CompactionThreshold * base
   148  		base *= levelRatio
   149  
   150  		// Begin with each level full.
   151  		newLevel := db.maxSSTableSizes[i]
   152  
   153  		db.sstables = append(db.sstables, newLevel)
   154  	}
   155  	db.sstables = append(db.sstables, 0)
   156  	db.maxSSTableSizes[numLevels-2] = math.MaxInt64
   157  
   158  	go db.drainMemtable()
   159  	go db.drainCompaction()
   160  
   161  	return db
   162  }
   163  
   164  // drainCompaction simulates background compactions.
   165  func (db *DB) drainCompaction() {
   166  	rng := rand.New(rand.NewSource(1))
   167  
   168  	for {
   169  		db.compactionMu.Lock()
   170  
   171  		for len(db.L0) <= l0CompactionThreshold {
   172  			db.compactionFlushCond.Wait()
   173  		}
   174  		l0Table := db.L0[0]
   175  		db.compactionMu.Unlock()
   176  
   177  		var delay bool
   178  		for i, size := int64(0), int64(0); i < *l0Table; i += size {
   179  			size = 10000 + rng.Int63n(500)
   180  			if size > (*l0Table - i) {
   181  				size = *l0Table - i
   182  			}
   183  			delay = db.compactionPacer.drain(size, delay)
   184  		}
   185  
   186  		db.compactionMu.Lock()
   187  		db.L0 = db.L0[1:]
   188  		db.compactionMu.Unlock()
   189  
   190  		singleTableSize := int64(memtableSize)
   191  		tablesToCompact := 0
   192  		for i := range db.sstables {
   193  			newSSTableSize := atomic.AddInt64(&db.sstables[i], singleTableSize)
   194  			if newSSTableSize > db.maxSSTableSizes[i] {
   195  				atomic.AddInt64(&db.sstables[i], -singleTableSize)
   196  				tablesToCompact++
   197  			} else {
   198  				// Lower levels do not need compaction if level above it did not
   199  				// need compaction.
   200  				break
   201  			}
   202  		}
   203  
   204  		totalCompactionBytes := int64(tablesToCompact * memtableSize)
   205  
   206  		for t := 0; t < tablesToCompact; t++ {
   207  			db.compactionPacer.fill(memtableSize)
   208  			for i, size := int64(0), int64(0); i < memtableSize; i += size {
   209  				size = 10000 + rng.Int63n(500)
   210  				if size > (totalCompactionBytes - i) {
   211  					size = totalCompactionBytes - i
   212  				}
   213  				delay = db.compactionPacer.drain(size, delay)
   214  			}
   215  
   216  			db.delayMemtableDrain()
   217  		}
   218  	}
   219  }
   220  
   221  // fillCompaction fills L0 sstables.
   222  func (db *DB) fillCompaction(size int64) {
   223  	db.compactionMu.Lock()
   224  
   225  	db.compactionPacer.fill(size)
   226  
   227  	last := db.L0[len(db.L0)-1]
   228  	if *last+size > memtableSize {
   229  		last = new(int64)
   230  		db.L0 = append(db.L0, last)
   231  		db.compactionFlushCond.Signal()
   232  	}
   233  	*last += size
   234  
   235  	db.compactionMu.Unlock()
   236  }
   237  
   238  // drainMemtable simulates memtable flushing.
   239  func (db *DB) drainMemtable() {
   240  	rng := rand.New(rand.NewSource(2))
   241  
   242  	for {
   243  		db.mu.Lock()
   244  		for len(db.memtables) <= 1 {
   245  			db.flushCond.Wait()
   246  		}
   247  		memtable := db.memtables[0]
   248  		db.mu.Unlock()
   249  
   250  		var delay bool
   251  		for i, size := int64(0), int64(0); i < *memtable; i += size {
   252  			size = 1000 + rng.Int63n(50)
   253  			if size > (*memtable - i) {
   254  				size = *memtable - i
   255  			}
   256  			delay = db.flushPacer.drain(size, delay)
   257  			atomic.AddInt64(&db.drain, size)
   258  
   259  			db.fillCompaction(size)
   260  		}
   261  
   262  		db.delayMemtableDrain()
   263  
   264  		db.mu.Lock()
   265  		db.memtables = db.memtables[1:]
   266  		db.mu.Unlock()
   267  	}
   268  }
   269  
   270  // delayMemtableDrain applies memtable drain delays depending on compaction debt.
   271  func (db *DB) delayMemtableDrain() {
   272  	totalCompactionBytes := atomic.LoadInt64(&db.compactionPacer.level)
   273  	compactionDebt := math.Max(float64(totalCompactionBytes)-l0CompactionThreshold*memtableSize, 0.0)
   274  
   275  	db.mu.Lock()
   276  	if compactionDebt > compactionDebtSlowdownThreshold {
   277  		// Compaction debt is above the threshold and the debt is growing. Throttle memtable flushing.
   278  		drainLimit := maxFlushRate * rate.Limit(compactionDebtSlowdownThreshold/compactionDebt)
   279  		if drainLimit > 0 && drainLimit <= maxFlushRate {
   280  			db.flushPacer.maxDrainer.SetLimit(drainLimit)
   281  		}
   282  	} else {
   283  		// Continuously speed up memtable flushing to make sure that slowdown signal did not
   284  		// decrease the memtable flush rate by too much.
   285  		drainLimit := db.flushPacer.maxDrainer.Limit() * 1.05
   286  		if drainLimit > 0 && drainLimit <= maxFlushRate {
   287  			db.flushPacer.maxDrainer.SetLimit(drainLimit)
   288  		}
   289  	}
   290  
   291  	db.prevCompactionDebt = compactionDebt
   292  	db.mu.Unlock()
   293  }
   294  
   295  // fillMemtable simulates memtable filling.
   296  func (db *DB) fillMemtable(size int64) {
   297  	db.mu.Lock()
   298  
   299  	for len(db.memtables) > maxMemtableCount {
   300  		db.flushPacer.fillCond.Wait()
   301  	}
   302  	db.flushPacer.fill(size)
   303  	atomic.AddInt64(&db.fill, size)
   304  
   305  	last := db.memtables[len(db.memtables)-1]
   306  	if *last+size > memtableSize {
   307  		last = new(int64)
   308  		db.memtables = append(db.memtables, last)
   309  		db.flushCond.Signal()
   310  	}
   311  	*last += size
   312  
   313  	db.mu.Unlock()
   314  }
   315  
   316  // printLevels prints the levels.
   317  func (db *DB) printLevels() {
   318  	db.mu.Lock()
   319  	for i := range db.sstables {
   320  		fmt.Printf("Level %d: %d/%d\n", i+1, db.sstables[i]/(1024*1024), db.maxSSTableSizes[i]/(1024*1024))
   321  	}
   322  	db.mu.Unlock()
   323  }
   324  
   325  // simulateWrite simulates user writes.
   326  func simulateWrite(db *DB, measureLatencyMode bool) {
   327  	limiter := rate.NewLimiter(10<<20, 10<<20) // 10 MB/s
   328  	fmt.Printf("filling at 10 MB/sec\n")
   329  
   330  	setRate := func(mb int) {
   331  		fmt.Printf("filling at %d MB/sec\n", mb)
   332  		limiter.SetLimit(rate.Limit(mb << 20))
   333  	}
   334  
   335  	if !measureLatencyMode {
   336  		go func() {
   337  			rng := rand.New(rand.NewSource(3))
   338  			for {
   339  				secs := 5 + rng.Intn(5)
   340  				time.Sleep(time.Duration(secs) * time.Second)
   341  				mb := 10 + rng.Intn(20)
   342  				setRate(mb)
   343  			}
   344  		}()
   345  	}
   346  
   347  	rng := rand.New(rand.NewSource(uint64(4)))
   348  
   349  	totalWrites := int64(0)
   350  	percentiles := []int64{50, 95, 99}
   351  	percentileIndex := 0
   352  	percentileTimes := make([]time.Time, 0)
   353  
   354  	startTime := time.Now()
   355  	for totalWrites <= writeAmount {
   356  		size := 1000 + rng.Int63n(50)
   357  		if !measureLatencyMode {
   358  			limiter.WaitN(context.Background(), int(size))
   359  		}
   360  		db.fillMemtable(size)
   361  
   362  		// Calculate latency percentiles
   363  		totalWrites += size
   364  		if percentileIndex < len(percentiles) && totalWrites > (percentiles[percentileIndex] * writeAmount / 100) {
   365  			percentileTimes = append(percentileTimes, time.Now())
   366  			percentileIndex += 1
   367  		}
   368  	}
   369  
   370  	time.Sleep(time.Second * 10)
   371  	// Latency should only be measured when `limiter.WaitN` is removed.
   372  	if measureLatencyMode {
   373  		fmt.Printf("_____p50______p95______p99\n")
   374  		fmt.Printf("%8s %8s %8s\n",
   375  			time.Duration(percentileTimes[0].Sub(startTime).Seconds())*time.Second,
   376  			time.Duration(percentileTimes[1].Sub(startTime).Seconds())*time.Second,
   377  			time.Duration(percentileTimes[2].Sub(startTime).Seconds())*time.Second)
   378  	}
   379  
   380  	os.Exit(0)
   381  }
   382  
   383  func main() {
   384  	db := newDB()
   385  
   386  	go simulateWrite(db, measureLatency)
   387  
   388  	tick := time.NewTicker(time.Second)
   389  	start := time.Now()
   390  	lastNow := start
   391  	var lastFill, lastDrain int64
   392  
   393  	for i := 0; ; i++ {
   394  		select {
   395  		case <-tick.C:
   396  			if (i % 20) == 0 {
   397  				fmt.Printf("_elapsed___memtbs____dirty_____fill____drain____cdebt__l0count___max-f-rate\n")
   398  			}
   399  
   400  			if (i % 7) == 0 {
   401  				//db.printLevels()
   402  			}
   403  
   404  			db.mu.Lock()
   405  			memtableCount := len(db.memtables)
   406  			db.mu.Unlock()
   407  			dirty := atomic.LoadInt64(&db.flushPacer.level)
   408  			fill := atomic.LoadInt64(&db.fill)
   409  			drain := atomic.LoadInt64(&db.drain)
   410  
   411  			db.compactionMu.Lock()
   412  			compactionL0 := len(db.L0)
   413  			db.compactionMu.Unlock()
   414  			totalCompactionBytes := atomic.LoadInt64(&db.compactionPacer.level)
   415  			compactionDebt := math.Max(float64(totalCompactionBytes)-l0CompactionThreshold*memtableSize, 0.0)
   416  			maxFlushRate := db.flushPacer.maxDrainer.Limit()
   417  
   418  			now := time.Now()
   419  			elapsed := now.Sub(lastNow).Seconds()
   420  			fmt.Printf("%8s %8d %8.1f %8.1f %8.1f %8.1f %8d %12.1f\n",
   421  				time.Duration(now.Sub(start).Seconds()+0.5)*time.Second,
   422  				memtableCount,
   423  				float64(dirty)/(1024.0*1024.0),
   424  				float64(fill-lastFill)/(1024.0*1024.0*elapsed),
   425  				float64(drain-lastDrain)/(1024.0*1024.0*elapsed),
   426  				compactionDebt/(1024.0*1024.0),
   427  				compactionL0,
   428  				maxFlushRate/(1024.0*1024.0))
   429  
   430  			lastNow = now
   431  			lastFill = fill
   432  			lastDrain = drain
   433  		}
   434  	}
   435  }