YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/yb/rocksdb/util/delete_scheduler_test.cc
Line
Count
Source (jump to first uncovered line)
1
//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2
//  This source code is licensed under the BSD-style license found in the
3
//  LICENSE file in the root directory of this source tree. An additional grant
4
//  of patent rights can be found in the PATENTS file in the same directory.
5
//
6
// The following only applies to changes made to this file as part of YugaByte development.
7
//
8
// Portions Copyright (c) YugaByte, Inc.
9
//
10
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
11
// in compliance with the License.  You may obtain a copy of the License at
12
//
13
// http://www.apache.org/licenses/LICENSE-2.0
14
//
15
// Unless required by applicable law or agreed to in writing, software distributed under the License
16
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
17
// or implied.  See the License for the specific language governing permissions and limitations
18
// under the License.
19
//
20
21
#ifndef __STDC_FORMAT_MACROS
22
#define __STDC_FORMAT_MACROS
23
#endif
24
25
#include <inttypes.h>
26
#include <atomic>
27
#include <thread>
28
#include <vector>
29
30
#include "yb/rocksdb/env.h"
31
#include "yb/rocksdb/util/delete_scheduler.h"
32
#include "yb/rocksdb/util/sync_point.h"
33
#include "yb/rocksdb/util/testharness.h"
34
#include "yb/rocksdb/util/testutil.h"
35
36
#include "yb/util/string_util.h"
37
#include "yb/util/test_util.h"
38
39
namespace rocksdb {
40
41
class DeleteSchedulerTest : public RocksDBTest {
42
 public:
43
8
  DeleteSchedulerTest() : env_(Env::Default()) {
44
8
    dummy_files_dir_ = test::TmpDir(env_) + "/dummy_data_dir";
45
8
    DestroyAndCreateDir(dummy_files_dir_);
46
8
    trash_dir_ = test::TmpDir(env_) + "/trash";
47
8
    DestroyAndCreateDir(trash_dir_);
48
8
  }
49
50
8
  ~DeleteSchedulerTest() {
51
8
    rocksdb::SyncPoint::GetInstance()->DisableProcessing();
52
8
    rocksdb::SyncPoint::GetInstance()->LoadDependency({});
53
8
    rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
54
8
    DestroyDir(dummy_files_dir_);
55
8
  }
56
57
35
  void DestroyDir(const std::string& dir) {
58
35
    if (env_->FileExists(dir).IsNotFound()) {
59
16
      return;
60
16
    }
61
19
    std::vector<std::string> files_in_dir;
62
19
    EXPECT_OK(env_->GetChildren(dir, &files_in_dir));
63
38
    for (auto& file_in_dir : files_in_dir) {
64
38
      if (file_in_dir == "." || file_in_dir == "..") {
65
38
        continue;
66
38
      }
67
0
      EXPECT_OK(env_->DeleteFile(dir + "/" + file_in_dir));
68
0
    }
69
19
    EXPECT_OK(env_->DeleteDir(dir));
70
19
  }
71
72
26
  void DestroyAndCreateDir(const std::string& dir) {
73
26
    DestroyDir(dir);
74
26
    EXPECT_OK(env_->CreateDir(dir));
75
26
  }
76
77
57
  int CountFilesInDir(const std::string& dir) {
78
57
    std::vector<std::string> files_in_dir;
79
57
    EXPECT_OK(env_->GetChildren(dir, &files_in_dir));
80
    // Ignore "." and ".."
81
57
    return static_cast<int>(files_in_dir.size()) - 2;
82
57
  }
83
84
1.19k
  std::string NewDummyFile(const std::string& file_name, uint64_t size = 1024) {
85
1.19k
    std::string file_path = dummy_files_dir_ + "/" + file_name;
86
1.19k
    std::unique_ptr<WritableFile> f;
87
1.19k
    EXPECT_OK(env_->NewWritableFile(file_path, &f, EnvOptions()));
88
1.19k
    std::string data(size, 'A');
89
1.19k
    EXPECT_OK(f->Append(data));
90
1.19k
    EXPECT_OK(f->Close());
91
1.19k
    return file_path;
92
1.19k
  }
93
94
16
  void NewDeleteScheduler() {
95
16
    ASSERT_OK(env_->CreateDirIfMissing(trash_dir_));
96
16
    delete_scheduler_.reset(new DeleteScheduler(
97
16
        env_, trash_dir_, rate_bytes_per_sec_, nullptr, nullptr));
98
16
  }
99
100
  Env* env_;
101
  std::string dummy_files_dir_;
102
  std::string trash_dir_;
103
  int64_t rate_bytes_per_sec_;
104
  std::shared_ptr<DeleteScheduler> delete_scheduler_;
105
};
106
107
// Test the basic functionality of DeleteScheduler (Rate Limiting).
108
// 1- Create 100 dummy files
109
// 2- Delete the 100 dummy files using DeleteScheduler
110
// --- Hold DeleteScheduler::BackgroundEmptyTrash ---
111
// 3- Wait for DeleteScheduler to delete all files in trash
112
// 4- Verify that BackgroundEmptyTrash used to correct penlties for the files
113
// 5- Make sure that all created files were completely deleted
114
1
TEST_F(DeleteSchedulerTest, BasicRateLimiting) {
115
1
  rocksdb::SyncPoint::GetInstance()->LoadDependency({
116
1
      {"DeleteSchedulerTest::BasicRateLimiting:1",
117
1
       "DeleteScheduler::BackgroundEmptyTrash"},
118
1
  });
119
120
1
  std::vector<uint64_t> penalties;
121
1
  rocksdb::SyncPoint::GetInstance()->SetCallBack(
122
1
      "DeleteScheduler::BackgroundEmptyTrash:Wait",
123
500
      [&](void* arg) { penalties.push_back(*(static_cast<int*>(arg))); });
124
125
1
  int num_files = 100;  // 100 files
126
1
  uint64_t file_size = 1024;  // every file is 1 kb
127
1
  std::vector<uint64_t> delete_kbs_per_sec = {512, 200, 100, 50, 25};
128
129
6
  for (size_t t = 0; t < delete_kbs_per_sec.size(); t++) {
130
5
    penalties.clear();
131
5
    rocksdb::SyncPoint::GetInstance()->ClearTrace();
132
5
    rocksdb::SyncPoint::GetInstance()->EnableProcessing();
133
134
5
    DestroyAndCreateDir(dummy_files_dir_);
135
5
    rate_bytes_per_sec_ = delete_kbs_per_sec[t] * 1024;
136
5
    NewDeleteScheduler();
137
138
    // Create 100 dummy files, every file is 1 Kb
139
5
    std::vector<std::string> generated_files;
140
505
    for (int i = 0; i < num_files; i++) {
141
500
      std::string file_name = "file" + ToString(i) + ".data";
142
500
      generated_files.push_back(NewDummyFile(file_name, file_size));
143
500
    }
144
145
    // Delete dummy files and measure time spent to empty trash
146
505
    for (int i = 0; i < num_files; i++) {
147
500
      ASSERT_OK(delete_scheduler_->DeleteFile(generated_files[i]));
148
500
    }
149
5
    ASSERT_EQ(CountFilesInDir(dummy_files_dir_), 0);
150
151
5
    uint64_t delete_start_time = env_->NowMicros();
152
5
    TEST_SYNC_POINT("DeleteSchedulerTest::BasicRateLimiting:1");
153
5
    delete_scheduler_->WaitForEmptyTrash();
154
5
    uint64_t time_spent_deleting = env_->NowMicros() - delete_start_time;
155
156
5
    auto bg_errors = delete_scheduler_->GetBackgroundErrors();
157
5
    ASSERT_EQ(bg_errors.size(), 0);
158
159
5
    uint64_t total_files_size = 0;
160
5
    uint64_t expected_penlty = 0;
161
5
    ASSERT_EQ(penalties.size(), num_files);
162
505
    for (int i = 0; i < num_files; i++) {
163
500
      total_files_size += file_size;
164
500
      expected_penlty = ((total_files_size * 1000000) / rate_bytes_per_sec_);
165
500
      ASSERT_EQ(expected_penlty, penalties[i]);
166
500
    }
167
5
    ASSERT_GT(time_spent_deleting, expected_penlty * 0.9);
168
169
5
    ASSERT_EQ(CountFilesInDir(trash_dir_), 0);
170
5
    rocksdb::SyncPoint::GetInstance()->DisableProcessing();
171
5
  }
172
1
}
173
174
// Same as the BasicRateLimiting test but delete files in multiple threads.
175
// 1- Create 100 dummy files
176
// 2- Delete the 100 dummy files using DeleteScheduler using 10 threads
177
// --- Hold DeleteScheduler::BackgroundEmptyTrash ---
178
// 3- Wait for DeleteScheduler to delete all files in queue
179
// 4- Verify that BackgroundEmptyTrash used to correct penlties for the files
180
// 5- Make sure that all created files were completely deleted
181
1
TEST_F(DeleteSchedulerTest, RateLimitingMultiThreaded) {
182
1
  rocksdb::SyncPoint::GetInstance()->LoadDependency({
183
1
      {"DeleteSchedulerTest::RateLimitingMultiThreaded:1",
184
1
       "DeleteScheduler::BackgroundEmptyTrash"},
185
1
  });
186
187
1
  std::vector<uint64_t> penalties;
188
1
  rocksdb::SyncPoint::GetInstance()->SetCallBack(
189
1
      "DeleteScheduler::BackgroundEmptyTrash:Wait",
190
500
      [&](void* arg) { penalties.push_back(*(static_cast<int*>(arg))); });
191
192
1
  int thread_cnt = 10;
193
1
  int num_files = 10;  // 10 files per thread
194
1
  uint64_t file_size = 1024;  // every file is 1 kb
195
196
1
  std::vector<uint64_t> delete_kbs_per_sec = {512, 200, 100, 50, 25};
197
6
  for (size_t t = 0; t < delete_kbs_per_sec.size(); t++) {
198
5
    penalties.clear();
199
5
    rocksdb::SyncPoint::GetInstance()->ClearTrace();
200
5
    rocksdb::SyncPoint::GetInstance()->EnableProcessing();
201
202
5
    DestroyAndCreateDir(dummy_files_dir_);
203
5
    rate_bytes_per_sec_ = delete_kbs_per_sec[t] * 1024;
204
5
    NewDeleteScheduler();
205
206
    // Create 100 dummy files, every file is 1 Kb
207
5
    std::vector<std::string> generated_files;
208
505
    for (int i = 0; i < num_files * thread_cnt; i++) {
209
500
      std::string file_name = "file" + ToString(i) + ".data";
210
500
      generated_files.push_back(NewDummyFile(file_name, file_size));
211
500
    }
212
213
    // Delete dummy files using 10 threads and measure time spent to empty trash
214
5
    std::atomic<int> thread_num(0);
215
5
    std::vector<std::thread> threads;
216
50
    std::function<void()> delete_thread = [&]() {
217
50
      int idx = thread_num.fetch_add(1);
218
50
      int range_start = idx * num_files;
219
50
      int range_end = range_start + num_files;
220
550
      for (int j = range_start; j < range_end; j++) {
221
500
        ASSERT_OK(delete_scheduler_->DeleteFile(generated_files[j]));
222
500
      }
223
50
    };
224
225
55
    for (int i = 0; i < thread_cnt; i++) {
226
50
      threads.emplace_back(delete_thread);
227
50
    }
228
229
55
    for (size_t i = 0; i < threads.size(); i++) {
230
50
      threads[i].join();
231
50
    }
232
233
5
    uint64_t delete_start_time = env_->NowMicros();
234
5
    TEST_SYNC_POINT("DeleteSchedulerTest::RateLimitingMultiThreaded:1");
235
5
    delete_scheduler_->WaitForEmptyTrash();
236
5
    uint64_t time_spent_deleting = env_->NowMicros() - delete_start_time;
237
238
5
    auto bg_errors = delete_scheduler_->GetBackgroundErrors();
239
5
    ASSERT_EQ(bg_errors.size(), 0);
240
241
5
    uint64_t total_files_size = 0;
242
5
    uint64_t expected_penlty = 0;
243
5
    ASSERT_EQ(penalties.size(), num_files * thread_cnt);
244
505
    for (int i = 0; i < num_files * thread_cnt; i++) {
245
500
      total_files_size += file_size;
246
500
      expected_penlty = ((total_files_size * 1000000) / rate_bytes_per_sec_);
247
500
      ASSERT_EQ(expected_penlty, penalties[i]);
248
500
    }
249
5
    ASSERT_GT(time_spent_deleting, expected_penlty * 0.9);
250
251
5
    ASSERT_EQ(CountFilesInDir(dummy_files_dir_), 0);
252
5
    ASSERT_EQ(CountFilesInDir(trash_dir_), 0);
253
5
    rocksdb::SyncPoint::GetInstance()->DisableProcessing();
254
5
  }
255
1
}
256
257
// Disable rate limiting by setting rate_bytes_per_sec_ to 0 and make sure
258
// that when DeleteScheduler delete a file it delete it immediately and dont
259
// move it to trash
260
1
TEST_F(DeleteSchedulerTest, DisableRateLimiting) {
261
1
  int bg_delete_file = 0;
262
1
  rocksdb::SyncPoint::GetInstance()->SetCallBack(
263
1
      "DeleteScheduler::DeleteTrashFile:DeleteFile",
264
0
      [&](void* arg) { bg_delete_file++; });
265
266
1
  rocksdb::SyncPoint::GetInstance()->EnableProcessing();
267
268
1
  rate_bytes_per_sec_ = 0;
269
1
  NewDeleteScheduler();
270
271
11
  for (int i = 0; i < 10; i++) {
272
    // Every file we delete will be deleted immediately
273
10
    std::string dummy_file = NewDummyFile("dummy.data");
274
10
    ASSERT_OK(delete_scheduler_->DeleteFile(dummy_file));
275
10
    ASSERT_TRUE(env_->FileExists(dummy_file).IsNotFound());
276
10
    ASSERT_EQ(CountFilesInDir(dummy_files_dir_), 0);
277
10
    ASSERT_EQ(CountFilesInDir(trash_dir_), 0);
278
10
  }
279
280
1
  ASSERT_EQ(bg_delete_file, 0);
281
282
1
  rocksdb::SyncPoint::GetInstance()->DisableProcessing();
283
1
}
284
285
// Testing that moving files to trash with the same name is not a problem
286
// 1- Create 10 files with the same name "conflict.data"
287
// 2- Delete the 10 files using DeleteScheduler
288
// 3- Make sure that trash directory contain 10 files ("conflict.data" x 10)
289
// --- Hold DeleteScheduler::BackgroundEmptyTrash ---
290
// 4- Make sure that files are deleted from trash
291
1
TEST_F(DeleteSchedulerTest, ConflictNames) {
292
1
  rocksdb::SyncPoint::GetInstance()->LoadDependency({
293
1
      {"DeleteSchedulerTest::ConflictNames:1",
294
1
       "DeleteScheduler::BackgroundEmptyTrash"},
295
1
  });
296
1
  rocksdb::SyncPoint::GetInstance()->EnableProcessing();
297
298
1
  rate_bytes_per_sec_ = 1024 * 1024;  // 1 Mb/sec
299
1
  NewDeleteScheduler();
300
301
  // Create "conflict.data" and move it to trash 10 times
302
11
  for (int i = 0; i < 10; i++) {
303
10
    std::string dummy_file = NewDummyFile("conflict.data");
304
10
    ASSERT_OK(delete_scheduler_->DeleteFile(dummy_file));
305
10
  }
306
1
  ASSERT_EQ(CountFilesInDir(dummy_files_dir_), 0);
307
  // 10 files ("conflict.data" x 10) in trash
308
1
  ASSERT_EQ(CountFilesInDir(trash_dir_), 10);
309
310
  // Hold BackgroundEmptyTrash
311
1
  TEST_SYNC_POINT("DeleteSchedulerTest::ConflictNames:1");
312
1
  delete_scheduler_->WaitForEmptyTrash();
313
1
  ASSERT_EQ(CountFilesInDir(trash_dir_), 0);
314
315
1
  auto bg_errors = delete_scheduler_->GetBackgroundErrors();
316
1
  ASSERT_EQ(bg_errors.size(), 0);
317
318
1
  rocksdb::SyncPoint::GetInstance()->DisableProcessing();
319
1
}
320
321
// 1- Create 10 dummy files
322
// 2- Delete the 10 files using DeleteScheduler (move them to trsah)
323
// 3- Delete the 10 files directly (using env_->DeleteFile)
324
// --- Hold DeleteScheduler::BackgroundEmptyTrash ---
325
// 4- Make sure that DeleteScheduler failed to delete the 10 files and
326
//    reported 10 background errors
327
1
TEST_F(DeleteSchedulerTest, BackgroundError) {
328
1
  rocksdb::SyncPoint::GetInstance()->LoadDependency({
329
1
      {"DeleteSchedulerTest::BackgroundError:1",
330
1
       "DeleteScheduler::BackgroundEmptyTrash"},
331
1
  });
332
1
  rocksdb::SyncPoint::GetInstance()->EnableProcessing();
333
334
1
  rate_bytes_per_sec_ = 1024 * 1024;  // 1 Mb/sec
335
1
  NewDeleteScheduler();
336
337
  // Generate 10 dummy files and move them to trash
338
11
  for (int i = 0; i < 10; i++) {
339
10
    std::string file_name = "data_" + ToString(i) + ".data";
340
10
    ASSERT_OK(delete_scheduler_->DeleteFile(NewDummyFile(file_name)));
341
10
  }
342
1
  ASSERT_EQ(CountFilesInDir(dummy_files_dir_), 0);
343
1
  ASSERT_EQ(CountFilesInDir(trash_dir_), 10);
344
345
  // Delete 10 files from trash, this will cause background errors in
346
  // BackgroundEmptyTrash since we already deleted the files it was
347
  // goind to delete
348
11
  for (int i = 0; i < 10; i++) {
349
10
    std::string file_name = "data_" + ToString(i) + ".data";
350
10
    ASSERT_OK(env_->DeleteFile(trash_dir_ + "/" + file_name));
351
10
  }
352
353
  // Hold BackgroundEmptyTrash
354
1
  TEST_SYNC_POINT("DeleteSchedulerTest::BackgroundError:1");
355
1
  delete_scheduler_->WaitForEmptyTrash();
356
1
  auto bg_errors = delete_scheduler_->GetBackgroundErrors();
357
1
  ASSERT_EQ(bg_errors.size(), 10);
358
359
1
  rocksdb::SyncPoint::GetInstance()->DisableProcessing();
360
1
}
361
362
// 1- Create 10 dummy files
363
// 2- Delete 10 dummy files using DeleteScheduler
364
// 3- Wait for DeleteScheduler to delete all files in queue
365
// 4- Make sure all files in trash directory were deleted
366
// 5- Repeat previous steps 5 times
367
1
TEST_F(DeleteSchedulerTest, StartBGEmptyTrashMultipleTimes) {
368
1
  int bg_delete_file = 0;
369
1
  rocksdb::SyncPoint::GetInstance()->SetCallBack(
370
1
      "DeleteScheduler::DeleteTrashFile:DeleteFile",
371
50
      [&](void* arg) { bg_delete_file++; });
372
1
  rocksdb::SyncPoint::GetInstance()->EnableProcessing();
373
374
1
  rate_bytes_per_sec_ = 1024 * 1024;  // 1 MB / sec
375
1
  NewDeleteScheduler();
376
377
  // Move files to trash, wait for empty trash, start again
378
6
  for (int run = 1; run <= 5; run++) {
379
    // Generate 10 dummy files and move them to trash
380
55
    for (int i = 0; i < 10; i++) {
381
50
      std::string file_name = "data_" + ToString(i) + ".data";
382
50
      ASSERT_OK(delete_scheduler_->DeleteFile(NewDummyFile(file_name)));
383
50
    }
384
5
    ASSERT_EQ(CountFilesInDir(dummy_files_dir_), 0);
385
5
    delete_scheduler_->WaitForEmptyTrash();
386
5
    ASSERT_EQ(bg_delete_file, 10 * run);
387
5
    ASSERT_EQ(CountFilesInDir(trash_dir_), 0);
388
389
5
    auto bg_errors = delete_scheduler_->GetBackgroundErrors();
390
5
    ASSERT_EQ(bg_errors.size(), 0);
391
5
  }
392
393
1
  ASSERT_EQ(bg_delete_file, 50);
394
1
  rocksdb::SyncPoint::GetInstance()->EnableProcessing();
395
1
}
396
397
// 1- Create a DeleteScheduler with very slow rate limit (1 Byte / sec)
398
// 2- Delete 100 files using DeleteScheduler
399
// 3- Delete the DeleteScheduler (call the destructor while queue is not empty)
400
// 4- Make sure that not all files were deleted from trash and that
401
//    DeleteScheduler background thread did not delete all files
402
1
TEST_F(DeleteSchedulerTest, DestructorWithNonEmptyQueue) {
403
1
  int bg_delete_file = 0;
404
1
  rocksdb::SyncPoint::GetInstance()->SetCallBack(
405
1
      "DeleteScheduler::DeleteTrashFile:DeleteFile",
406
1
      [&](void* arg) { bg_delete_file++; });
407
1
  rocksdb::SyncPoint::GetInstance()->EnableProcessing();
408
409
1
  rate_bytes_per_sec_ = 1;  // 1 Byte / sec
410
1
  NewDeleteScheduler();
411
412
101
  for (int i = 0; i < 100; i++) {
413
100
    std::string file_name = "data_" + ToString(i) + ".data";
414
100
    ASSERT_OK(delete_scheduler_->DeleteFile(NewDummyFile(file_name)));
415
100
  }
416
417
  // Deleting 100 files will need >28 hours to delete
418
  // we will delete the DeleteScheduler while delete queue is not empty
419
1
  delete_scheduler_.reset();
420
421
1
  ASSERT_LT(bg_delete_file, 100);
422
1
  ASSERT_GT(CountFilesInDir(trash_dir_), 0);
423
424
1
  rocksdb::SyncPoint::GetInstance()->DisableProcessing();
425
1
}
426
427
// 1- Delete the trash directory
428
// 2- Delete 10 files using DeleteScheduler
429
// 3- Make sure that the 10 files were deleted immediately since DeleteScheduler
430
//    failed to move them to trash directory
431
1
TEST_F(DeleteSchedulerTest, MoveToTrashError) {
432
1
  int bg_delete_file = 0;
433
1
  rocksdb::SyncPoint::GetInstance()->SetCallBack(
434
1
      "DeleteScheduler::DeleteTrashFile:DeleteFile",
435
0
      [&](void* arg) { bg_delete_file++; });
436
1
  rocksdb::SyncPoint::GetInstance()->EnableProcessing();
437
438
1
  rate_bytes_per_sec_ = 1024;  // 1 Kb / sec
439
1
  NewDeleteScheduler();
440
441
  // We will delete the trash directory, that mean that DeleteScheduler wont
442
  // be able to move files to trash and will delete files them immediately.
443
1
  DestroyDir(trash_dir_);
444
11
  for (int i = 0; i < 10; i++) {
445
10
    std::string file_name = "data_" + ToString(i) + ".data";
446
10
    ASSERT_OK(delete_scheduler_->DeleteFile(NewDummyFile(file_name)));
447
10
  }
448
449
1
  ASSERT_EQ(CountFilesInDir(dummy_files_dir_), 0);
450
1
  ASSERT_EQ(bg_delete_file, 0);
451
452
1
  rocksdb::SyncPoint::GetInstance()->DisableProcessing();
453
1
}
454
}  // namespace rocksdb
455
456
13.2k
int main(int argc, char** argv) {
457
13.2k
  ::testing::InitGoogleTest(&argc, argv);
458
13.2k
  return RUN_ALL_TESTS();
459
13.2k
}