github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/storageccl/import_test.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Licensed as a CockroachDB Enterprise file under the Cockroach Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt 8 9 package storageccl 10 11 import ( 12 "context" 13 "fmt" 14 "io/ioutil" 15 "os" 16 "path/filepath" 17 "reflect" 18 "strconv" 19 "sync/atomic" 20 "testing" 21 "time" 22 23 "github.com/cockroachdb/cockroach/pkg/base" 24 "github.com/cockroachdb/cockroach/pkg/keys" 25 "github.com/cockroachdb/cockroach/pkg/kv" 26 "github.com/cockroachdb/cockroach/pkg/kv/kvserver" 27 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverbase" 28 "github.com/cockroachdb/cockroach/pkg/roachpb" 29 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 30 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 31 "github.com/cockroachdb/cockroach/pkg/storage" 32 "github.com/cockroachdb/cockroach/pkg/storage/cloud" 33 "github.com/cockroachdb/cockroach/pkg/testutils" 34 "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" 35 "github.com/cockroachdb/cockroach/pkg/util/encoding" 36 "github.com/cockroachdb/cockroach/pkg/util/hlc" 37 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 38 "github.com/cockroachdb/errors" 39 ) 40 41 func TestMaxImportBatchSize(t *testing.T) { 42 defer leaktest.AfterTest(t)() 43 44 testCases := []struct { 45 importBatchSize int64 46 maxCommandSize int64 47 expected int64 48 }{ 49 {importBatchSize: 2 << 20, maxCommandSize: 64 << 20, expected: 2 << 20}, 50 {importBatchSize: 128 << 20, maxCommandSize: 64 << 20, expected: 63 << 20}, 51 {importBatchSize: 64 << 20, maxCommandSize: 64 << 20, expected: 63 << 20}, 52 {importBatchSize: 63 << 20, maxCommandSize: 64 << 20, expected: 63 << 20}, 53 } 54 for i, testCase := range testCases { 55 st := cluster.MakeTestingClusterSettings() 56 importBatchSize.Override(&st.SV, testCase.importBatchSize) 57 kvserver.MaxCommandSize.Override(&st.SV, testCase.maxCommandSize) 58 if e, a := MaxImportBatchSize(st), testCase.expected; e != a { 59 t.Errorf("%d: expected max batch size %d, but got %d", i, e, a) 60 } 61 } 62 } 63 64 func slurpSSTablesLatestKey( 65 t *testing.T, dir string, paths []string, kr prefixRewriter, 66 ) []storage.MVCCKeyValue { 67 start, end := storage.MVCCKey{Key: keys.MinKey}, storage.MVCCKey{Key: keys.MaxKey} 68 69 e := storage.NewDefaultInMem() 70 defer e.Close() 71 batch := e.NewBatch() 72 defer batch.Close() 73 74 for _, path := range paths { 75 sst := storage.MakeRocksDBSstFileReader() 76 defer sst.Close() 77 78 fileContents, err := ioutil.ReadFile(filepath.Join(dir, path)) 79 if err != nil { 80 t.Fatalf("%+v", err) 81 } 82 if err := sst.IngestExternalFile(fileContents); err != nil { 83 t.Fatalf("%+v", err) 84 } 85 if err := sst.Iterate(start.Key, end.Key, func(kv storage.MVCCKeyValue) (bool, error) { 86 var ok bool 87 kv.Key.Key, ok = kr.rewriteKey(kv.Key.Key) 88 if !ok { 89 return true, errors.Errorf("could not rewrite key: %s", kv.Key.Key) 90 } 91 v := roachpb.Value{RawBytes: kv.Value} 92 v.ClearChecksum() 93 v.InitChecksum(kv.Key.Key) 94 if err := batch.Put(kv.Key, v.RawBytes); err != nil { 95 return true, err 96 } 97 return false, nil 98 }); err != nil { 99 t.Fatalf("%+v", err) 100 } 101 } 102 103 var kvs []storage.MVCCKeyValue 104 it := batch.NewIterator(storage.IterOptions{UpperBound: roachpb.KeyMax}) 105 defer it.Close() 106 for it.SeekGE(start); ; it.NextKey() { 107 if ok, err := it.Valid(); err != nil { 108 t.Fatal(err) 109 } else if !ok || !it.UnsafeKey().Less(end) { 110 break 111 } 112 kvs = append(kvs, storage.MVCCKeyValue{Key: it.Key(), Value: it.Value()}) 113 } 114 return kvs 115 } 116 117 func clientKVsToEngineKVs(kvs []kv.KeyValue) []storage.MVCCKeyValue { 118 var ret []storage.MVCCKeyValue 119 for _, kv := range kvs { 120 if kv.Value == nil { 121 continue 122 } 123 k := storage.MVCCKey{ 124 Key: kv.Key, 125 Timestamp: kv.Value.Timestamp, 126 } 127 ret = append(ret, storage.MVCCKeyValue{Key: k, Value: kv.Value.RawBytes}) 128 } 129 return ret 130 } 131 132 func TestImport(t *testing.T) { 133 defer leaktest.AfterTest(t)() 134 t.Run("batch=default", func(t *testing.T) { 135 runTestImport(t, func(_ *cluster.Settings) {}) 136 }) 137 t.Run("batch=1", func(t *testing.T) { 138 // The test normally doesn't trigger the batching behavior, so lower 139 // the threshold to force it. 140 init := func(st *cluster.Settings) { 141 importBatchSize.Override(&st.SV, 1) 142 } 143 runTestImport(t, init) 144 }) 145 } 146 147 func runTestImport(t *testing.T, init func(*cluster.Settings)) { 148 defer leaktest.AfterTest(t)() 149 150 dir, dirCleanupFn := testutils.TempDir(t) 151 defer dirCleanupFn() 152 153 if err := os.Mkdir(filepath.Join(dir, "foo"), 0755); err != nil { 154 t.Fatal(err) 155 } 156 157 const ( 158 oldID = 51 159 indexID = 1 160 ) 161 162 srcPrefix := makeKeyRewriterPrefixIgnoringInterleaved(oldID, indexID) 163 var keys []roachpb.Key 164 for i := 0; i < 8; i++ { 165 key := append([]byte(nil), srcPrefix...) 166 key = encoding.EncodeStringAscending(key, fmt.Sprintf("k%d", i)) 167 keys = append(keys, key) 168 } 169 170 writeSST := func(t *testing.T, offsets []int) string { 171 path := strconv.FormatInt(hlc.UnixNano(), 10) 172 173 sstFile := &storage.MemFile{} 174 sst := storage.MakeBackupSSTWriter(sstFile) 175 defer sst.Close() 176 ts := hlc.NewClock(hlc.UnixNano, time.Nanosecond).Now() 177 value := roachpb.MakeValueFromString("bar") 178 for _, idx := range offsets { 179 key := keys[idx] 180 value.ClearChecksum() 181 value.InitChecksum(key) 182 if err := sst.Put(storage.MVCCKey{Key: key, Timestamp: ts}, value.RawBytes); err != nil { 183 t.Fatalf("%+v", err) 184 } 185 } 186 if err := sst.Finish(); err != nil { 187 t.Fatalf("%+v", err) 188 } 189 if err := ioutil.WriteFile(filepath.Join(dir, "foo", path), sstFile.Data(), 0644); err != nil { 190 t.Fatalf("%+v", err) 191 } 192 return path 193 } 194 195 // Make the first few WriteBatch/AddSSTable calls return 196 // AmbiguousResultError. Import should be resilient to this. 197 const initialAmbiguousSubReqs = 3 198 remainingAmbiguousSubReqs := int64(initialAmbiguousSubReqs) 199 knobs := base.TestingKnobs{Store: &kvserver.StoreTestingKnobs{ 200 EvalKnobs: kvserverbase.BatchEvalTestingKnobs{ 201 TestingEvalFilter: func(filterArgs kvserverbase.FilterArgs) *roachpb.Error { 202 switch filterArgs.Req.(type) { 203 case *roachpb.WriteBatchRequest, *roachpb.AddSSTableRequest: 204 // No-op. 205 default: 206 return nil 207 } 208 r := atomic.AddInt64(&remainingAmbiguousSubReqs, -1) 209 if r < 0 { 210 return nil 211 } 212 return roachpb.NewError(roachpb.NewAmbiguousResultError(strconv.Itoa(int(r)))) 213 }, 214 }, 215 }} 216 217 ctx := context.Background() 218 args := base.TestServerArgs{Knobs: knobs, ExternalIODir: dir} 219 // TODO(dan): This currently doesn't work with AddSSTable on in-memory 220 // stores because RocksDB's InMemoryEnv doesn't support NewRandomRWFile 221 // (which breaks the global-seqno rewrite used when the added sstable 222 // overlaps with existing data in the RocksDB instance). #16345. 223 args.StoreSpecs = []base.StoreSpec{{InMemory: false, Path: filepath.Join(dir, "testserver")}} 224 s, _, kvDB := serverutils.StartServer(t, args) 225 defer s.Stopper().Stop(ctx) 226 init(s.ClusterSettings()) 227 228 storage, err := cloud.ExternalStorageConfFromURI("nodelocal://0/foo") 229 if err != nil { 230 t.Fatalf("%+v", err) 231 } 232 233 const splitKey1, splitKey2 = 3, 5 234 // Each test case consists of some number of batches of keys, represented as 235 // ints [0, 8). Splits are at 3 and 5. 236 for i, testCase := range [][][]int{ 237 // Simple cases, no spanning splits, try first, last, middle, etc in each. 238 // r1 239 {{0}}, 240 {{1}}, 241 {{2}}, 242 {{0, 1, 2}}, 243 {{0}, {1}, {2}}, 244 245 // r2 246 {{3}}, 247 {{4}}, 248 {{3, 4}}, 249 {{3}, {4}}, 250 251 // r3 252 {{5}}, 253 {{5, 6, 7}}, 254 {{6}}, 255 256 // batches exactly matching spans. 257 {{0, 1, 2}, {3, 4}, {5, 6, 7}}, 258 259 // every key, in its own batch. 260 {{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}}, 261 262 // every key in one big batch. 263 {{0, 1, 2, 3, 4, 5, 6, 7}}, 264 265 // Look for off-by-ones on and around the splits. 266 {{2, 3}}, 267 {{1, 3}}, 268 {{2, 4}}, 269 {{1, 4}}, 270 {{1, 5}}, 271 {{2, 5}}, 272 273 // Mixture of split-aligned and non-aligned batches. 274 {{1}, {5}, {6}}, 275 {{1, 2, 3}, {4, 5}, {6, 7}}, 276 {{0}, {2, 3, 5}, {7}}, 277 {{0, 4}, {5, 7}}, 278 {{0, 3}, {4}}, 279 } { 280 t.Run(fmt.Sprintf("%d-%v", i, testCase), func(t *testing.T) { 281 newID := sqlbase.ID(100 + i) 282 kr := prefixRewriter{{ 283 OldPrefix: srcPrefix, 284 NewPrefix: makeKeyRewriterPrefixIgnoringInterleaved(newID, indexID), 285 }} 286 rekeys := []roachpb.ImportRequest_TableRekey{ 287 { 288 OldID: oldID, 289 NewDesc: mustMarshalDesc(t, &sqlbase.TableDescriptor{ 290 ID: newID, 291 PrimaryIndex: sqlbase.IndexDescriptor{ 292 ID: indexID, 293 }, 294 }), 295 }, 296 } 297 298 first := keys[testCase[0][0]] 299 last := keys[testCase[len(testCase)-1][len(testCase[len(testCase)-1])-1]] 300 301 reqStartKey, ok := kr.rewriteKey(append([]byte(nil), keys[0]...)) 302 if !ok { 303 t.Fatalf("failed to rewrite key: %s", reqStartKey) 304 } 305 reqEndKey, ok := kr.rewriteKey(append([]byte(nil), keys[len(keys)-1].PrefixEnd()...)) 306 if !ok { 307 t.Fatalf("failed to rewrite key: %s", reqEndKey) 308 } 309 reqMidKey1, ok := kr.rewriteKey(append([]byte(nil), keys[splitKey1]...)) 310 if !ok { 311 t.Fatalf("failed to rewrite key: %s", reqMidKey1) 312 } 313 reqMidKey2, ok := kr.rewriteKey(append([]byte(nil), keys[splitKey2]...)) 314 if !ok { 315 t.Fatalf("failed to rewrite key: %s", reqMidKey2) 316 } 317 318 if err := kvDB.AdminSplit(ctx, reqMidKey1, reqMidKey1, hlc.MaxTimestamp /* expirationTime */); err != nil { 319 t.Fatal(err) 320 } 321 if err := kvDB.AdminSplit(ctx, reqMidKey2, reqMidKey2, hlc.MaxTimestamp /* expirationTime */); err != nil { 322 t.Fatal(err) 323 } 324 325 atomic.StoreInt64(&remainingAmbiguousSubReqs, initialAmbiguousSubReqs) 326 327 req := &roachpb.ImportRequest{ 328 RequestHeader: roachpb.RequestHeader{Key: reqStartKey}, 329 DataSpan: roachpb.Span{Key: first, EndKey: last.PrefixEnd()}, 330 Rekeys: rekeys, 331 } 332 333 var slurp []string 334 for ks := range testCase { 335 f := writeSST(t, testCase[ks]) 336 slurp = append(slurp, f) 337 req.Files = append(req.Files, roachpb.ImportRequest_File{Dir: storage, Path: f}) 338 } 339 expectedKVs := slurpSSTablesLatestKey(t, filepath.Join(dir, "foo"), slurp, kr) 340 341 // Import may be retried by DistSender if it takes too long to return, so 342 // make sure it's idempotent. 343 for j := 0; j < 2; j++ { 344 b := &kv.Batch{} 345 b.AddRawRequest(req) 346 if err := kvDB.Run(ctx, b); err != nil { 347 t.Fatalf("%+v", err) 348 } 349 clientKVs, err := kvDB.Scan(ctx, reqStartKey, reqEndKey, 0) 350 if err != nil { 351 t.Fatalf("%+v", err) 352 } 353 kvs := clientKVsToEngineKVs(clientKVs) 354 355 if !reflect.DeepEqual(kvs, expectedKVs) { 356 for i := 0; i < len(kvs) || i < len(expectedKVs); i++ { 357 if i < len(expectedKVs) { 358 t.Logf("expected %d\t%v\t%v", i, expectedKVs[i].Key, expectedKVs[i].Value) 359 } 360 if i < len(kvs) { 361 t.Logf("got %d\t%v\t%v", i, kvs[i].Key, kvs[i].Value) 362 } 363 } 364 t.Fatalf("got %+v expected %+v", kvs, expectedKVs) 365 } 366 } 367 368 if r := atomic.LoadInt64(&remainingAmbiguousSubReqs); r > 0 { 369 t.Errorf("expected ambiguous sub-requests to be depleted got %d", r) 370 } 371 }) 372 } 373 }