db_table_properties_test.cc 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
  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 <memory>
  10. #include <unordered_set>
  11. #include <vector>
  12. #include "db/db_test_util.h"
  13. #include "port/port.h"
  14. #include "port/stack_trace.h"
  15. #include "rocksdb/db.h"
  16. #include "rocksdb/types.h"
  17. #include "rocksdb/utilities/table_properties_collectors.h"
  18. #include "table/format.h"
  19. #include "table/meta_blocks.h"
  20. #include "table/table_properties_internal.h"
  21. #include "test_util/testharness.h"
  22. #include "test_util/testutil.h"
  23. #include "util/atomic.h"
  24. #include "util/random.h"
  25. namespace ROCKSDB_NAMESPACE {
  26. // A helper function that ensures the table properties returned in
  27. // `GetPropertiesOfAllTablesTest` is correct.
  28. // This test assumes entries size is different for each of the tables.
  29. namespace {
  30. void VerifyTableProperties(DB* db, uint64_t expected_entries_size) {
  31. TablePropertiesCollection props;
  32. ASSERT_OK(db->GetPropertiesOfAllTables(&props));
  33. ASSERT_EQ(4U, props.size());
  34. std::unordered_set<uint64_t> unique_entries;
  35. // Indirect test
  36. uint64_t sum = 0;
  37. for (const auto& item : props) {
  38. unique_entries.insert(item.second->num_entries);
  39. sum += item.second->num_entries;
  40. }
  41. ASSERT_EQ(props.size(), unique_entries.size());
  42. ASSERT_EQ(expected_entries_size, sum);
  43. VerifySstUniqueIds(props);
  44. }
  45. } // anonymous namespace
  46. class DBTablePropertiesTest : public DBTestBase,
  47. public testing::WithParamInterface<std::string> {
  48. public:
  49. DBTablePropertiesTest()
  50. : DBTestBase("db_table_properties_test", /*env_do_fsync=*/false) {}
  51. };
  52. TEST_F(DBTablePropertiesTest, GetPropertiesOfAllTablesTest) {
  53. Options options = CurrentOptions();
  54. options.level0_file_num_compaction_trigger = 8;
  55. // Part of strategy to prevent pinning table files
  56. options.max_open_files = 42;
  57. Reopen(options);
  58. // Create 4 tables
  59. for (int table = 0; table < 4; ++table) {
  60. // Use old meta name for table properties for one file
  61. if (table == 3) {
  62. SyncPoint::GetInstance()->SetCallBack(
  63. "BlockBasedTableBuilder::WritePropertiesBlock:Meta", [&](void* meta) {
  64. *static_cast<const std::string**>(meta) = &kPropertiesBlockOldName;
  65. });
  66. SyncPoint::GetInstance()->EnableProcessing();
  67. }
  68. // Build file
  69. for (int i = 0; i < 10 + table; ++i) {
  70. ASSERT_OK(
  71. db_->Put(WriteOptions(), std::to_string(table * 100 + i), "val"));
  72. }
  73. ASSERT_OK(db_->Flush(FlushOptions()));
  74. }
  75. SyncPoint::GetInstance()->DisableProcessing();
  76. std::string original_session_id;
  77. ASSERT_OK(db_->GetDbSessionId(original_session_id));
  78. // Part of strategy to prevent pinning table files
  79. SyncPoint::GetInstance()->SetCallBack(
  80. "VersionEditHandler::LoadTables:skip_load_table_files",
  81. [&](void* skip_load) { *static_cast<bool*>(skip_load) = true; });
  82. SyncPoint::GetInstance()->EnableProcessing();
  83. // 1. Read table properties directly from file
  84. Reopen(options);
  85. // Clear out auto-opened files
  86. dbfull()->TEST_table_cache()->EraseUnRefEntries();
  87. ASSERT_EQ(dbfull()->TEST_table_cache()->GetUsage(), 0U);
  88. VerifyTableProperties(db_, 10 + 11 + 12 + 13);
  89. // 2. Put two tables to table cache and
  90. Reopen(options);
  91. // Clear out auto-opened files
  92. dbfull()->TEST_table_cache()->EraseUnRefEntries();
  93. ASSERT_EQ(dbfull()->TEST_table_cache()->GetUsage(), 0U);
  94. // fetch key from 1st and 2nd table, which will internally place that table to
  95. // the table cache.
  96. for (int i = 0; i < 2; ++i) {
  97. Get(std::to_string(i * 100 + 0));
  98. }
  99. VerifyTableProperties(db_, 10 + 11 + 12 + 13);
  100. // 3. Put all tables to table cache
  101. Reopen(options);
  102. // fetch key from all tables, which will place them in table cache.
  103. for (int i = 0; i < 4; ++i) {
  104. Get(std::to_string(i * 100 + 0));
  105. }
  106. VerifyTableProperties(db_, 10 + 11 + 12 + 13);
  107. // 4. Try to read CORRUPT properties (a) directly from file, and (b)
  108. // through reader on Get
  109. // It's not practical to prevent table file read on Open, so we
  110. // corrupt after open and after purging table cache.
  111. for (bool direct : {true, false}) {
  112. Reopen(options);
  113. // Clear out auto-opened files
  114. dbfull()->TEST_table_cache()->EraseUnRefEntries();
  115. ASSERT_EQ(dbfull()->TEST_table_cache()->GetUsage(), 0U);
  116. TablePropertiesCollection props;
  117. ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
  118. std::string sst_file = props.begin()->first;
  119. // Corrupt the file's TableProperties using session id
  120. std::string contents;
  121. ASSERT_OK(
  122. ReadFileToString(env_->GetFileSystem().get(), sst_file, &contents));
  123. size_t pos = contents.find(original_session_id);
  124. ASSERT_NE(pos, std::string::npos);
  125. ASSERT_OK(test::CorruptFile(env_, sst_file, static_cast<int>(pos), 1,
  126. /*verify checksum fails*/ false));
  127. // Try to read CORRUPT properties
  128. if (direct) {
  129. ASSERT_TRUE(db_->GetPropertiesOfAllTables(&props).IsCorruption());
  130. } else {
  131. bool found_corruption = false;
  132. for (int i = 0; i < 4; ++i) {
  133. std::string result = Get(std::to_string(i * 100 + 0));
  134. if (result.find_first_of("Corruption: block checksum mismatch") !=
  135. std::string::npos) {
  136. found_corruption = true;
  137. }
  138. }
  139. ASSERT_TRUE(found_corruption);
  140. }
  141. // UN-corrupt file for next iteration
  142. ASSERT_OK(test::CorruptFile(env_, sst_file, static_cast<int>(pos), 1,
  143. /*verify checksum fails*/ false));
  144. }
  145. SyncPoint::GetInstance()->DisableProcessing();
  146. }
  147. TEST_F(DBTablePropertiesTest, InvalidIgnored) {
  148. // RocksDB versions 2.5 - 2.7 generate some properties that Block considers
  149. // invalid in some way. This approximates that.
  150. // Inject properties block data that Block considers invalid
  151. SyncPoint::GetInstance()->SetCallBack(
  152. "BlockBasedTableBuilder::WritePropertiesBlock:BlockData",
  153. [&](void* block_data) { *static_cast<Slice*>(block_data) = Slice("X"); });
  154. SyncPoint::GetInstance()->EnableProcessing();
  155. // Corrupting the table properties corrupts the unique id.
  156. // Ignore the unique id recorded in the manifest.
  157. auto options = CurrentOptions();
  158. options.verify_sst_unique_id_in_manifest = false;
  159. Reopen(options);
  160. // Build file
  161. for (int i = 0; i < 10; ++i) {
  162. ASSERT_OK(db_->Put(WriteOptions(), std::to_string(i), "val"));
  163. }
  164. ASSERT_OK(db_->Flush(FlushOptions()));
  165. SyncPoint::GetInstance()->DisableProcessing();
  166. // Not crashing is good enough
  167. TablePropertiesCollection props;
  168. ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
  169. }
  170. TEST_F(DBTablePropertiesTest, CreateOnDeletionCollectorFactory) {
  171. ConfigOptions options;
  172. options.ignore_unsupported_options = false;
  173. std::shared_ptr<TablePropertiesCollectorFactory> factory;
  174. std::string id = CompactOnDeletionCollectorFactory::kClassName();
  175. ASSERT_OK(
  176. TablePropertiesCollectorFactory::CreateFromString(options, id, &factory));
  177. auto del_factory = factory->CheckedCast<CompactOnDeletionCollectorFactory>();
  178. ASSERT_NE(del_factory, nullptr);
  179. ASSERT_EQ(0U, del_factory->GetWindowSize());
  180. ASSERT_EQ(0U, del_factory->GetDeletionTrigger());
  181. ASSERT_EQ(0.0, del_factory->GetDeletionRatio());
  182. ASSERT_OK(TablePropertiesCollectorFactory::CreateFromString(
  183. options, "window_size=100; deletion_trigger=90; id=" + id, &factory));
  184. del_factory = factory->CheckedCast<CompactOnDeletionCollectorFactory>();
  185. ASSERT_NE(del_factory, nullptr);
  186. ASSERT_EQ(100U, del_factory->GetWindowSize());
  187. ASSERT_EQ(90U, del_factory->GetDeletionTrigger());
  188. ASSERT_EQ(0.0, del_factory->GetDeletionRatio());
  189. ASSERT_OK(TablePropertiesCollectorFactory::CreateFromString(
  190. options,
  191. "window_size=100; deletion_trigger=90; deletion_ratio=0.5; id=" + id,
  192. &factory));
  193. del_factory = factory->CheckedCast<CompactOnDeletionCollectorFactory>();
  194. ASSERT_NE(del_factory, nullptr);
  195. ASSERT_EQ(100U, del_factory->GetWindowSize());
  196. ASSERT_EQ(90U, del_factory->GetDeletionTrigger());
  197. ASSERT_EQ(0.5, del_factory->GetDeletionRatio());
  198. }
  199. TEST_F(DBTablePropertiesTest, GetPropertiesOfTablesByLevelTest) {
  200. Random rnd(202);
  201. Options options;
  202. options.level_compaction_dynamic_level_bytes = false;
  203. options.create_if_missing = true;
  204. options.write_buffer_size = 4096;
  205. options.max_write_buffer_number = 2;
  206. options.level0_file_num_compaction_trigger = 2;
  207. options.level0_slowdown_writes_trigger = 2;
  208. options.level0_stop_writes_trigger = 2;
  209. options.target_file_size_base = 2048;
  210. options.max_bytes_for_level_base = 40960;
  211. options.max_bytes_for_level_multiplier = 4;
  212. options.hard_pending_compaction_bytes_limit = 16 * 1024;
  213. options.num_levels = 8;
  214. options.env = env_;
  215. DestroyAndReopen(options);
  216. // build a decent LSM
  217. for (int i = 0; i < 10000; i++) {
  218. EXPECT_OK(Put(test::RandomKey(&rnd, 5), rnd.RandomString(102)));
  219. }
  220. ASSERT_OK(Flush());
  221. ASSERT_OK(dbfull()->TEST_WaitForCompact());
  222. if (NumTableFilesAtLevel(0) == 0) {
  223. EXPECT_OK(Put(test::RandomKey(&rnd, 5), rnd.RandomString(102)));
  224. ASSERT_OK(Flush());
  225. }
  226. ASSERT_OK(db_->PauseBackgroundWork());
  227. // Ensure that we have at least L0, L1 and L2
  228. ASSERT_GT(NumTableFilesAtLevel(0), 0);
  229. ASSERT_GT(NumTableFilesAtLevel(1), 0);
  230. ASSERT_GT(NumTableFilesAtLevel(2), 0);
  231. ColumnFamilyMetaData cf_meta;
  232. db_->GetColumnFamilyMetaData(&cf_meta);
  233. std::vector<std::unique_ptr<TablePropertiesCollection>> levels_props;
  234. ASSERT_OK(db_->GetPropertiesOfTablesByLevel(db_->DefaultColumnFamily(),
  235. &levels_props));
  236. for (int i = 0; i < 8; i++) {
  237. const std::unique_ptr<TablePropertiesCollection>& level_props =
  238. levels_props[i];
  239. ASSERT_EQ(level_props->size(), cf_meta.levels[i].files.size());
  240. }
  241. Close();
  242. }
  243. // Test params:
  244. // 1) whether to enable user-defined timestamps
  245. class DBTablePropertiesInRangeTest : public DBTestBase,
  246. public testing::WithParamInterface<bool> {
  247. public:
  248. DBTablePropertiesInRangeTest()
  249. : DBTestBase("db_table_properties_in_range_test",
  250. /*env_do_fsync=*/false) {}
  251. void SetUp() override { enable_udt_ = GetParam(); }
  252. protected:
  253. void PutKeyValue(const Slice& key, const Slice& value) {
  254. if (enable_udt_) {
  255. EXPECT_OK(db_->Put(WriteOptions(), key, min_ts_, value));
  256. } else {
  257. EXPECT_OK(Put(key, value));
  258. }
  259. }
  260. std::string GetValue(const std::string& key) {
  261. ReadOptions roptions;
  262. std::string result;
  263. if (enable_udt_) {
  264. roptions.timestamp = &min_ts_;
  265. }
  266. Status s = db_->Get(roptions, key, &result);
  267. EXPECT_TRUE(s.ok());
  268. return result;
  269. }
  270. Status MaybeGetValue(const std::string& key, std::string* result) {
  271. ReadOptions roptions;
  272. if (enable_udt_) {
  273. roptions.timestamp = &min_ts_;
  274. }
  275. Status s = db_->Get(roptions, key, result);
  276. EXPECT_TRUE(s.IsNotFound() || s.ok());
  277. return s;
  278. }
  279. TablePropertiesCollection TestGetPropertiesOfTablesInRange(
  280. std::vector<Range> ranges, std::size_t* num_properties = nullptr,
  281. std::size_t* num_files = nullptr) {
  282. // Since we deref zero element in the vector it can not be empty
  283. // otherwise we pass an address to some random memory
  284. EXPECT_GT(ranges.size(), 0U);
  285. // run the query
  286. TablePropertiesCollection props;
  287. ColumnFamilyHandle* default_cf = db_->DefaultColumnFamily();
  288. EXPECT_OK(db_->GetPropertiesOfTablesInRange(default_cf, ranges.data(),
  289. ranges.size(), &props));
  290. const Comparator* ucmp = default_cf->GetComparator();
  291. EXPECT_NE(ucmp, nullptr);
  292. const size_t ts_sz = ucmp->timestamp_size();
  293. const size_t range_size = ranges.size();
  294. autovector<UserKeyRange> ukey_ranges;
  295. std::vector<std::string> keys;
  296. ukey_ranges.reserve(range_size);
  297. keys.reserve(range_size * 2);
  298. for (auto& r : ranges) {
  299. auto [start, limit] = MaybeAddTimestampsToRange(
  300. r.start, r.limit, ts_sz, &keys.emplace_back(), &keys.emplace_back(),
  301. /*exclusive_end=*/false);
  302. EXPECT_TRUE(start.has_value());
  303. EXPECT_TRUE(limit.has_value());
  304. ukey_ranges.emplace_back(start.value(), limit.value());
  305. }
  306. // Make sure that we've received properties for those and for those files
  307. // only which fall within requested ranges
  308. std::vector<LiveFileMetaData> vmd;
  309. db_->GetLiveFilesMetaData(&vmd);
  310. for (auto& md : vmd) {
  311. std::string fn = md.db_path + md.name;
  312. bool in_range = false;
  313. for (auto& r : ukey_ranges) {
  314. if (ucmp->Compare(r.start, md.largestkey) <= 0 &&
  315. ucmp->Compare(r.limit, md.smallestkey) >= 0) {
  316. in_range = true;
  317. EXPECT_GT(props.count(fn), 0);
  318. }
  319. }
  320. if (!in_range) {
  321. EXPECT_EQ(props.count(fn), 0);
  322. }
  323. }
  324. if (num_properties) {
  325. *num_properties = props.size();
  326. }
  327. if (num_files) {
  328. *num_files = vmd.size();
  329. }
  330. return props;
  331. }
  332. bool enable_udt_ = false;
  333. Slice min_ts_ = MinU64Ts();
  334. };
  335. TEST_P(DBTablePropertiesInRangeTest, GetPropertiesOfTablesInRange) {
  336. // Fixed random sead
  337. Random rnd(301);
  338. Options options;
  339. options.level_compaction_dynamic_level_bytes = false;
  340. options.create_if_missing = true;
  341. options.write_buffer_size = 4096;
  342. options.max_write_buffer_number = 2;
  343. options.level0_file_num_compaction_trigger = 2;
  344. options.level0_slowdown_writes_trigger = 2;
  345. options.level0_stop_writes_trigger = 2;
  346. options.target_file_size_base = 2048;
  347. options.max_bytes_for_level_base = 40960;
  348. options.max_bytes_for_level_multiplier = 4;
  349. options.hard_pending_compaction_bytes_limit = 16 * 1024;
  350. options.num_levels = 8;
  351. options.env = env_;
  352. bool udt_enabled = GetParam();
  353. if (udt_enabled) {
  354. options.comparator = test::BytewiseComparatorWithU64TsWrapper();
  355. }
  356. DestroyAndReopen(options);
  357. // build a decent LSM
  358. for (int i = 0; i < 10000; i++) {
  359. PutKeyValue(test::RandomKey(&rnd, 5), rnd.RandomString(102));
  360. }
  361. ASSERT_OK(Flush());
  362. ASSERT_OK(dbfull()->TEST_WaitForCompact());
  363. if (NumTableFilesAtLevel(0) == 0) {
  364. PutKeyValue(test::RandomKey(&rnd, 5), rnd.RandomString(102));
  365. ASSERT_OK(Flush());
  366. }
  367. ASSERT_OK(db_->PauseBackgroundWork());
  368. // Ensure that we have at least L0, L1 and L2
  369. ASSERT_GT(NumTableFilesAtLevel(0), 0);
  370. ASSERT_GT(NumTableFilesAtLevel(1), 0);
  371. ASSERT_GT(NumTableFilesAtLevel(2), 0);
  372. // Query the largest range
  373. std::size_t num_properties, num_files;
  374. TestGetPropertiesOfTablesInRange(
  375. {Range(test::RandomKey(&rnd, 5, test::RandomKeyType::SMALLEST),
  376. test::RandomKey(&rnd, 5, test::RandomKeyType::LARGEST))},
  377. &num_properties, &num_files);
  378. ASSERT_EQ(num_properties, num_files);
  379. // Query the empty range
  380. TestGetPropertiesOfTablesInRange(
  381. {Range(test::RandomKey(&rnd, 5, test::RandomKeyType::LARGEST),
  382. test::RandomKey(&rnd, 5, test::RandomKeyType::SMALLEST))},
  383. &num_properties, &num_files);
  384. ASSERT_GT(num_files, 0);
  385. ASSERT_EQ(num_properties, 0);
  386. // Query the middle rangee
  387. TestGetPropertiesOfTablesInRange(
  388. {Range(test::RandomKey(&rnd, 5, test::RandomKeyType::MIDDLE),
  389. test::RandomKey(&rnd, 5, test::RandomKeyType::LARGEST))},
  390. &num_properties, &num_files);
  391. ASSERT_GT(num_files, 0);
  392. ASSERT_GT(num_files, num_properties);
  393. ASSERT_GT(num_properties, 0);
  394. // Query a bunch of random ranges
  395. for (int j = 0; j < 100; j++) {
  396. // create a bunch of ranges
  397. std::vector<std::string> random_keys;
  398. // Random returns numbers with zero included
  399. // when we pass empty ranges TestGetPropertiesOfTablesInRange()
  400. // derefs random memory in the empty ranges[0]
  401. // so want to be greater than zero and even since
  402. // the below loop requires that random_keys.size() to be even.
  403. auto n = 2 * (rnd.Uniform(50) + 1);
  404. for (uint32_t i = 0; i < n; ++i) {
  405. random_keys.push_back(test::RandomKey(&rnd, 5));
  406. }
  407. ASSERT_GT(random_keys.size(), 0U);
  408. ASSERT_EQ((random_keys.size() % 2), 0U);
  409. std::vector<Range> ranges;
  410. auto it = random_keys.begin();
  411. while (it != random_keys.end()) {
  412. ranges.emplace_back(*it, *(it + 1));
  413. it += 2;
  414. }
  415. TestGetPropertiesOfTablesInRange(std::move(ranges));
  416. }
  417. }
  418. INSTANTIATE_TEST_CASE_P(DBTablePropertiesInRangeTest,
  419. DBTablePropertiesInRangeTest,
  420. ::testing::Values(true, false));
  421. TEST_F(DBTablePropertiesTest, GetColumnFamilyNameProperty) {
  422. std::string kExtraCfName = "pikachu";
  423. CreateAndReopenWithCF({kExtraCfName}, CurrentOptions());
  424. // Create one table per CF, then verify it was created with the column family
  425. // name property.
  426. for (uint32_t cf = 0; cf < 2; ++cf) {
  427. ASSERT_OK(Put(cf, "key", "val"));
  428. ASSERT_OK(Flush(cf));
  429. TablePropertiesCollection fname_to_props;
  430. ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[cf], &fname_to_props));
  431. ASSERT_EQ(1U, fname_to_props.size());
  432. std::string expected_cf_name;
  433. if (cf > 0) {
  434. expected_cf_name = kExtraCfName;
  435. } else {
  436. expected_cf_name = kDefaultColumnFamilyName;
  437. }
  438. ASSERT_EQ(expected_cf_name,
  439. fname_to_props.begin()->second->column_family_name);
  440. ASSERT_EQ(cf, static_cast<uint32_t>(
  441. fname_to_props.begin()->second->column_family_id));
  442. }
  443. }
  444. TEST_F(DBTablePropertiesTest, GetDbIdentifiersProperty) {
  445. CreateAndReopenWithCF({"goku"}, CurrentOptions());
  446. for (uint32_t cf = 0; cf < 2; ++cf) {
  447. ASSERT_OK(Put(cf, "key", "val"));
  448. ASSERT_OK(Put(cf, "foo", "bar"));
  449. ASSERT_OK(Flush(cf));
  450. TablePropertiesCollection fname_to_props;
  451. ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[cf], &fname_to_props));
  452. ASSERT_EQ(1U, fname_to_props.size());
  453. std::string id, sid;
  454. ASSERT_OK(db_->GetDbIdentity(id));
  455. ASSERT_OK(db_->GetDbSessionId(sid));
  456. ASSERT_EQ(id, fname_to_props.begin()->second->db_id);
  457. ASSERT_EQ(sid, fname_to_props.begin()->second->db_session_id);
  458. }
  459. }
  460. TEST_F(DBTablePropertiesTest, FactoryReturnsNull) {
  461. struct JunkTablePropertiesCollector : public TablePropertiesCollector {
  462. const char* Name() const override { return "JunkTablePropertiesCollector"; }
  463. Status Finish(UserCollectedProperties* properties) override {
  464. properties->insert({"Junk", "Junk"});
  465. return Status::OK();
  466. }
  467. UserCollectedProperties GetReadableProperties() const override {
  468. return {};
  469. }
  470. };
  471. // Alternates between putting a "Junk" property and using `nullptr` to
  472. // opt out.
  473. static RelaxedAtomic<int> count{0};
  474. struct SometimesTablePropertiesCollectorFactory
  475. : public TablePropertiesCollectorFactory {
  476. const char* Name() const override {
  477. return "SometimesTablePropertiesCollectorFactory";
  478. }
  479. TablePropertiesCollector* CreateTablePropertiesCollector(
  480. TablePropertiesCollectorFactory::Context /*context*/) override {
  481. if (count.FetchAddRelaxed(1) & 1) {
  482. return nullptr;
  483. } else {
  484. return new JunkTablePropertiesCollector();
  485. }
  486. }
  487. };
  488. Options options = CurrentOptions();
  489. options.table_properties_collector_factories.emplace_back(
  490. std::make_shared<SometimesTablePropertiesCollectorFactory>());
  491. // For plain table
  492. options.prefix_extractor.reset(NewFixedPrefixTransform(4));
  493. for (const std::shared_ptr<TableFactory>& tf :
  494. {options.table_factory,
  495. std::shared_ptr<TableFactory>(NewPlainTableFactory({}))}) {
  496. SCOPED_TRACE("Table factory = " + std::string(tf->Name()));
  497. options.table_factory = tf;
  498. DestroyAndReopen(options);
  499. ASSERT_OK(Put("key0", "value1"));
  500. ASSERT_OK(Flush());
  501. ASSERT_OK(Put("key0", "value2"));
  502. ASSERT_OK(Flush());
  503. TablePropertiesCollection props;
  504. ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
  505. int no_junk_count = 0;
  506. int junk_count = 0;
  507. for (const auto& item : props) {
  508. if (item.second->user_collected_properties.find("Junk") !=
  509. item.second->user_collected_properties.end()) {
  510. junk_count++;
  511. } else {
  512. no_junk_count++;
  513. }
  514. }
  515. EXPECT_EQ(1, no_junk_count);
  516. EXPECT_EQ(1, junk_count);
  517. }
  518. }
  519. class DBTableHostnamePropertyTest
  520. : public DBTestBase,
  521. public ::testing::WithParamInterface<std::tuple<int, std::string>> {
  522. public:
  523. DBTableHostnamePropertyTest()
  524. : DBTestBase("db_table_hostname_property_test",
  525. /*env_do_fsync=*/false) {}
  526. };
  527. TEST_P(DBTableHostnamePropertyTest, DbHostLocationProperty) {
  528. option_config_ = std::get<0>(GetParam());
  529. Options opts = CurrentOptions();
  530. std::string expected_host_id = std::get<1>(GetParam());
  531. ;
  532. if (expected_host_id == kHostnameForDbHostId) {
  533. ASSERT_OK(env_->GetHostNameString(&expected_host_id));
  534. } else {
  535. opts.db_host_id = expected_host_id;
  536. }
  537. CreateAndReopenWithCF({"goku"}, opts);
  538. for (uint32_t cf = 0; cf < 2; ++cf) {
  539. ASSERT_OK(Put(cf, "key", "val"));
  540. ASSERT_OK(Put(cf, "foo", "bar"));
  541. ASSERT_OK(Flush(cf));
  542. TablePropertiesCollection fname_to_props;
  543. ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[cf], &fname_to_props));
  544. ASSERT_EQ(1U, fname_to_props.size());
  545. ASSERT_EQ(fname_to_props.begin()->second->db_host_id, expected_host_id);
  546. }
  547. }
  548. INSTANTIATE_TEST_CASE_P(
  549. DBTableHostnamePropertyTest, DBTableHostnamePropertyTest,
  550. ::testing::Values(
  551. // OptionConfig, override db_host_location
  552. std::make_tuple(DBTestBase::OptionConfig::kDefault,
  553. kHostnameForDbHostId),
  554. std::make_tuple(DBTestBase::OptionConfig::kDefault, "foobar"),
  555. std::make_tuple(DBTestBase::OptionConfig::kDefault, ""),
  556. std::make_tuple(DBTestBase::OptionConfig::kPlainTableFirstBytePrefix,
  557. kHostnameForDbHostId),
  558. std::make_tuple(DBTestBase::OptionConfig::kPlainTableFirstBytePrefix,
  559. "foobar"),
  560. std::make_tuple(DBTestBase::OptionConfig::kPlainTableFirstBytePrefix,
  561. "")));
  562. class DeletionTriggeredCompactionTestListener : public EventListener {
  563. public:
  564. void OnCompactionBegin(DB*, const CompactionJobInfo& ci) override {
  565. ASSERT_EQ(ci.compaction_reason,
  566. CompactionReason::kFilesMarkedForCompaction);
  567. }
  568. void OnCompactionCompleted(DB*, const CompactionJobInfo& ci) override {
  569. ASSERT_EQ(ci.compaction_reason,
  570. CompactionReason::kFilesMarkedForCompaction);
  571. }
  572. };
  573. TEST_P(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
  574. int kNumKeys = 1000;
  575. int kWindowSize = 100;
  576. int kNumDelsTrigger = 90;
  577. std::shared_ptr<TablePropertiesCollectorFactory> compact_on_del =
  578. NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger);
  579. Options opts = CurrentOptions();
  580. opts.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
  581. opts.table_properties_collector_factories.emplace_back(compact_on_del);
  582. if (GetParam() == "kCompactionStyleUniversal") {
  583. opts.compaction_style = kCompactionStyleUniversal;
  584. }
  585. Reopen(opts);
  586. // add an L1 file to prevent tombstones from dropping due to obsolescence
  587. // during flush
  588. ASSERT_OK(Put(Key(0), "val"));
  589. ASSERT_OK(Flush());
  590. MoveFilesToLevel(1);
  591. DeletionTriggeredCompactionTestListener* listener =
  592. new DeletionTriggeredCompactionTestListener();
  593. opts.listeners.emplace_back(listener);
  594. Reopen(opts);
  595. for (int i = 0; i < kNumKeys; ++i) {
  596. if (i >= kNumKeys - kWindowSize &&
  597. i < kNumKeys - kWindowSize + kNumDelsTrigger) {
  598. ASSERT_OK(Delete(Key(i)));
  599. } else {
  600. ASSERT_OK(Put(Key(i), "val"));
  601. }
  602. }
  603. ASSERT_OK(Flush());
  604. ASSERT_OK(dbfull()->TEST_WaitForCompact());
  605. ASSERT_EQ(0, NumTableFilesAtLevel(0));
  606. // Change the window size and deletion trigger and ensure new values take
  607. // effect
  608. kWindowSize = 50;
  609. kNumDelsTrigger = 40;
  610. static_cast<CompactOnDeletionCollectorFactory*>(compact_on_del.get())
  611. ->SetWindowSize(kWindowSize);
  612. static_cast<CompactOnDeletionCollectorFactory*>(compact_on_del.get())
  613. ->SetDeletionTrigger(kNumDelsTrigger);
  614. for (int i = 0; i < kNumKeys; ++i) {
  615. if (i >= kNumKeys - kWindowSize &&
  616. i < kNumKeys - kWindowSize + kNumDelsTrigger) {
  617. ASSERT_OK(Delete(Key(i)));
  618. } else {
  619. ASSERT_OK(Put(Key(i), "val"));
  620. }
  621. }
  622. ASSERT_OK(Flush());
  623. ASSERT_OK(dbfull()->TEST_WaitForCompact());
  624. ASSERT_EQ(0, NumTableFilesAtLevel(0));
  625. // Change the window size to disable delete triggered compaction
  626. kWindowSize = 0;
  627. static_cast<CompactOnDeletionCollectorFactory*>(compact_on_del.get())
  628. ->SetWindowSize(kWindowSize);
  629. static_cast<CompactOnDeletionCollectorFactory*>(compact_on_del.get())
  630. ->SetDeletionTrigger(kNumDelsTrigger);
  631. for (int i = 0; i < kNumKeys; ++i) {
  632. if (i >= kNumKeys - kWindowSize &&
  633. i < kNumKeys - kWindowSize + kNumDelsTrigger) {
  634. ASSERT_OK(Delete(Key(i)));
  635. } else {
  636. ASSERT_OK(Put(Key(i), "val"));
  637. }
  638. }
  639. ASSERT_OK(Flush());
  640. ASSERT_OK(dbfull()->TEST_WaitForCompact());
  641. ASSERT_EQ(1, NumTableFilesAtLevel(0));
  642. ASSERT_LT(0, opts.statistics->getTickerCount(COMPACT_WRITE_BYTES_MARKED));
  643. ASSERT_LT(0, opts.statistics->getTickerCount(COMPACT_READ_BYTES_MARKED));
  644. }
  645. TEST_P(DBTablePropertiesTest, RatioBasedDeletionTriggeredCompactionMarking) {
  646. constexpr int kNumKeys = 1000;
  647. constexpr int kWindowSize = 0;
  648. constexpr int kNumDelsTrigger = 0;
  649. constexpr double kDeletionRatio = 0.1;
  650. std::shared_ptr<TablePropertiesCollectorFactory> compact_on_del =
  651. NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger,
  652. kDeletionRatio);
  653. Options opts = CurrentOptions();
  654. opts.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
  655. opts.table_properties_collector_factories.emplace_back(compact_on_del);
  656. Reopen(opts);
  657. // Add an L2 file to prevent tombstones from dropping due to obsolescence
  658. // during flush
  659. ASSERT_OK(Put(Key(0), "val"));
  660. ASSERT_OK(Flush());
  661. MoveFilesToLevel(2);
  662. auto* listener = new DeletionTriggeredCompactionTestListener();
  663. opts.listeners.emplace_back(listener);
  664. Reopen(opts);
  665. // Generate one L0 with kNumKeys Put.
  666. for (int i = 0; i < kNumKeys; ++i) {
  667. ASSERT_OK(Put(Key(i), "not important"));
  668. }
  669. ASSERT_OK(Flush());
  670. // Generate another L0 with kNumKeys Delete.
  671. // This file, due to deletion ratio, will trigger compaction: 2@0 files to L1.
  672. // The resulting L1 file has only one tombstone for user key 'Key(0)'.
  673. // Again, due to deletion ratio, a compaction will be triggered: 1@1 + 1@2
  674. // files to L2. However, the resulting file is empty because the tombstone
  675. // and value are both dropped.
  676. for (int i = 0; i < kNumKeys; ++i) {
  677. ASSERT_OK(Delete(Key(i)));
  678. }
  679. ASSERT_OK(Flush());
  680. ASSERT_OK(dbfull()->TEST_WaitForCompact());
  681. for (int i = 0; i < 3; ++i) {
  682. ASSERT_EQ(0, NumTableFilesAtLevel(i));
  683. }
  684. }
  685. TEST_F(DBTablePropertiesTest, KeyLargestSmallestSeqno) {
  686. ASSERT_OK(db_->Put(WriteOptions(), "key1", "value1"));
  687. ASSERT_OK(db_->Put(WriteOptions(), "key2", "value2"));
  688. ASSERT_OK(db_->Put(WriteOptions(), "key3", "value3"));
  689. ASSERT_OK(db_->Flush(FlushOptions()));
  690. {
  691. TablePropertiesCollection props;
  692. ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
  693. ASSERT_EQ(1U, props.size());
  694. auto table_props = props.begin()->second;
  695. ASSERT_TRUE(table_props->HasKeyLargestSeqno());
  696. ASSERT_TRUE(table_props->HasKeySmallestSeqno());
  697. ASSERT_EQ(table_props->key_largest_seqno,
  698. table_props->key_smallest_seqno + 2);
  699. ASSERT_GT(table_props->key_largest_seqno, 0U);
  700. ASSERT_GT(table_props->key_smallest_seqno, 0U);
  701. }
  702. // Becomes zero after compaction
  703. {
  704. CompactRangeOptions cro;
  705. cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
  706. ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
  707. TablePropertiesCollection props;
  708. ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
  709. ASSERT_EQ(1U, props.size());
  710. auto table_props = props.begin()->second;
  711. ASSERT_TRUE(table_props->HasKeyLargestSeqno());
  712. ASSERT_TRUE(table_props->HasKeySmallestSeqno());
  713. ASSERT_EQ(table_props->key_largest_seqno, table_props->key_smallest_seqno);
  714. ASSERT_EQ(table_props->key_largest_seqno, 0U);
  715. }
  716. }
  717. INSTANTIATE_TEST_CASE_P(DBTablePropertiesTest, DBTablePropertiesTest,
  718. ::testing::Values("kCompactionStyleLevel",
  719. "kCompactionStyleUniversal"));
  720. } // namespace ROCKSDB_NAMESPACE
  721. int main(int argc, char** argv) {
  722. ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
  723. ::testing::InitGoogleTest(&argc, argv);
  724. return RUN_ALL_TESTS();
  725. }