github.com/pingcap/badger@v1.5.1-0.20230103063557-828f39b09b6d/manifest_test.go (about) 1 /* 2 * Copyright 2017 Dgraph Labs, Inc. and Contributors 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 badger 18 19 import ( 20 "fmt" 21 "io/ioutil" 22 "math/rand" 23 "os" 24 "path/filepath" 25 "sort" 26 "testing" 27 28 "github.com/pingcap/badger/cache" 29 "github.com/pingcap/badger/epoch" 30 "github.com/pingcap/badger/options" 31 "github.com/pingcap/badger/protos" 32 "github.com/pingcap/badger/table/sstable" 33 "github.com/pingcap/badger/y" 34 "github.com/stretchr/testify/require" 35 ) 36 37 func testCache() *cache.Cache { 38 c, err := cache.NewCache(&cache.Config{ 39 NumCounters: 1000000 * 10, 40 MaxCost: 1000000, 41 BufferItems: 64, 42 Metrics: true, 43 }) 44 y.Check(err) 45 return c 46 } 47 48 func TestManifestBasic(t *testing.T) { 49 dir, err := ioutil.TempDir("", "badger") 50 require.NoError(t, err) 51 defer os.RemoveAll(dir) 52 53 opt := getTestOptions(dir) 54 { 55 kv, err := Open(opt) 56 require.NoError(t, err) 57 n := 5000 58 for i := 0; i < n; i++ { 59 if (i % 10000) == 0 { 60 fmt.Printf("Putting i=%d\n", i) 61 } 62 k := []byte(fmt.Sprintf("%16x", rand.Int63())) 63 txnSet(t, kv, k, k, 0x00) 64 } 65 txnSet(t, kv, []byte("testkey"), []byte("testval"), 0x05) 66 kv.validate() 67 require.NoError(t, kv.Close()) 68 } 69 70 kv, err := Open(opt) 71 require.NoError(t, err) 72 73 require.NoError(t, kv.View(func(txn *Txn) error { 74 item, err := txn.Get([]byte("testkey")) 75 require.NoError(t, err) 76 require.EqualValues(t, "testval", string(getItemValue(t, item))) 77 require.EqualValues(t, []byte{0x05}, item.UserMeta()) 78 return nil 79 })) 80 require.NoError(t, kv.Close()) 81 } 82 83 func helpTestManifestFileCorruption(t *testing.T, off int64, errorContent string) { 84 dir, err := ioutil.TempDir("", "badger") 85 require.NoError(t, err) 86 defer os.RemoveAll(dir) 87 88 opt := getTestOptions(dir) 89 { 90 kv, err := Open(opt) 91 require.NoError(t, err) 92 require.NoError(t, kv.Close()) 93 } 94 fp, err := os.OpenFile(filepath.Join(dir, ManifestFilename), os.O_RDWR, 0) 95 require.NoError(t, err) 96 // Mess with magic value or version to force error 97 _, err = fp.WriteAt([]byte{'X'}, off) 98 require.NoError(t, err) 99 require.NoError(t, fp.Close()) 100 kv, err := Open(opt) 101 defer func() { 102 if kv != nil { 103 kv.Close() 104 } 105 }() 106 require.Error(t, err) 107 require.Contains(t, err.Error(), errorContent) 108 } 109 110 func TestManifestMagic(t *testing.T) { 111 helpTestManifestFileCorruption(t, 3, "bad magic") 112 } 113 114 func TestManifestVersion(t *testing.T) { 115 helpTestManifestFileCorruption(t, 4, "unsupported version") 116 } 117 118 func key(prefix string, i int) string { 119 return prefix + fmt.Sprintf("%04d", i) 120 } 121 122 func buildTestTable(t *testing.T, prefix string, n int) *os.File { 123 y.Assert(n <= 10000) 124 keyValues := make([][]string, n) 125 for i := 0; i < n; i++ { 126 k := key(prefix, i) 127 v := fmt.Sprintf("%d", i) 128 keyValues[i] = []string{k, v} 129 } 130 return buildTable(t, keyValues) 131 } 132 133 // TODO - Move these to somewhere where table package can also use it. 134 // keyValues is n by 2 where n is number of pairs. 135 func buildTable(t *testing.T, keyValues [][]string) *os.File { 136 // TODO: Add test for file garbage collection here. No files should be left after the tests here. 137 138 filename := fmt.Sprintf("%s%s%x.sst", os.TempDir(), string(os.PathSeparator), rand.Uint32()) 139 f, err := y.OpenSyncedFile(filename, false) 140 if t != nil { 141 require.NoError(t, err) 142 } else { 143 y.Check(err) 144 } 145 146 sort.Slice(keyValues, func(i, j int) bool { 147 return keyValues[i][0] < keyValues[j][0] 148 }) 149 150 opts := DefaultOptions.TableBuilderOptions 151 opts.CompressionPerLevel = getTestCompression(options.ZSTD) 152 b := sstable.NewTableBuilder(f, nil, 0, opts) 153 defer b.Close() 154 for _, kv := range keyValues { 155 y.Assert(len(kv) == 2) 156 err := b.Add(y.KeyWithTs([]byte(kv[0]), 10), y.ValueStruct{ 157 Value: []byte(kv[1]), 158 Meta: 'A', 159 }) 160 if t != nil { 161 require.NoError(t, err) 162 } else { 163 y.Check(err) 164 } 165 } 166 _, err = b.Finish() 167 y.Check(err) 168 f.Close() 169 f, _ = y.OpenSyncedFile(filename, true) 170 return f 171 } 172 173 func TestOverlappingKeyRangeError(t *testing.T) { 174 dir, err := ioutil.TempDir("", "badger") 175 require.NoError(t, err) 176 defer os.RemoveAll(dir) 177 opt := DefaultOptions 178 opt.Dir = dir 179 opt.ValueDir = dir 180 kv, err := Open(opt) 181 require.NoError(t, err) 182 defer kv.Close() 183 blkCache, idxCache := testCache(), testCache() 184 185 lh0 := newLevelHandler(kv, 0) 186 lh1 := newLevelHandler(kv, 1) 187 f := buildTestTable(t, "k", 2) 188 t1, err := sstable.OpenTable(f.Name(), blkCache, idxCache) 189 require.NoError(t, err) 190 defer t1.Delete() 191 192 done := lh0.tryAddLevel0Table(t1) 193 require.Equal(t, true, done) 194 195 cd := &CompactDef{ 196 Level: 0, 197 } 198 199 closer := y.NewCloser(0) 200 defer closer.SignalAndWait() 201 rm := epoch.NewResourceManager(closer, epoch.NoOpInspector{}) 202 g := rm.Acquire() 203 defer g.Done() 204 manifest := createManifest() 205 opts := DefaultOptions.TableBuilderOptions 206 lc, err := newLevelsController(kv, &manifest, rm, opts) 207 require.NoError(t, err) 208 done = cd.fillTablesL0(&lc.cstatus, lh0, lh1) 209 require.Equal(t, true, done) 210 lc.runCompactDef(cd, g) 211 212 f = buildTestTable(t, "l", 2) 213 t2, err := sstable.OpenTable(f.Name(), blkCache, idxCache) 214 require.NoError(t, err) 215 defer t2.Delete() 216 done = lh0.tryAddLevel0Table(t2) 217 require.Equal(t, true, done) 218 219 cd = &CompactDef{ 220 Level: 0, 221 } 222 cd.fillTablesL0(&lc.cstatus, lh0, lh1) 223 lc.runCompactDef(cd, g) 224 } 225 226 func TestManifestRewrite(t *testing.T) { 227 dir, err := ioutil.TempDir("", "badger") 228 require.NoError(t, err) 229 defer os.RemoveAll(dir) 230 deletionsThreshold := 10 231 mf, m, err := helpOpenOrCreateManifestFile(dir, false, deletionsThreshold) 232 defer func() { 233 if mf != nil { 234 mf.close() 235 } 236 }() 237 require.NoError(t, err) 238 require.Equal(t, 0, m.Creations) 239 require.Equal(t, 0, m.Deletions) 240 head := &protos.HeadInfo{ 241 Version: 1, 242 LogID: 1, 243 LogOffset: 1, 244 } 245 err = mf.addChanges([]*protos.ManifestChange{ 246 newCreateChange(0, 0), 247 }, head) 248 require.NoError(t, err) 249 require.NotNil(t, mf.manifest.Head) 250 251 for i := uint64(0); i < uint64(deletionsThreshold*3); i++ { 252 ch := []*protos.ManifestChange{ 253 newCreateChange(i+1, 0), 254 newDeleteChange(i), 255 } 256 // Only add head for some change set to make sure head is not overwritten to nil. 257 if i < uint64(deletionsThreshold) { 258 head = &protos.HeadInfo{ 259 Version: i, 260 LogID: uint32(i), 261 LogOffset: uint32(i), 262 } 263 err := mf.addChanges(ch, head) 264 require.NoError(t, err) 265 } else { 266 err := mf.addChanges(ch, nil) 267 require.NoError(t, err) 268 } 269 } 270 err = mf.close() 271 require.NoError(t, err) 272 mf = nil 273 mf, m, err = helpOpenOrCreateManifestFile(dir, false, deletionsThreshold) 274 require.NoError(t, err) 275 require.Equal(t, map[uint64]tableManifest{ 276 uint64(deletionsThreshold * 3): {Level: 0}, 277 }, m.Tables) 278 require.NotNil(t, m.Head) 279 require.Equal(t, *m.Head, *head) 280 }