]> git.proxmox.com Git - ceph.git/blob - ceph/src/rocksdb/db/db_options_test.cc
import 14.2.4 nautilus point release
[ceph.git] / ceph / src / rocksdb / db / db_options_test.cc
1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
5 //
6 // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
7 // Use of this source code is governed by a BSD-style license that can be
8 // found in the LICENSE file. See the AUTHORS file for names of contributors.
9 #include <limits>
10 #include <string>
11 #include <unordered_map>
12
13 #include "db/column_family.h"
14 #include "db/db_impl.h"
15 #include "db/db_test_util.h"
16 #include "options/options_helper.h"
17 #include "port/stack_trace.h"
18 #include "rocksdb/cache.h"
19 #include "rocksdb/convenience.h"
20 #include "rocksdb/rate_limiter.h"
21 #include "rocksdb/stats_history.h"
22 #include "util/random.h"
23 #include "util/sync_point.h"
24 #include "util/testutil.h"
25
26 namespace rocksdb {
27
28 const int kMicrosInSec = 1000000;
29
30 class DBOptionsTest : public DBTestBase {
31 public:
32 DBOptionsTest() : DBTestBase("/db_options_test") {}
33
34 #ifndef ROCKSDB_LITE
35 std::unordered_map<std::string, std::string> GetMutableDBOptionsMap(
36 const DBOptions& options) {
37 std::string options_str;
38 GetStringFromDBOptions(&options_str, options);
39 std::unordered_map<std::string, std::string> options_map;
40 StringToMap(options_str, &options_map);
41 std::unordered_map<std::string, std::string> mutable_map;
42 for (const auto opt : db_options_type_info) {
43 if (opt.second.is_mutable &&
44 opt.second.verification != OptionVerificationType::kDeprecated) {
45 mutable_map[opt.first] = options_map[opt.first];
46 }
47 }
48 return mutable_map;
49 }
50
51 std::unordered_map<std::string, std::string> GetMutableCFOptionsMap(
52 const ColumnFamilyOptions& options) {
53 std::string options_str;
54 GetStringFromColumnFamilyOptions(&options_str, options);
55 std::unordered_map<std::string, std::string> options_map;
56 StringToMap(options_str, &options_map);
57 std::unordered_map<std::string, std::string> mutable_map;
58 for (const auto opt : cf_options_type_info) {
59 if (opt.second.is_mutable &&
60 opt.second.verification != OptionVerificationType::kDeprecated) {
61 mutable_map[opt.first] = options_map[opt.first];
62 }
63 }
64 return mutable_map;
65 }
66
67 std::unordered_map<std::string, std::string> GetRandomizedMutableCFOptionsMap(
68 Random* rnd) {
69 Options options;
70 options.env = env_;
71 ImmutableDBOptions db_options(options);
72 test::RandomInitCFOptions(&options, rnd);
73 auto sanitized_options = SanitizeOptions(db_options, options);
74 auto opt_map = GetMutableCFOptionsMap(sanitized_options);
75 delete options.compaction_filter;
76 return opt_map;
77 }
78
79 std::unordered_map<std::string, std::string> GetRandomizedMutableDBOptionsMap(
80 Random* rnd) {
81 DBOptions db_options;
82 test::RandomInitDBOptions(&db_options, rnd);
83 auto sanitized_options = SanitizeOptions(dbname_, db_options);
84 return GetMutableDBOptionsMap(sanitized_options);
85 }
86 #endif // ROCKSDB_LITE
87 };
88
89 // RocksDB lite don't support dynamic options.
90 #ifndef ROCKSDB_LITE
91
92 TEST_F(DBOptionsTest, GetLatestDBOptions) {
93 // GetOptions should be able to get latest option changed by SetOptions.
94 Options options;
95 options.create_if_missing = true;
96 options.env = env_;
97 Random rnd(228);
98 Reopen(options);
99 auto new_options = GetRandomizedMutableDBOptionsMap(&rnd);
100 ASSERT_OK(dbfull()->SetDBOptions(new_options));
101 ASSERT_EQ(new_options, GetMutableDBOptionsMap(dbfull()->GetDBOptions()));
102 }
103
104 TEST_F(DBOptionsTest, GetLatestCFOptions) {
105 // GetOptions should be able to get latest option changed by SetOptions.
106 Options options;
107 options.create_if_missing = true;
108 options.env = env_;
109 Random rnd(228);
110 Reopen(options);
111 CreateColumnFamilies({"foo"}, options);
112 ReopenWithColumnFamilies({"default", "foo"}, options);
113 auto options_default = GetRandomizedMutableCFOptionsMap(&rnd);
114 auto options_foo = GetRandomizedMutableCFOptionsMap(&rnd);
115 ASSERT_OK(dbfull()->SetOptions(handles_[0], options_default));
116 ASSERT_OK(dbfull()->SetOptions(handles_[1], options_foo));
117 ASSERT_EQ(options_default,
118 GetMutableCFOptionsMap(dbfull()->GetOptions(handles_[0])));
119 ASSERT_EQ(options_foo,
120 GetMutableCFOptionsMap(dbfull()->GetOptions(handles_[1])));
121 }
122
123 TEST_F(DBOptionsTest, SetBytesPerSync) {
124 const size_t kValueSize = 1024 * 1024; // 1MB
125 Options options;
126 options.create_if_missing = true;
127 options.bytes_per_sync = 1024 * 1024;
128 options.use_direct_reads = false;
129 options.write_buffer_size = 400 * kValueSize;
130 options.disable_auto_compactions = true;
131 options.compression = kNoCompression;
132 options.env = env_;
133 Reopen(options);
134 int counter = 0;
135 int low_bytes_per_sync = 0;
136 int i = 0;
137 const std::string kValue(kValueSize, 'v');
138 ASSERT_EQ(options.bytes_per_sync, dbfull()->GetDBOptions().bytes_per_sync);
139 rocksdb::SyncPoint::GetInstance()->SetCallBack(
140 "WritableFileWriter::RangeSync:0", [&](void* /*arg*/) {
141 counter++;
142 });
143
144 WriteOptions write_opts;
145 // should sync approximately 40MB/1MB ~= 40 times.
146 for (i = 0; i < 40; i++) {
147 Put(Key(i), kValue, write_opts);
148 }
149 rocksdb::SyncPoint::GetInstance()->EnableProcessing();
150 ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
151 rocksdb::SyncPoint::GetInstance()->DisableProcessing();
152 low_bytes_per_sync = counter;
153 ASSERT_GT(low_bytes_per_sync, 35);
154 ASSERT_LT(low_bytes_per_sync, 45);
155
156 counter = 0;
157 // 8388608 = 8 * 1024 * 1024
158 ASSERT_OK(dbfull()->SetDBOptions({{"bytes_per_sync", "8388608"}}));
159 ASSERT_EQ(8388608, dbfull()->GetDBOptions().bytes_per_sync);
160 // should sync approximately 40MB*2/8MB ~= 10 times.
161 // data will be 40*2MB because of previous Puts too.
162 for (i = 0; i < 40; i++) {
163 Put(Key(i), kValue, write_opts);
164 }
165 rocksdb::SyncPoint::GetInstance()->EnableProcessing();
166 ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
167 ASSERT_GT(counter, 5);
168 ASSERT_LT(counter, 15);
169
170 // Redundant assert. But leaving it here just to get the point across that
171 // low_bytes_per_sync > counter.
172 ASSERT_GT(low_bytes_per_sync, counter);
173 }
174
175 TEST_F(DBOptionsTest, SetWalBytesPerSync) {
176 const size_t kValueSize = 1024 * 1024 * 3;
177 Options options;
178 options.create_if_missing = true;
179 options.wal_bytes_per_sync = 512;
180 options.write_buffer_size = 100 * kValueSize;
181 options.disable_auto_compactions = true;
182 options.compression = kNoCompression;
183 options.env = env_;
184 Reopen(options);
185 ASSERT_EQ(512, dbfull()->GetDBOptions().wal_bytes_per_sync);
186 int counter = 0;
187 int low_bytes_per_sync = 0;
188 rocksdb::SyncPoint::GetInstance()->SetCallBack(
189 "WritableFileWriter::RangeSync:0", [&](void* /*arg*/) {
190 counter++;
191 });
192 rocksdb::SyncPoint::GetInstance()->EnableProcessing();
193 const std::string kValue(kValueSize, 'v');
194 int i = 0;
195 for (; i < 10; i++) {
196 Put(Key(i), kValue);
197 }
198 // Do not flush. If we flush here, SwitchWAL will reuse old WAL file since its
199 // empty and will not get the new wal_bytes_per_sync value.
200 low_bytes_per_sync = counter;
201 //5242880 = 1024 * 1024 * 5
202 ASSERT_OK(dbfull()->SetDBOptions({{"wal_bytes_per_sync", "5242880"}}));
203 ASSERT_EQ(5242880, dbfull()->GetDBOptions().wal_bytes_per_sync);
204 counter = 0;
205 i = 0;
206 for (; i < 10; i++) {
207 Put(Key(i), kValue);
208 }
209 ASSERT_GT(counter, 0);
210 ASSERT_GT(low_bytes_per_sync, 0);
211 ASSERT_GT(low_bytes_per_sync, counter);
212 }
213
214 TEST_F(DBOptionsTest, WritableFileMaxBufferSize) {
215 Options options;
216 options.create_if_missing = true;
217 options.writable_file_max_buffer_size = 1024 * 1024;
218 options.level0_file_num_compaction_trigger = 3;
219 options.max_manifest_file_size = 1;
220 options.env = env_;
221 int buffer_size = 1024 * 1024;
222 Reopen(options);
223 ASSERT_EQ(buffer_size,
224 dbfull()->GetDBOptions().writable_file_max_buffer_size);
225
226 std::atomic<int> match_cnt(0);
227 std::atomic<int> unmatch_cnt(0);
228 rocksdb::SyncPoint::GetInstance()->SetCallBack(
229 "WritableFileWriter::WritableFileWriter:0", [&](void* arg) {
230 int value = static_cast<int>(reinterpret_cast<uintptr_t>(arg));
231 if (value == buffer_size) {
232 match_cnt++;
233 } else {
234 unmatch_cnt++;
235 }
236 });
237 rocksdb::SyncPoint::GetInstance()->EnableProcessing();
238 int i = 0;
239 for (; i < 3; i++) {
240 ASSERT_OK(Put("foo", ToString(i)));
241 ASSERT_OK(Put("bar", ToString(i)));
242 Flush();
243 }
244 dbfull()->TEST_WaitForCompact();
245 ASSERT_EQ(unmatch_cnt, 0);
246 ASSERT_GE(match_cnt, 11);
247
248 ASSERT_OK(
249 dbfull()->SetDBOptions({{"writable_file_max_buffer_size", "524288"}}));
250 buffer_size = 512 * 1024;
251 match_cnt = 0;
252 unmatch_cnt = 0; // SetDBOptions() will create a WriteableFileWriter
253
254 ASSERT_EQ(buffer_size,
255 dbfull()->GetDBOptions().writable_file_max_buffer_size);
256 i = 0;
257 for (; i < 3; i++) {
258 ASSERT_OK(Put("foo", ToString(i)));
259 ASSERT_OK(Put("bar", ToString(i)));
260 Flush();
261 }
262 dbfull()->TEST_WaitForCompact();
263 ASSERT_EQ(unmatch_cnt, 0);
264 ASSERT_GE(match_cnt, 11);
265 }
266
267 TEST_F(DBOptionsTest, SetOptionsAndReopen) {
268 Random rnd(1044);
269 auto rand_opts = GetRandomizedMutableCFOptionsMap(&rnd);
270 ASSERT_OK(dbfull()->SetOptions(rand_opts));
271 // Verify if DB can be reopen after setting options.
272 Options options;
273 options.env = env_;
274 ASSERT_OK(TryReopen(options));
275 }
276
277 TEST_F(DBOptionsTest, EnableAutoCompactionAndTriggerStall) {
278 const std::string kValue(1024, 'v');
279 for (int method_type = 0; method_type < 2; method_type++) {
280 for (int option_type = 0; option_type < 4; option_type++) {
281 Options options;
282 options.create_if_missing = true;
283 options.disable_auto_compactions = true;
284 options.write_buffer_size = 1024 * 1024 * 10;
285 options.compression = CompressionType::kNoCompression;
286 options.level0_file_num_compaction_trigger = 1;
287 options.level0_stop_writes_trigger = std::numeric_limits<int>::max();
288 options.level0_slowdown_writes_trigger = std::numeric_limits<int>::max();
289 options.hard_pending_compaction_bytes_limit =
290 std::numeric_limits<uint64_t>::max();
291 options.soft_pending_compaction_bytes_limit =
292 std::numeric_limits<uint64_t>::max();
293 options.env = env_;
294
295 DestroyAndReopen(options);
296 int i = 0;
297 for (; i < 1024; i++) {
298 Put(Key(i), kValue);
299 }
300 Flush();
301 for (; i < 1024 * 2; i++) {
302 Put(Key(i), kValue);
303 }
304 Flush();
305 dbfull()->TEST_WaitForFlushMemTable();
306 ASSERT_EQ(2, NumTableFilesAtLevel(0));
307 uint64_t l0_size = SizeAtLevel(0);
308
309 switch (option_type) {
310 case 0:
311 // test with level0_stop_writes_trigger
312 options.level0_stop_writes_trigger = 2;
313 options.level0_slowdown_writes_trigger = 2;
314 break;
315 case 1:
316 options.level0_slowdown_writes_trigger = 2;
317 break;
318 case 2:
319 options.hard_pending_compaction_bytes_limit = l0_size;
320 options.soft_pending_compaction_bytes_limit = l0_size;
321 break;
322 case 3:
323 options.soft_pending_compaction_bytes_limit = l0_size;
324 break;
325 }
326 Reopen(options);
327 dbfull()->TEST_WaitForCompact();
328 ASSERT_FALSE(dbfull()->TEST_write_controler().IsStopped());
329 ASSERT_FALSE(dbfull()->TEST_write_controler().NeedsDelay());
330
331 SyncPoint::GetInstance()->LoadDependency(
332 {{"DBOptionsTest::EnableAutoCompactionAndTriggerStall:1",
333 "BackgroundCallCompaction:0"},
334 {"DBImpl::BackgroundCompaction():BeforePickCompaction",
335 "DBOptionsTest::EnableAutoCompactionAndTriggerStall:2"},
336 {"DBOptionsTest::EnableAutoCompactionAndTriggerStall:3",
337 "DBImpl::BackgroundCompaction():AfterPickCompaction"}});
338 // Block background compaction.
339 SyncPoint::GetInstance()->EnableProcessing();
340
341 switch (method_type) {
342 case 0:
343 ASSERT_OK(
344 dbfull()->SetOptions({{"disable_auto_compactions", "false"}}));
345 break;
346 case 1:
347 ASSERT_OK(dbfull()->EnableAutoCompaction(
348 {dbfull()->DefaultColumnFamily()}));
349 break;
350 }
351 TEST_SYNC_POINT("DBOptionsTest::EnableAutoCompactionAndTriggerStall:1");
352 // Wait for stall condition recalculate.
353 TEST_SYNC_POINT("DBOptionsTest::EnableAutoCompactionAndTriggerStall:2");
354
355 switch (option_type) {
356 case 0:
357 ASSERT_TRUE(dbfull()->TEST_write_controler().IsStopped());
358 break;
359 case 1:
360 ASSERT_FALSE(dbfull()->TEST_write_controler().IsStopped());
361 ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
362 break;
363 case 2:
364 ASSERT_TRUE(dbfull()->TEST_write_controler().IsStopped());
365 break;
366 case 3:
367 ASSERT_FALSE(dbfull()->TEST_write_controler().IsStopped());
368 ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
369 break;
370 }
371 TEST_SYNC_POINT("DBOptionsTest::EnableAutoCompactionAndTriggerStall:3");
372
373 // Background compaction executed.
374 dbfull()->TEST_WaitForCompact();
375 ASSERT_FALSE(dbfull()->TEST_write_controler().IsStopped());
376 ASSERT_FALSE(dbfull()->TEST_write_controler().NeedsDelay());
377 }
378 }
379 }
380
381 TEST_F(DBOptionsTest, SetOptionsMayTriggerCompaction) {
382 Options options;
383 options.create_if_missing = true;
384 options.level0_file_num_compaction_trigger = 1000;
385 options.env = env_;
386 Reopen(options);
387 for (int i = 0; i < 3; i++) {
388 // Need to insert two keys to avoid trivial move.
389 ASSERT_OK(Put("foo", ToString(i)));
390 ASSERT_OK(Put("bar", ToString(i)));
391 Flush();
392 }
393 ASSERT_EQ("3", FilesPerLevel());
394 ASSERT_OK(
395 dbfull()->SetOptions({{"level0_file_num_compaction_trigger", "3"}}));
396 dbfull()->TEST_WaitForCompact();
397 ASSERT_EQ("0,1", FilesPerLevel());
398 }
399
400 TEST_F(DBOptionsTest, SetBackgroundCompactionThreads) {
401 Options options;
402 options.create_if_missing = true;
403 options.max_background_compactions = 1; // default value
404 options.env = env_;
405 Reopen(options);
406 ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed());
407 ASSERT_OK(dbfull()->SetDBOptions({{"max_background_compactions", "3"}}));
408 ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed());
409 auto stop_token = dbfull()->TEST_write_controler().GetStopToken();
410 ASSERT_EQ(3, dbfull()->TEST_BGCompactionsAllowed());
411 }
412
413 TEST_F(DBOptionsTest, SetBackgroundJobs) {
414 Options options;
415 options.create_if_missing = true;
416 options.max_background_jobs = 8;
417 options.env = env_;
418 Reopen(options);
419
420 for (int i = 0; i < 2; ++i) {
421 if (i > 0) {
422 options.max_background_jobs = 12;
423 ASSERT_OK(dbfull()->SetDBOptions(
424 {{"max_background_jobs",
425 std::to_string(options.max_background_jobs)}}));
426 }
427
428 ASSERT_EQ(options.max_background_jobs / 4,
429 dbfull()->TEST_BGFlushesAllowed());
430 ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed());
431
432 auto stop_token = dbfull()->TEST_write_controler().GetStopToken();
433
434 ASSERT_EQ(options.max_background_jobs / 4,
435 dbfull()->TEST_BGFlushesAllowed());
436 ASSERT_EQ(3 * options.max_background_jobs / 4,
437 dbfull()->TEST_BGCompactionsAllowed());
438 }
439 }
440
441 TEST_F(DBOptionsTest, AvoidFlushDuringShutdown) {
442 Options options;
443 options.create_if_missing = true;
444 options.disable_auto_compactions = true;
445 options.env = env_;
446 WriteOptions write_without_wal;
447 write_without_wal.disableWAL = true;
448
449 ASSERT_FALSE(options.avoid_flush_during_shutdown);
450 DestroyAndReopen(options);
451 ASSERT_OK(Put("foo", "v1", write_without_wal));
452 Reopen(options);
453 ASSERT_EQ("v1", Get("foo"));
454 ASSERT_EQ("1", FilesPerLevel());
455
456 DestroyAndReopen(options);
457 ASSERT_OK(Put("foo", "v2", write_without_wal));
458 ASSERT_OK(dbfull()->SetDBOptions({{"avoid_flush_during_shutdown", "true"}}));
459 Reopen(options);
460 ASSERT_EQ("NOT_FOUND", Get("foo"));
461 ASSERT_EQ("", FilesPerLevel());
462 }
463
464 TEST_F(DBOptionsTest, SetDelayedWriteRateOption) {
465 Options options;
466 options.create_if_missing = true;
467 options.delayed_write_rate = 2 * 1024U * 1024U;
468 options.env = env_;
469 Reopen(options);
470 ASSERT_EQ(2 * 1024U * 1024U, dbfull()->TEST_write_controler().max_delayed_write_rate());
471
472 ASSERT_OK(dbfull()->SetDBOptions({{"delayed_write_rate", "20000"}}));
473 ASSERT_EQ(20000, dbfull()->TEST_write_controler().max_delayed_write_rate());
474 }
475
476 TEST_F(DBOptionsTest, MaxTotalWalSizeChange) {
477 Random rnd(1044);
478 const auto value_size = size_t(1024);
479 std::string value;
480 test::RandomString(&rnd, value_size, &value);
481
482 Options options;
483 options.create_if_missing = true;
484 options.env = env_;
485 CreateColumnFamilies({"1", "2", "3"}, options);
486 ReopenWithColumnFamilies({"default", "1", "2", "3"}, options);
487
488 WriteOptions write_options;
489
490 const int key_count = 100;
491 for (int i = 0; i < key_count; ++i) {
492 for (size_t cf = 0; cf < handles_.size(); ++cf) {
493 ASSERT_OK(Put(static_cast<int>(cf), Key(i), value));
494 }
495 }
496 ASSERT_OK(dbfull()->SetDBOptions({{"max_total_wal_size", "10"}}));
497
498 for (size_t cf = 0; cf < handles_.size(); ++cf) {
499 dbfull()->TEST_WaitForFlushMemTable(handles_[cf]);
500 ASSERT_EQ("1", FilesPerLevel(static_cast<int>(cf)));
501 }
502 }
503
504 TEST_F(DBOptionsTest, SetStatsDumpPeriodSec) {
505 Options options;
506 options.create_if_missing = true;
507 options.stats_dump_period_sec = 5;
508 options.env = env_;
509 Reopen(options);
510 ASSERT_EQ(5, dbfull()->GetDBOptions().stats_dump_period_sec);
511
512 for (int i = 0; i < 20; i++) {
513 int num = rand() % 5000 + 1;
514 ASSERT_OK(
515 dbfull()->SetDBOptions({{"stats_dump_period_sec", ToString(num)}}));
516 ASSERT_EQ(num, dbfull()->GetDBOptions().stats_dump_period_sec);
517 }
518 Close();
519 }
520
521 TEST_F(DBOptionsTest, RunStatsDumpPeriodSec) {
522 Options options;
523 options.create_if_missing = true;
524 options.stats_dump_period_sec = 5;
525 std::unique_ptr<rocksdb::MockTimeEnv> mock_env;
526 mock_env.reset(new rocksdb::MockTimeEnv(env_));
527 mock_env->set_current_time(0); // in seconds
528 options.env = mock_env.get();
529 int counter = 0;
530 rocksdb::SyncPoint::GetInstance()->DisableProcessing();
531 rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
532 #if defined(OS_MACOSX) && !defined(NDEBUG)
533 rocksdb::SyncPoint::GetInstance()->SetCallBack(
534 "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
535 uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
536 if (time_us < mock_env->RealNowMicros()) {
537 *reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
538 }
539 });
540 #endif // OS_MACOSX && !NDEBUG
541 rocksdb::SyncPoint::GetInstance()->SetCallBack(
542 "DBImpl::DumpStats:1", [&](void* /*arg*/) {
543 counter++;
544 });
545 rocksdb::SyncPoint::GetInstance()->EnableProcessing();
546 Reopen(options);
547 ASSERT_EQ(5, dbfull()->GetDBOptions().stats_dump_period_sec);
548 dbfull()->TEST_WaitForDumpStatsRun([&] { mock_env->set_current_time(5); });
549 ASSERT_GE(counter, 1);
550
551 // Test cacel job through SetOptions
552 ASSERT_OK(dbfull()->SetDBOptions({{"stats_dump_period_sec", "0"}}));
553 int old_val = counter;
554 for (int i = 6; i < 20; ++i) {
555 dbfull()->TEST_WaitForDumpStatsRun([&] { mock_env->set_current_time(i); });
556 }
557 ASSERT_EQ(counter, old_val);
558 Close();
559 }
560
561 // Test persistent stats background thread scheduling and cancelling
562 TEST_F(DBOptionsTest, StatsPersistScheduling) {
563 Options options;
564 options.create_if_missing = true;
565 options.stats_persist_period_sec = 5;
566 std::unique_ptr<rocksdb::MockTimeEnv> mock_env;
567 mock_env.reset(new rocksdb::MockTimeEnv(env_));
568 mock_env->set_current_time(0); // in seconds
569 options.env = mock_env.get();
570 rocksdb::SyncPoint::GetInstance()->DisableProcessing();
571 rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
572 #if defined(OS_MACOSX) && !defined(NDEBUG)
573 rocksdb::SyncPoint::GetInstance()->SetCallBack(
574 "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
575 uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
576 if (time_us < mock_env->RealNowMicros()) {
577 *reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
578 }
579 });
580 #endif // OS_MACOSX && !NDEBUG
581 int counter = 0;
582 rocksdb::SyncPoint::GetInstance()->SetCallBack(
583 "DBImpl::PersistStats:Entry", [&](void* /*arg*/) { counter++; });
584 rocksdb::SyncPoint::GetInstance()->EnableProcessing();
585 Reopen(options);
586 ASSERT_EQ(5, dbfull()->GetDBOptions().stats_persist_period_sec);
587 dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); });
588 ASSERT_GE(counter, 1);
589
590 // Test cacel job through SetOptions
591 ASSERT_TRUE(dbfull()->TEST_IsPersistentStatsEnabled());
592 ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "0"}}));
593 ASSERT_FALSE(dbfull()->TEST_IsPersistentStatsEnabled());
594 Close();
595 }
596
597 // Test enabling persistent stats for the first time
598 TEST_F(DBOptionsTest, PersistentStatsFreshInstall) {
599 Options options;
600 options.create_if_missing = true;
601 options.stats_persist_period_sec = 0;
602 std::unique_ptr<rocksdb::MockTimeEnv> mock_env;
603 mock_env.reset(new rocksdb::MockTimeEnv(env_));
604 mock_env->set_current_time(0); // in seconds
605 options.env = mock_env.get();
606 rocksdb::SyncPoint::GetInstance()->DisableProcessing();
607 rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
608 #if defined(OS_MACOSX) && !defined(NDEBUG)
609 rocksdb::SyncPoint::GetInstance()->SetCallBack(
610 "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
611 uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
612 if (time_us < mock_env->RealNowMicros()) {
613 *reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
614 }
615 });
616 #endif // OS_MACOSX && !NDEBUG
617 int counter = 0;
618 rocksdb::SyncPoint::GetInstance()->SetCallBack(
619 "DBImpl::PersistStats:Entry", [&](void* /*arg*/) { counter++; });
620 rocksdb::SyncPoint::GetInstance()->EnableProcessing();
621 Reopen(options);
622 ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "5"}}));
623 ASSERT_EQ(5, dbfull()->GetDBOptions().stats_persist_period_sec);
624 dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); });
625 ASSERT_GE(counter, 1);
626 Close();
627 }
628
629 TEST_F(DBOptionsTest, SetOptionsStatsPersistPeriodSec) {
630 Options options;
631 options.create_if_missing = true;
632 options.stats_persist_period_sec = 5;
633 options.env = env_;
634 Reopen(options);
635 ASSERT_EQ(5, dbfull()->GetDBOptions().stats_persist_period_sec);
636
637 ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "12345"}}));
638 ASSERT_EQ(12345, dbfull()->GetDBOptions().stats_persist_period_sec);
639 ASSERT_NOK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "abcde"}}));
640 ASSERT_EQ(12345, dbfull()->GetDBOptions().stats_persist_period_sec);
641 }
642
643 TEST_F(DBOptionsTest, GetStatsHistory) {
644 Options options;
645 options.create_if_missing = true;
646 options.stats_persist_period_sec = 5;
647 options.statistics = rocksdb::CreateDBStatistics();
648 std::unique_ptr<rocksdb::MockTimeEnv> mock_env;
649 mock_env.reset(new rocksdb::MockTimeEnv(env_));
650 mock_env->set_current_time(0); // in seconds
651 options.env = mock_env.get();
652 #if defined(OS_MACOSX) && !defined(NDEBUG)
653 rocksdb::SyncPoint::GetInstance()->DisableProcessing();
654 rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
655 rocksdb::SyncPoint::GetInstance()->SetCallBack(
656 "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
657 uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
658 if (time_us < mock_env->RealNowMicros()) {
659 *reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
660 }
661 });
662 rocksdb::SyncPoint::GetInstance()->EnableProcessing();
663 #endif // OS_MACOSX && !NDEBUG
664
665 CreateColumnFamilies({"pikachu"}, options);
666 ASSERT_OK(Put("foo", "bar"));
667 ReopenWithColumnFamilies({"default", "pikachu"}, options);
668
669 int mock_time = 1;
670 // Wait for stats persist to finish
671 dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); });
672 std::unique_ptr<StatsHistoryIterator> stats_iter;
673 db_->GetStatsHistory(0, 6 * kMicrosInSec, &stats_iter);
674 ASSERT_TRUE(stats_iter != nullptr);
675 // disabled stats snapshots
676 ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "0"}}));
677 size_t stats_count = 0;
678 for (; stats_iter->Valid(); stats_iter->Next()) {
679 auto stats_map = stats_iter->GetStatsMap();
680 stats_count += stats_map.size();
681 }
682 ASSERT_GT(stats_count, 0);
683 // Wait a bit and verify no more stats are found
684 for (mock_time = 6; mock_time < 20; ++mock_time) {
685 dbfull()->TEST_WaitForPersistStatsRun(
686 [&] { mock_env->set_current_time(mock_time); });
687 }
688 db_->GetStatsHistory(0, 20 * kMicrosInSec, &stats_iter);
689 ASSERT_TRUE(stats_iter != nullptr);
690 size_t stats_count_new = 0;
691 for (; stats_iter->Valid(); stats_iter->Next()) {
692 stats_count_new += stats_iter->GetStatsMap().size();
693 }
694 ASSERT_EQ(stats_count_new, stats_count);
695 Close();
696 }
697
698 TEST_F(DBOptionsTest, InMemoryStatsHistoryPurging) {
699 Options options;
700 options.create_if_missing = true;
701 options.statistics = rocksdb::CreateDBStatistics();
702 options.stats_persist_period_sec = 1;
703 std::unique_ptr<rocksdb::MockTimeEnv> mock_env;
704 mock_env.reset(new rocksdb::MockTimeEnv(env_));
705 mock_env->set_current_time(0); // in seconds
706 options.env = mock_env.get();
707 #if defined(OS_MACOSX) && !defined(NDEBUG)
708 rocksdb::SyncPoint::GetInstance()->DisableProcessing();
709 rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
710 rocksdb::SyncPoint::GetInstance()->SetCallBack(
711 "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
712 uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
713 if (time_us < mock_env->RealNowMicros()) {
714 *reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
715 }
716 });
717 rocksdb::SyncPoint::GetInstance()->EnableProcessing();
718 #endif // OS_MACOSX && !NDEBUG
719
720 CreateColumnFamilies({"pikachu"}, options);
721 ASSERT_OK(Put("foo", "bar"));
722 ReopenWithColumnFamilies({"default", "pikachu"}, options);
723 // some random operation to populate statistics
724 ASSERT_OK(Delete("foo"));
725 ASSERT_OK(Put("sol", "sol"));
726 ASSERT_OK(Put("epic", "epic"));
727 ASSERT_OK(Put("ltd", "ltd"));
728 ASSERT_EQ("sol", Get("sol"));
729 ASSERT_EQ("epic", Get("epic"));
730 ASSERT_EQ("ltd", Get("ltd"));
731 Iterator* iterator = db_->NewIterator(ReadOptions());
732 for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
733 ASSERT_TRUE(iterator->key() == iterator->value());
734 }
735 delete iterator;
736 ASSERT_OK(Flush());
737 ASSERT_OK(Delete("sol"));
738 db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
739 int mock_time = 1;
740 // Wait for stats persist to finish
741 for (; mock_time < 5; ++mock_time) {
742 dbfull()->TEST_WaitForPersistStatsRun(
743 [&] { mock_env->set_current_time(mock_time); });
744 }
745
746 // second round of ops
747 ASSERT_OK(Put("saigon", "saigon"));
748 ASSERT_OK(Put("noodle talk", "noodle talk"));
749 ASSERT_OK(Put("ping bistro", "ping bistro"));
750 iterator = db_->NewIterator(ReadOptions());
751 for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
752 ASSERT_TRUE(iterator->key() == iterator->value());
753 }
754 delete iterator;
755 ASSERT_OK(Flush());
756 db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
757 for (; mock_time < 10; ++mock_time) {
758 dbfull()->TEST_WaitForPersistStatsRun(
759 [&] { mock_env->set_current_time(mock_time); });
760 }
761 std::unique_ptr<StatsHistoryIterator> stats_iter;
762 db_->GetStatsHistory(0, 10 * kMicrosInSec, &stats_iter);
763 ASSERT_TRUE(stats_iter != nullptr);
764 size_t stats_count = 0;
765 int slice_count = 0;
766 for (; stats_iter->Valid(); stats_iter->Next()) {
767 slice_count++;
768 auto stats_map = stats_iter->GetStatsMap();
769 stats_count += stats_map.size();
770 }
771 size_t stats_history_size = dbfull()->TEST_EstiamteStatsHistorySize();
772 ASSERT_GE(slice_count, 9);
773 ASSERT_GE(stats_history_size, 12000);
774 // capping memory cost at 12000 bytes since one slice is around 10000~12000
775 ASSERT_OK(dbfull()->SetDBOptions({{"stats_history_buffer_size", "12000"}}));
776 ASSERT_EQ(12000, dbfull()->GetDBOptions().stats_history_buffer_size);
777 // Wait for stats persist to finish
778 for (; mock_time < 20; ++mock_time) {
779 dbfull()->TEST_WaitForPersistStatsRun(
780 [&] { mock_env->set_current_time(mock_time); });
781 }
782 db_->GetStatsHistory(0, 20 * kMicrosInSec, &stats_iter);
783 ASSERT_TRUE(stats_iter != nullptr);
784 size_t stats_count_reopen = 0;
785 slice_count = 0;
786 for (; stats_iter->Valid(); stats_iter->Next()) {
787 slice_count++;
788 auto stats_map = stats_iter->GetStatsMap();
789 stats_count_reopen += stats_map.size();
790 }
791 size_t stats_history_size_reopen = dbfull()->TEST_EstiamteStatsHistorySize();
792 // only one slice can fit under the new stats_history_buffer_size
793 ASSERT_LT(slice_count, 2);
794 ASSERT_TRUE(stats_history_size_reopen < 12000 &&
795 stats_history_size_reopen > 0);
796 ASSERT_TRUE(stats_count_reopen < stats_count && stats_count_reopen > 0);
797 Close();
798 }
799
800 static void assert_candidate_files_empty(DBImpl* dbfull, const bool empty) {
801 dbfull->TEST_LockMutex();
802 JobContext job_context(0);
803 dbfull->FindObsoleteFiles(&job_context, false);
804 ASSERT_EQ(empty, job_context.full_scan_candidate_files.empty());
805 dbfull->TEST_UnlockMutex();
806 if (job_context.HaveSomethingToDelete()) {
807 // fulfill the contract of FindObsoleteFiles by calling PurgeObsoleteFiles
808 // afterwards; otherwise the test may hang on shutdown
809 dbfull->PurgeObsoleteFiles(job_context);
810 }
811 job_context.Clean();
812 }
813
814 TEST_F(DBOptionsTest, DeleteObsoleteFilesPeriodChange) {
815 SpecialEnv env(env_);
816 env.time_elapse_only_sleep_ = true;
817 Options options;
818 options.env = &env;
819 options.create_if_missing = true;
820 ASSERT_OK(TryReopen(options));
821
822 // Verify that candidate files set is empty when no full scan requested.
823 assert_candidate_files_empty(dbfull(), true);
824
825 ASSERT_OK(
826 dbfull()->SetDBOptions({{"delete_obsolete_files_period_micros", "0"}}));
827
828 // After delete_obsolete_files_period_micros updated to 0, the next call
829 // to FindObsoleteFiles should make a full scan
830 assert_candidate_files_empty(dbfull(), false);
831
832 ASSERT_OK(
833 dbfull()->SetDBOptions({{"delete_obsolete_files_period_micros", "20"}}));
834
835 assert_candidate_files_empty(dbfull(), true);
836
837 env.addon_time_.store(20);
838 assert_candidate_files_empty(dbfull(), true);
839
840 env.addon_time_.store(21);
841 assert_candidate_files_empty(dbfull(), false);
842
843 Close();
844 }
845
846 TEST_F(DBOptionsTest, MaxOpenFilesChange) {
847 SpecialEnv env(env_);
848 Options options;
849 options.env = CurrentOptions().env;
850 options.max_open_files = -1;
851
852 Reopen(options);
853
854 Cache* tc = dbfull()->TEST_table_cache();
855
856 ASSERT_EQ(-1, dbfull()->GetDBOptions().max_open_files);
857 ASSERT_LT(2000, tc->GetCapacity());
858 ASSERT_OK(dbfull()->SetDBOptions({{"max_open_files", "1024"}}));
859 ASSERT_EQ(1024, dbfull()->GetDBOptions().max_open_files);
860 // examine the table cache (actual size should be 1014)
861 ASSERT_GT(1500, tc->GetCapacity());
862 Close();
863 }
864
865 TEST_F(DBOptionsTest, SanitizeDelayedWriteRate) {
866 Options options;
867 options.delayed_write_rate = 0;
868 Reopen(options);
869 ASSERT_EQ(16 * 1024 * 1024, dbfull()->GetDBOptions().delayed_write_rate);
870
871 options.rate_limiter.reset(NewGenericRateLimiter(31 * 1024 * 1024));
872 Reopen(options);
873 ASSERT_EQ(31 * 1024 * 1024, dbfull()->GetDBOptions().delayed_write_rate);
874 }
875
876 TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
877 Options options;
878 options.compaction_style = kCompactionStyleFIFO;
879 options.write_buffer_size = 10 << 10; // 10KB
880 options.arena_block_size = 4096;
881 options.compression = kNoCompression;
882 options.create_if_missing = true;
883 options.compaction_options_fifo.allow_compaction = false;
884 env_->time_elapse_only_sleep_ = false;
885 options.env = env_;
886
887 // Test dynamically changing ttl.
888 env_->addon_time_.store(0);
889 options.ttl = 1 * 60 * 60; // 1 hour
890 ASSERT_OK(TryReopen(options));
891
892 Random rnd(301);
893 for (int i = 0; i < 10; i++) {
894 // Generate and flush a file about 10KB.
895 for (int j = 0; j < 10; j++) {
896 ASSERT_OK(Put(ToString(i * 20 + j), RandomString(&rnd, 980)));
897 }
898 Flush();
899 }
900 ASSERT_OK(dbfull()->TEST_WaitForCompact());
901 ASSERT_EQ(NumTableFilesAtLevel(0), 10);
902
903 // Add 61 seconds to the time.
904 env_->addon_time_.fetch_add(61);
905
906 // No files should be compacted as ttl is set to 1 hour.
907 ASSERT_EQ(dbfull()->GetOptions().ttl, 3600);
908 dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
909 ASSERT_EQ(NumTableFilesAtLevel(0), 10);
910
911 // Set ttl to 1 minute. So all files should get deleted.
912 ASSERT_OK(dbfull()->SetOptions({{"ttl", "60"}}));
913 ASSERT_EQ(dbfull()->GetOptions().ttl, 60);
914 dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
915 ASSERT_OK(dbfull()->TEST_WaitForCompact());
916 ASSERT_EQ(NumTableFilesAtLevel(0), 0);
917
918 // Test dynamically changing compaction_options_fifo.max_table_files_size
919 env_->addon_time_.store(0);
920 options.compaction_options_fifo.max_table_files_size = 500 << 10; // 00KB
921 options.ttl = 0;
922 DestroyAndReopen(options);
923
924 for (int i = 0; i < 10; i++) {
925 // Generate and flush a file about 10KB.
926 for (int j = 0; j < 10; j++) {
927 ASSERT_OK(Put(ToString(i * 20 + j), RandomString(&rnd, 980)));
928 }
929 Flush();
930 }
931 ASSERT_OK(dbfull()->TEST_WaitForCompact());
932 ASSERT_EQ(NumTableFilesAtLevel(0), 10);
933
934 // No files should be compacted as max_table_files_size is set to 500 KB.
935 ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
936 500 << 10);
937 dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
938 ASSERT_EQ(NumTableFilesAtLevel(0), 10);
939
940 // Set max_table_files_size to 12 KB. So only 1 file should remain now.
941 ASSERT_OK(dbfull()->SetOptions(
942 {{"compaction_options_fifo", "{max_table_files_size=12288;}"}}));
943 ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
944 12 << 10);
945 dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
946 ASSERT_OK(dbfull()->TEST_WaitForCompact());
947 ASSERT_EQ(NumTableFilesAtLevel(0), 1);
948
949 // Test dynamically changing compaction_options_fifo.allow_compaction
950 options.compaction_options_fifo.max_table_files_size = 500 << 10; // 500KB
951 options.ttl = 0;
952 options.compaction_options_fifo.allow_compaction = false;
953 options.level0_file_num_compaction_trigger = 6;
954 DestroyAndReopen(options);
955
956 for (int i = 0; i < 10; i++) {
957 // Generate and flush a file about 10KB.
958 for (int j = 0; j < 10; j++) {
959 ASSERT_OK(Put(ToString(i * 20 + j), RandomString(&rnd, 980)));
960 }
961 Flush();
962 }
963 ASSERT_OK(dbfull()->TEST_WaitForCompact());
964 ASSERT_EQ(NumTableFilesAtLevel(0), 10);
965
966 // No files should be compacted as max_table_files_size is set to 500 KB and
967 // allow_compaction is false
968 ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
969 false);
970 dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
971 ASSERT_EQ(NumTableFilesAtLevel(0), 10);
972
973 // Set allow_compaction to true. So number of files should be between 1 and 5.
974 ASSERT_OK(dbfull()->SetOptions(
975 {{"compaction_options_fifo", "{allow_compaction=true;}"}}));
976 ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
977 true);
978 dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
979 ASSERT_OK(dbfull()->TEST_WaitForCompact());
980 ASSERT_GE(NumTableFilesAtLevel(0), 1);
981 ASSERT_LE(NumTableFilesAtLevel(0), 5);
982 }
983
984 TEST_F(DBOptionsTest, CompactionReadaheadSizeChange) {
985 SpecialEnv env(env_);
986 Options options;
987 options.env = &env;
988
989 options.compaction_readahead_size = 0;
990 options.new_table_reader_for_compaction_inputs = true;
991 options.level0_file_num_compaction_trigger = 2;
992 const std::string kValue(1024, 'v');
993 Reopen(options);
994
995 ASSERT_EQ(0, dbfull()->GetDBOptions().compaction_readahead_size);
996 ASSERT_OK(dbfull()->SetDBOptions({{"compaction_readahead_size", "256"}}));
997 ASSERT_EQ(256, dbfull()->GetDBOptions().compaction_readahead_size);
998 for (int i = 0; i < 1024; i++) {
999 Put(Key(i), kValue);
1000 }
1001 Flush();
1002 for (int i = 0; i < 1024 * 2; i++) {
1003 Put(Key(i), kValue);
1004 }
1005 Flush();
1006 dbfull()->TEST_WaitForCompact();
1007 ASSERT_EQ(256, env_->compaction_readahead_size_);
1008 Close();
1009 }
1010 #endif // ROCKSDB_LITE
1011
1012 } // namespace rocksdb
1013
1014 int main(int argc, char** argv) {
1015 rocksdb::port::InstallStackTraceHandler();
1016 ::testing::InitGoogleTest(&argc, argv);
1017 return RUN_ALL_TESTS();
1018 }