1 // Copyright (c) 2016-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).
12 #include "db/db_impl/db_impl.h"
13 #include "db/db_test_util.h"
14 #include "file/file_util.h"
15 #include "rocksdb/comparator.h"
16 #include "rocksdb/db.h"
17 #include "rocksdb/transaction_log.h"
18 #include "util/string_util.h"
20 namespace ROCKSDB_NAMESPACE
{
23 class RepairTest
: public DBTestBase
{
25 RepairTest() : DBTestBase("/repair_test", /*env_do_fsync=*/true) {}
27 Status
GetFirstSstPath(std::string
* first_sst_path
) {
28 assert(first_sst_path
!= nullptr);
29 first_sst_path
->clear();
30 uint64_t manifest_size
;
31 std::vector
<std::string
> files
;
32 Status s
= db_
->GetLiveFiles(files
, &manifest_size
);
35 std::find_if(files
.begin(), files
.end(), [](const std::string
& file
) {
38 bool ok
= ParseFileName(file
, &number
, &type
);
39 return ok
&& type
== kTableFile
;
41 *first_sst_path
= sst_iter
== files
.end() ? "" : dbname_
+ *sst_iter
;
47 TEST_F(RepairTest
, LostManifest
) {
48 // Add a couple SST files, delete the manifest, and verify RepairDB() saves
50 ASSERT_OK(Put("key", "val"));
52 ASSERT_OK(Put("key2", "val2"));
54 // Need to get path before Close() deletes db_, but delete it after Close() to
55 // ensure Close() didn't change the manifest.
56 std::string manifest_path
=
57 DescriptorFileName(dbname_
, dbfull()->TEST_Current_Manifest_FileNo());
60 ASSERT_OK(env_
->FileExists(manifest_path
));
61 ASSERT_OK(env_
->DeleteFile(manifest_path
));
62 ASSERT_OK(RepairDB(dbname_
, CurrentOptions()));
63 Reopen(CurrentOptions());
65 ASSERT_EQ(Get("key"), "val");
66 ASSERT_EQ(Get("key2"), "val2");
69 TEST_F(RepairTest
, CorruptManifest
) {
70 // Manifest is in an invalid format. Expect a full recovery.
71 ASSERT_OK(Put("key", "val"));
73 ASSERT_OK(Put("key2", "val2"));
75 // Need to get path before Close() deletes db_, but overwrite it after Close()
76 // to ensure Close() didn't change the manifest.
77 std::string manifest_path
=
78 DescriptorFileName(dbname_
, dbfull()->TEST_Current_Manifest_FileNo());
81 ASSERT_OK(env_
->FileExists(manifest_path
));
83 LegacyFileSystemWrapper
fs(env_
);
84 ASSERT_OK(CreateFile(&fs
, manifest_path
, "blah", false /* use_fsync */));
85 ASSERT_OK(RepairDB(dbname_
, CurrentOptions()));
86 Reopen(CurrentOptions());
88 ASSERT_EQ(Get("key"), "val");
89 ASSERT_EQ(Get("key2"), "val2");
92 TEST_F(RepairTest
, IncompleteManifest
) {
93 // In this case, the manifest is valid but does not reference all of the SST
94 // files. Expect a full recovery.
95 ASSERT_OK(Put("key", "val"));
97 std::string orig_manifest_path
=
98 DescriptorFileName(dbname_
, dbfull()->TEST_Current_Manifest_FileNo());
99 CopyFile(orig_manifest_path
, orig_manifest_path
+ ".tmp");
100 ASSERT_OK(Put("key2", "val2"));
102 // Need to get path before Close() deletes db_, but overwrite it after Close()
103 // to ensure Close() didn't change the manifest.
104 std::string new_manifest_path
=
105 DescriptorFileName(dbname_
, dbfull()->TEST_Current_Manifest_FileNo());
108 ASSERT_OK(env_
->FileExists(new_manifest_path
));
109 // Replace the manifest with one that is only aware of the first SST file.
110 CopyFile(orig_manifest_path
+ ".tmp", new_manifest_path
);
111 ASSERT_OK(RepairDB(dbname_
, CurrentOptions()));
112 Reopen(CurrentOptions());
114 ASSERT_EQ(Get("key"), "val");
115 ASSERT_EQ(Get("key2"), "val2");
118 TEST_F(RepairTest
, PostRepairSstFileNumbering
) {
119 // Verify after a DB is repaired, new files will be assigned higher numbers
121 ASSERT_OK(Put("key", "val"));
123 ASSERT_OK(Put("key2", "val2"));
125 uint64_t pre_repair_file_num
= dbfull()->TEST_Current_Next_FileNo();
128 ASSERT_OK(RepairDB(dbname_
, CurrentOptions()));
130 Reopen(CurrentOptions());
131 uint64_t post_repair_file_num
= dbfull()->TEST_Current_Next_FileNo();
132 ASSERT_GE(post_repair_file_num
, pre_repair_file_num
);
135 TEST_F(RepairTest
, LostSst
) {
136 // Delete one of the SST files but preserve the manifest that refers to it,
137 // then verify the DB is still usable for the intact SST.
138 ASSERT_OK(Put("key", "val"));
140 ASSERT_OK(Put("key2", "val2"));
142 std::string sst_path
;
143 ASSERT_OK(GetFirstSstPath(&sst_path
));
144 ASSERT_FALSE(sst_path
.empty());
145 ASSERT_OK(env_
->DeleteFile(sst_path
));
148 ASSERT_OK(RepairDB(dbname_
, CurrentOptions()));
149 Reopen(CurrentOptions());
151 // Exactly one of the key-value pairs should be in the DB now.
152 ASSERT_TRUE((Get("key") == "val") != (Get("key2") == "val2"));
155 TEST_F(RepairTest
, CorruptSst
) {
156 // Corrupt one of the SST files but preserve the manifest that refers to it,
157 // then verify the DB is still usable for the intact SST.
158 ASSERT_OK(Put("key", "val"));
160 ASSERT_OK(Put("key2", "val2"));
162 std::string sst_path
;
163 ASSERT_OK(GetFirstSstPath(&sst_path
));
164 ASSERT_FALSE(sst_path
.empty());
166 LegacyFileSystemWrapper
fs(env_
);
167 ASSERT_OK(CreateFile(&fs
, sst_path
, "blah", false /* use_fsync */));
170 ASSERT_OK(RepairDB(dbname_
, CurrentOptions()));
171 Reopen(CurrentOptions());
173 // Exactly one of the key-value pairs should be in the DB now.
174 ASSERT_TRUE((Get("key") == "val") != (Get("key2") == "val2"));
177 TEST_F(RepairTest
, UnflushedSst
) {
178 // This test case invokes repair while some data is unflushed, then verifies
179 // that data is in the db.
180 ASSERT_OK(Put("key", "val"));
181 VectorLogPtr wal_files
;
182 ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files
));
183 ASSERT_EQ(wal_files
.size(), 1);
185 uint64_t total_ssts_size
;
186 std::unordered_map
<std::string
, uint64_t> sst_files
;
187 ASSERT_OK(GetAllSSTFiles(&sst_files
, &total_ssts_size
));
188 ASSERT_EQ(total_ssts_size
, 0);
190 // Need to get path before Close() deletes db_, but delete it after Close() to
191 // ensure Close() didn't change the manifest.
192 std::string manifest_path
=
193 DescriptorFileName(dbname_
, dbfull()->TEST_Current_Manifest_FileNo());
196 ASSERT_OK(env_
->FileExists(manifest_path
));
197 ASSERT_OK(env_
->DeleteFile(manifest_path
));
198 ASSERT_OK(RepairDB(dbname_
, CurrentOptions()));
199 Reopen(CurrentOptions());
201 ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files
));
202 ASSERT_EQ(wal_files
.size(), 0);
204 uint64_t total_ssts_size
;
205 std::unordered_map
<std::string
, uint64_t> sst_files
;
206 ASSERT_OK(GetAllSSTFiles(&sst_files
, &total_ssts_size
));
207 ASSERT_GT(total_ssts_size
, 0);
209 ASSERT_EQ(Get("key"), "val");
212 TEST_F(RepairTest
, SeparateWalDir
) {
214 Options options
= CurrentOptions();
215 DestroyAndReopen(options
);
216 ASSERT_OK(Put("key", "val"));
217 ASSERT_OK(Put("foo", "bar"));
218 VectorLogPtr wal_files
;
219 ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files
));
220 ASSERT_EQ(wal_files
.size(), 1);
222 uint64_t total_ssts_size
;
223 std::unordered_map
<std::string
, uint64_t> sst_files
;
224 ASSERT_OK(GetAllSSTFiles(&sst_files
, &total_ssts_size
));
225 ASSERT_EQ(total_ssts_size
, 0);
227 std::string manifest_path
=
228 DescriptorFileName(dbname_
, dbfull()->TEST_Current_Manifest_FileNo());
231 ASSERT_OK(env_
->FileExists(manifest_path
));
232 ASSERT_OK(env_
->DeleteFile(manifest_path
));
233 ASSERT_OK(RepairDB(dbname_
, options
));
235 // make sure that all WALs are converted to SSTables.
236 options
.wal_dir
= "";
239 ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files
));
240 ASSERT_EQ(wal_files
.size(), 0);
242 uint64_t total_ssts_size
;
243 std::unordered_map
<std::string
, uint64_t> sst_files
;
244 ASSERT_OK(GetAllSSTFiles(&sst_files
, &total_ssts_size
));
245 ASSERT_GT(total_ssts_size
, 0);
247 ASSERT_EQ(Get("key"), "val");
248 ASSERT_EQ(Get("foo"), "bar");
250 } while(ChangeWalOptions());
253 TEST_F(RepairTest
, RepairMultipleColumnFamilies
) {
254 // Verify repair logic associates SST files with their original column
256 const int kNumCfs
= 3;
257 const int kEntriesPerCf
= 2;
258 DestroyAndReopen(CurrentOptions());
259 CreateAndReopenWithCF({"pikachu1", "pikachu2"}, CurrentOptions());
260 for (int i
= 0; i
< kNumCfs
; ++i
) {
261 for (int j
= 0; j
< kEntriesPerCf
; ++j
) {
262 ASSERT_OK(Put(i
, "key" + ToString(j
), "val" + ToString(j
)));
263 if (j
== kEntriesPerCf
- 1 && i
== kNumCfs
- 1) {
264 // Leave one unflushed so we can verify WAL entries are properly
265 // associated with column families.
272 // Need to get path before Close() deletes db_, but delete it after Close() to
273 // ensure Close() doesn't re-create the manifest.
274 std::string manifest_path
=
275 DescriptorFileName(dbname_
, dbfull()->TEST_Current_Manifest_FileNo());
277 ASSERT_OK(env_
->FileExists(manifest_path
));
278 ASSERT_OK(env_
->DeleteFile(manifest_path
));
280 ASSERT_OK(RepairDB(dbname_
, CurrentOptions()));
282 ReopenWithColumnFamilies({"default", "pikachu1", "pikachu2"},
284 for (int i
= 0; i
< kNumCfs
; ++i
) {
285 for (int j
= 0; j
< kEntriesPerCf
; ++j
) {
286 ASSERT_EQ(Get(i
, "key" + ToString(j
)), "val" + ToString(j
));
291 TEST_F(RepairTest
, RepairColumnFamilyOptions
) {
292 // Verify repair logic uses correct ColumnFamilyOptions when repairing a
293 // database with different options for column families.
294 const int kNumCfs
= 2;
295 const int kEntriesPerCf
= 2;
297 Options
opts(CurrentOptions()), rev_opts(CurrentOptions());
298 opts
.comparator
= BytewiseComparator();
299 rev_opts
.comparator
= ReverseBytewiseComparator();
301 DestroyAndReopen(opts
);
302 CreateColumnFamilies({"reverse"}, rev_opts
);
303 ReopenWithColumnFamilies({"default", "reverse"},
304 std::vector
<Options
>{opts
, rev_opts
});
305 for (int i
= 0; i
< kNumCfs
; ++i
) {
306 for (int j
= 0; j
< kEntriesPerCf
; ++j
) {
307 ASSERT_OK(Put(i
, "key" + ToString(j
), "val" + ToString(j
)));
308 if (i
== kNumCfs
- 1 && j
== kEntriesPerCf
- 1) {
309 // Leave one unflushed so we can verify RepairDB's flush logic
317 // RepairDB() records the comparator in the manifest, and DB::Open would fail
318 // if a different comparator were used.
319 ASSERT_OK(RepairDB(dbname_
, opts
, {{"default", opts
}, {"reverse", rev_opts
}},
320 opts
/* unknown_cf_opts */));
321 ASSERT_OK(TryReopenWithColumnFamilies({"default", "reverse"},
322 std::vector
<Options
>{opts
, rev_opts
}));
323 for (int i
= 0; i
< kNumCfs
; ++i
) {
324 for (int j
= 0; j
< kEntriesPerCf
; ++j
) {
325 ASSERT_EQ(Get(i
, "key" + ToString(j
)), "val" + ToString(j
));
329 // Examine table properties to verify RepairDB() used the right options when
330 // converting WAL->SST
331 TablePropertiesCollection fname_to_props
;
332 ASSERT_OK(db_
->GetPropertiesOfAllTables(handles_
[1], &fname_to_props
));
333 ASSERT_EQ(fname_to_props
.size(), 2U);
334 for (const auto& fname_and_props
: fname_to_props
) {
335 std::string
comparator_name (
336 InternalKeyComparator(rev_opts
.comparator
).Name());
337 comparator_name
= comparator_name
.substr(comparator_name
.find(':') + 1);
338 ASSERT_EQ(comparator_name
,
339 fname_and_props
.second
->comparator_name
);
343 // Also check comparator when it's provided via "unknown" CF options
344 ASSERT_OK(RepairDB(dbname_
, opts
, {{"default", opts
}},
345 rev_opts
/* unknown_cf_opts */));
346 ASSERT_OK(TryReopenWithColumnFamilies({"default", "reverse"},
347 std::vector
<Options
>{opts
, rev_opts
}));
348 for (int i
= 0; i
< kNumCfs
; ++i
) {
349 for (int j
= 0; j
< kEntriesPerCf
; ++j
) {
350 ASSERT_EQ(Get(i
, "key" + ToString(j
)), "val" + ToString(j
));
355 TEST_F(RepairTest
, DbNameContainsTrailingSlash
) {
358 if (env_
->AreFilesSame("", "", &tmp
).IsNotSupported()) {
360 "skipping RepairTest.DbNameContainsTrailingSlash due to "
361 "unsupported Env::AreFilesSame\n");
366 ASSERT_OK(Put("key", "val"));
370 ASSERT_OK(RepairDB(dbname_
+ "/", CurrentOptions()));
371 Reopen(CurrentOptions());
372 ASSERT_EQ(Get("key"), "val");
374 #endif // ROCKSDB_LITE
375 } // namespace ROCKSDB_NAMESPACE
377 int main(int argc
, char** argv
) {
378 ::testing::InitGoogleTest(&argc
, argv
);
379 return RUN_ALL_TESTS();
385 int main(int /*argc*/, char** /*argv*/) {
386 fprintf(stderr
, "SKIPPED as RepairDB is not supported in ROCKSDB_LITE\n");
390 #endif // ROCKSDB_LITE