github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/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 "fmt" 9 "math" 10 "os" 11 "sync" 12 "sync/atomic" 13 "time" 14 15 "github.com/cockroachdb/pebble/internal/rate" 16 "golang.org/x/exp/rand" 17 ) 18 19 const ( 20 // If measureLatency is true, the simulator outputs p50, p95, and p99 21 // latencies after all writes are completed. In this mode, all writes 22 // are immediately queued. If this is disabled, writes come in continually 23 // at different rates. 24 measureLatency = false 25 26 writeAmount = 2000 << 20 // 2 GB 27 28 // Max rate for all compactions. This is intentionally set low enough that 29 // user writes will have to be delayed. 30 maxCompactionRate = 100 << 20 // 100 MB/s 31 minCompactionRate = 20 << 20 // 20 MB/s 32 33 memtableSize = 64 << 20 // 64 MB 34 maxMemtableCount = 5 35 drainDelayThreshold = 1.05 * memtableSize 36 maxFlushRate = 30 << 20 // 30 MB/s 37 minFlushRate = 4 << 20 // 4 MB/s 38 39 l0CompactionThreshold = 1 40 41 levelRatio = 10 42 numLevels = 7 43 44 compactionDebtSlowdownThreshold = 2 * memtableSize 45 ) 46 47 type compactionPacer struct { 48 level atomic.Int64 49 maxDrainer *rate.Limiter 50 minDrainer *rate.Limiter 51 } 52 53 func newCompactionPacer() *compactionPacer { 54 p := &compactionPacer{ 55 maxDrainer: rate.NewLimiter(maxCompactionRate, maxCompactionRate), 56 minDrainer: rate.NewLimiter(minCompactionRate, minCompactionRate), 57 } 58 return p 59 } 60 61 func (p *compactionPacer) fill(n int64) { 62 p.level.Add(n) 63 } 64 65 func (p *compactionPacer) drain(n int64, delay bool) bool { 66 p.maxDrainer.Wait(float64(n)) 67 68 if delay { 69 p.minDrainer.Wait(float64(n)) 70 } 71 level := p.level.Add(-n) 72 return level <= compactionDebtSlowdownThreshold 73 } 74 75 type flushPacer struct { 76 level atomic.Int64 77 drainDelayLevel float64 78 fillCond sync.Cond 79 // minDrainer is the drainer which sets the minimum speed of draining. 80 minDrainer *rate.Limiter 81 // maxDrainer is the drainer which sets the maximum speed of draining. 82 maxDrainer *rate.Limiter 83 } 84 85 func newFlushPacer(mu *sync.Mutex) *flushPacer { 86 p := &flushPacer{ 87 drainDelayLevel: drainDelayThreshold, 88 minDrainer: rate.NewLimiter(minFlushRate, minFlushRate), 89 maxDrainer: rate.NewLimiter(maxFlushRate, maxFlushRate), 90 } 91 p.fillCond.L = mu 92 return p 93 } 94 95 func (p *flushPacer) fill(n int64) { 96 p.level.Add(n) 97 p.fillCond.Signal() 98 } 99 100 func (p *flushPacer) drain(n int64, delay bool) bool { 101 p.maxDrainer.Wait(float64(n)) 102 103 if delay { 104 p.minDrainer.Wait(float64(n)) 105 } 106 level := p.level.Add(-n) 107 p.fillCond.Signal() 108 return float64(level) <= p.drainDelayLevel 109 } 110 111 // DB models a Pebble DB. 112 type DB struct { 113 mu sync.Mutex 114 flushPacer *flushPacer 115 flushCond sync.Cond 116 memtables []*int64 117 fill atomic.Int64 118 drain atomic.Int64 119 120 compactionMu sync.Mutex 121 compactionPacer *compactionPacer 122 // L0 is represented as an array of integers whereas every other level 123 // is represented as a single integer. 124 L0 []*int64 125 // Non-L0 sstables. sstables[0] == L1. 126 sstables []atomic.Int64 127 maxSSTableSizes []int64 128 compactionFlushCond sync.Cond 129 prevCompactionDebt float64 130 } 131 132 func newDB() *DB { 133 db := &DB{} 134 db.flushPacer = newFlushPacer(&db.mu) 135 db.flushCond.L = &db.mu 136 db.memtables = append(db.memtables, new(int64)) 137 138 db.compactionFlushCond.L = &db.compactionMu 139 db.L0 = append(db.L0, new(int64)) 140 db.compactionPacer = newCompactionPacer() 141 142 db.maxSSTableSizes = make([]int64, numLevels-1) 143 db.sstables = make([]atomic.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[i].Store(newLevel) 154 } 155 db.sstables[numLevels-2].Store(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 := db.sstables[i].Add(singleTableSize) 194 if newSSTableSize > db.maxSSTableSizes[i] { 195 db.sstables[i].Add(-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 db.drain.Add(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 := db.compactionPacer.level.Load() 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 * float64(compactionDebtSlowdownThreshold/compactionDebt) 279 if drainLimit > 0 && drainLimit <= maxFlushRate { 280 db.flushPacer.maxDrainer.SetRate(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.Rate() * 1.05 286 if drainLimit > 0 && drainLimit <= maxFlushRate { 287 db.flushPacer.maxDrainer.SetRate(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 db.fill.Add(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 // simulateWrite simulates user writes. 317 func simulateWrite(db *DB, measureLatencyMode bool) { 318 limiter := rate.NewLimiter(10<<20, 10<<20) // 10 MB/s 319 fmt.Printf("filling at 10 MB/sec\n") 320 321 setRate := func(mb int) { 322 fmt.Printf("filling at %d MB/sec\n", mb) 323 limiter.SetRate(float64(mb << 20)) 324 } 325 326 if !measureLatencyMode { 327 go func() { 328 rng := rand.New(rand.NewSource(3)) 329 for { 330 secs := 5 + rng.Intn(5) 331 time.Sleep(time.Duration(secs) * time.Second) 332 mb := 10 + rng.Intn(20) 333 setRate(mb) 334 } 335 }() 336 } 337 338 rng := rand.New(rand.NewSource(uint64(4))) 339 340 totalWrites := int64(0) 341 percentiles := []int64{50, 95, 99} 342 percentileIndex := 0 343 percentileTimes := make([]time.Time, 0) 344 345 startTime := time.Now() 346 for totalWrites <= writeAmount { 347 size := 1000 + rng.Int63n(50) 348 if !measureLatencyMode { 349 limiter.Wait(float64(size)) 350 } 351 db.fillMemtable(size) 352 353 // Calculate latency percentiles 354 totalWrites += size 355 if percentileIndex < len(percentiles) && totalWrites > (percentiles[percentileIndex]*writeAmount/100) { 356 percentileTimes = append(percentileTimes, time.Now()) 357 percentileIndex++ 358 } 359 } 360 361 time.Sleep(time.Second * 10) 362 // Latency should only be measured when `limiter.WaitN` is removed. 363 if measureLatencyMode { 364 fmt.Printf("_____p50______p95______p99\n") 365 fmt.Printf("%8s %8s %8s\n", 366 time.Duration(percentileTimes[0].Sub(startTime).Seconds())*time.Second, 367 time.Duration(percentileTimes[1].Sub(startTime).Seconds())*time.Second, 368 time.Duration(percentileTimes[2].Sub(startTime).Seconds())*time.Second) 369 } 370 371 os.Exit(0) 372 } 373 374 func main() { 375 db := newDB() 376 377 go simulateWrite(db, measureLatency) 378 379 tick := time.NewTicker(time.Second) 380 start := time.Now() 381 lastNow := start 382 var lastFill, lastDrain int64 383 384 for i := 0; ; i++ { 385 <-tick.C 386 if (i % 20) == 0 { 387 fmt.Printf("_elapsed___memtbs____dirty_____fill____drain____cdebt__l0count___max-f-rate\n") 388 } 389 390 db.mu.Lock() 391 memtableCount := len(db.memtables) 392 db.mu.Unlock() 393 dirty := db.flushPacer.level.Load() 394 fill := db.fill.Load() 395 drain := db.drain.Load() 396 397 db.compactionMu.Lock() 398 compactionL0 := len(db.L0) 399 db.compactionMu.Unlock() 400 totalCompactionBytes := db.compactionPacer.level.Load() 401 compactionDebt := math.Max(float64(totalCompactionBytes)-l0CompactionThreshold*memtableSize, 0.0) 402 maxFlushRate := db.flushPacer.maxDrainer.Rate() 403 404 now := time.Now() 405 elapsed := now.Sub(lastNow).Seconds() 406 fmt.Printf("%8s %8d %8.1f %8.1f %8.1f %8.1f %8d %12.1f\n", 407 time.Duration(now.Sub(start).Seconds()+0.5)*time.Second, 408 memtableCount, 409 float64(dirty)/(1024.0*1024.0), 410 float64(fill-lastFill)/(1024.0*1024.0*elapsed), 411 float64(drain-lastDrain)/(1024.0*1024.0*elapsed), 412 compactionDebt/(1024.0*1024.0), 413 compactionL0, 414 maxFlushRate/(1024.0*1024.0)) 415 416 lastNow = now 417 lastFill = fill 418 lastDrain = drain 419 } 420 }