github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/checkpoint_test.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 pebble 6 7 import ( 8 "bytes" 9 "context" 10 "fmt" 11 "math/rand" 12 "slices" 13 "sort" 14 "strings" 15 "sync" 16 "testing" 17 18 "github.com/cockroachdb/datadriven" 19 "github.com/cockroachdb/pebble/internal/base" 20 "github.com/cockroachdb/pebble/vfs" 21 "github.com/stretchr/testify/require" 22 ) 23 24 func TestCheckpoint(t *testing.T) { 25 dbs := make(map[string]*DB) 26 defer func() { 27 for _, db := range dbs { 28 if db.closed.Load() == nil { 29 require.NoError(t, db.Close()) 30 } 31 } 32 }() 33 34 mem := vfs.NewMem() 35 var memLog base.InMemLogger 36 opts := &Options{ 37 FS: vfs.WithLogging(mem, memLog.Infof), 38 FormatMajorVersion: internalFormatNewest, 39 L0CompactionThreshold: 10, 40 DisableAutomaticCompactions: true, 41 } 42 opts.private.disableTableStats = true 43 opts.private.testingAlwaysWaitForCleanup = true 44 45 datadriven.RunTest(t, "testdata/checkpoint", func(t *testing.T, td *datadriven.TestData) string { 46 switch td.Cmd { 47 case "batch": 48 if len(td.CmdArgs) != 1 { 49 return "batch <db>" 50 } 51 memLog.Reset() 52 d := dbs[td.CmdArgs[0].String()] 53 b := d.NewBatch() 54 if err := runBatchDefineCmd(td, b); err != nil { 55 return err.Error() 56 } 57 if err := b.Commit(Sync); err != nil { 58 return err.Error() 59 } 60 return memLog.String() 61 62 case "checkpoint": 63 if !(len(td.CmdArgs) == 2 || (len(td.CmdArgs) == 3 && td.CmdArgs[2].Key == "restrict")) { 64 return "checkpoint <db> <dir> [restrict=(start-end, ...)]" 65 } 66 var opts []CheckpointOption 67 if len(td.CmdArgs) == 3 { 68 var spans []CheckpointSpan 69 for _, v := range td.CmdArgs[2].Vals { 70 splits := strings.SplitN(v, "-", 2) 71 if len(splits) != 2 { 72 return fmt.Sprintf("invalid restrict range %q", v) 73 } 74 spans = append(spans, CheckpointSpan{ 75 Start: []byte(splits[0]), 76 End: []byte(splits[1]), 77 }) 78 } 79 opts = append(opts, WithRestrictToSpans(spans)) 80 } 81 memLog.Reset() 82 d := dbs[td.CmdArgs[0].String()] 83 if err := d.Checkpoint(td.CmdArgs[1].String(), opts...); err != nil { 84 return err.Error() 85 } 86 return memLog.String() 87 88 case "ingest-and-excise": 89 d := dbs[td.CmdArgs[0].String()] 90 91 // Hacky but the command doesn't expect a db string. Get rid of it. 92 td.CmdArgs = td.CmdArgs[1:] 93 if err := runIngestAndExciseCmd(td, d, mem); err != nil { 94 return err.Error() 95 } 96 return "" 97 98 case "build": 99 d := dbs[td.CmdArgs[0].String()] 100 101 // Hacky but the command doesn't expect a db string. Get rid of it. 102 td.CmdArgs = td.CmdArgs[1:] 103 if err := runBuildCmd(td, d, mem); err != nil { 104 return err.Error() 105 } 106 return "" 107 108 case "lsm": 109 d := dbs[td.CmdArgs[0].String()] 110 111 // Hacky but the command doesn't expect a db string. Get rid of it. 112 td.CmdArgs = td.CmdArgs[1:] 113 return runLSMCmd(td, d) 114 115 case "compact": 116 if len(td.CmdArgs) != 1 { 117 return "compact <db>" 118 } 119 memLog.Reset() 120 d := dbs[td.CmdArgs[0].String()] 121 if err := d.Compact(nil, []byte("\xff"), false); err != nil { 122 return err.Error() 123 } 124 d.TestOnlyWaitForCleaning() 125 return memLog.String() 126 127 case "print-backing": 128 // prints contents of the file backing map in the version. Used to 129 // test whether the checkpoint removed the filebackings correctly. 130 if len(td.CmdArgs) != 1 { 131 return "print-backing <db>" 132 } 133 d := dbs[td.CmdArgs[0].String()] 134 d.mu.Lock() 135 d.mu.versions.logLock() 136 var fileNums []base.DiskFileNum 137 for _, b := range d.mu.versions.backingState.fileBackingMap { 138 fileNums = append(fileNums, b.DiskFileNum) 139 } 140 d.mu.versions.logUnlock() 141 d.mu.Unlock() 142 143 slices.Sort(fileNums) 144 var buf bytes.Buffer 145 for _, f := range fileNums { 146 buf.WriteString(fmt.Sprintf("%s\n", f.String())) 147 } 148 return buf.String() 149 150 case "close": 151 if len(td.CmdArgs) != 1 { 152 return "close <db>" 153 } 154 d := dbs[td.CmdArgs[0].String()] 155 require.NoError(t, d.Close()) 156 return "" 157 158 case "flush": 159 if len(td.CmdArgs) != 1 { 160 return "flush <db>" 161 } 162 memLog.Reset() 163 d := dbs[td.CmdArgs[0].String()] 164 if err := d.Flush(); err != nil { 165 return err.Error() 166 } 167 return memLog.String() 168 169 case "list": 170 if len(td.CmdArgs) != 1 { 171 return "list <dir>" 172 } 173 paths, err := mem.List(td.CmdArgs[0].String()) 174 if err != nil { 175 return err.Error() 176 } 177 sort.Strings(paths) 178 return fmt.Sprintf("%s\n", strings.Join(paths, "\n")) 179 180 case "open": 181 if len(td.CmdArgs) != 1 && len(td.CmdArgs) != 2 { 182 return "open <dir> [readonly]" 183 } 184 opts.ReadOnly = false 185 if len(td.CmdArgs) == 2 { 186 if td.CmdArgs[1].String() != "readonly" { 187 return "open <dir> [readonly]" 188 } 189 opts.ReadOnly = true 190 } 191 192 memLog.Reset() 193 dir := td.CmdArgs[0].String() 194 d, err := Open(dir, opts) 195 if err != nil { 196 return err.Error() 197 } 198 dbs[dir] = d 199 return memLog.String() 200 201 case "scan": 202 if len(td.CmdArgs) != 1 { 203 return "scan <db>" 204 } 205 memLog.Reset() 206 d := dbs[td.CmdArgs[0].String()] 207 iter, _ := d.NewIter(nil) 208 for valid := iter.First(); valid; valid = iter.Next() { 209 memLog.Infof("%s %s", iter.Key(), iter.Value()) 210 } 211 memLog.Infof(".") 212 if err := iter.Close(); err != nil { 213 memLog.Infof("%v\n", err) 214 } 215 return memLog.String() 216 217 default: 218 return fmt.Sprintf("unknown command: %s", td.Cmd) 219 } 220 }) 221 } 222 223 func TestCheckpointCompaction(t *testing.T) { 224 fs := vfs.NewMem() 225 d, err := Open("", &Options{FS: fs}) 226 require.NoError(t, err) 227 228 ctx, cancel := context.WithCancel(context.Background()) 229 230 var wg sync.WaitGroup 231 wg.Add(4) 232 go func() { 233 defer cancel() 234 defer wg.Done() 235 for i := 0; ctx.Err() == nil; i++ { 236 if err := d.Set([]byte(fmt.Sprintf("key%06d", i)), nil, nil); err != nil { 237 t.Error(err) 238 return 239 } 240 } 241 }() 242 go func() { 243 defer cancel() 244 defer wg.Done() 245 for ctx.Err() == nil { 246 if err := d.Compact([]byte("key"), []byte("key999999"), false); err != nil { 247 t.Error(err) 248 return 249 } 250 } 251 }() 252 check := make(chan string, 100) 253 go func() { 254 defer cancel() 255 defer close(check) 256 defer wg.Done() 257 for i := 0; ctx.Err() == nil && i < 200; i++ { 258 dir := fmt.Sprintf("checkpoint%06d", i) 259 if err := d.Checkpoint(dir); err != nil { 260 t.Error(err) 261 return 262 } 263 select { 264 case <-ctx.Done(): 265 return 266 case check <- dir: 267 } 268 } 269 }() 270 go func() { 271 opts := &Options{FS: fs} 272 defer cancel() 273 defer wg.Done() 274 for dir := range check { 275 d2, err := Open(dir, opts) 276 if err != nil { 277 t.Error(err) 278 return 279 } 280 // Check the checkpoint has all the sstables that the manifest 281 // claims it has. 282 tableInfos, _ := d2.SSTables() 283 for _, tables := range tableInfos { 284 for _, tbl := range tables { 285 if tbl.Virtual { 286 continue 287 } 288 if _, err := fs.Stat(base.MakeFilepath(fs, dir, base.FileTypeTable, tbl.FileNum.DiskFileNum())); err != nil { 289 t.Error(err) 290 return 291 } 292 } 293 } 294 if err := d2.Close(); err != nil { 295 t.Error(err) 296 return 297 } 298 } 299 }() 300 <-ctx.Done() 301 wg.Wait() 302 require.NoError(t, d.Close()) 303 } 304 305 func TestCheckpointFlushWAL(t *testing.T) { 306 const checkpointPath = "checkpoints/checkpoint" 307 fs := vfs.NewStrictMem() 308 opts := &Options{FS: fs} 309 key, value := []byte("key"), []byte("value") 310 311 // Create a checkpoint from an unsynced DB. 312 { 313 d, err := Open("", opts) 314 require.NoError(t, err) 315 { 316 wb := d.NewBatch() 317 err = wb.Set(key, value, nil) 318 require.NoError(t, err) 319 err = d.Apply(wb, NoSync) 320 require.NoError(t, err) 321 } 322 err = d.Checkpoint(checkpointPath, WithFlushedWAL()) 323 require.NoError(t, err) 324 require.NoError(t, d.Close()) 325 fs.ResetToSyncedState() 326 } 327 328 // Check that the WAL has been flushed in the checkpoint. 329 { 330 files, err := fs.List(checkpointPath) 331 require.NoError(t, err) 332 hasLogFile := false 333 for _, f := range files { 334 info, err := fs.Stat(fs.PathJoin(checkpointPath, f)) 335 require.NoError(t, err) 336 if strings.HasSuffix(f, ".log") { 337 hasLogFile = true 338 require.NotZero(t, info.Size()) 339 } 340 } 341 require.True(t, hasLogFile) 342 } 343 344 // Check that the checkpoint contains the expected data. 345 { 346 d, err := Open(checkpointPath, opts) 347 require.NoError(t, err) 348 iter, _ := d.NewIter(nil) 349 require.True(t, iter.First()) 350 require.Equal(t, key, iter.Key()) 351 require.Equal(t, value, iter.Value()) 352 require.False(t, iter.Next()) 353 require.NoError(t, iter.Close()) 354 require.NoError(t, d.Close()) 355 } 356 } 357 358 func TestCheckpointManyFiles(t *testing.T) { 359 if testing.Short() { 360 t.Skip("skipping because of short flag") 361 } 362 const checkpointPath = "checkpoint" 363 opts := &Options{ 364 FS: vfs.NewMem(), 365 FormatMajorVersion: internalFormatNewest, 366 DisableAutomaticCompactions: true, 367 } 368 // Disable compression to speed up the test. 369 opts.EnsureDefaults() 370 for i := range opts.Levels { 371 opts.Levels[i].Compression = NoCompression 372 } 373 374 d, err := Open("", opts) 375 require.NoError(t, err) 376 defer d.Close() 377 378 mkKey := func(x int) []byte { 379 return []byte(fmt.Sprintf("key%06d", x)) 380 } 381 // We want to test the case where the appended record with the excluded files 382 // makes the manifest cross 32KB. This will happen for a range of values 383 // around 450. 384 n := 400 + rand.Intn(100) 385 for i := 0; i < n; i++ { 386 err := d.Set(mkKey(i), nil, nil) 387 require.NoError(t, err) 388 err = d.Flush() 389 require.NoError(t, err) 390 } 391 err = d.Checkpoint(checkpointPath, WithRestrictToSpans([]CheckpointSpan{ 392 { 393 Start: mkKey(0), 394 End: mkKey(10), 395 }, 396 })) 397 require.NoError(t, err) 398 399 // Open the checkpoint and iterate through all the keys. 400 { 401 d, err := Open(checkpointPath, opts) 402 require.NoError(t, err) 403 iter, _ := d.NewIter(nil) 404 require.True(t, iter.First()) 405 require.NoError(t, iter.Error()) 406 n := 1 407 for iter.Next() { 408 n++ 409 } 410 require.NoError(t, iter.Error()) 411 require.NoError(t, iter.Close()) 412 require.NoError(t, d.Close()) 413 require.Equal(t, 10, n) 414 } 415 }