leveldb
recovery_test.cc
Go to the documentation of this file.
1 // Copyright (c) 2014 The LevelDB Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. See the AUTHORS file for names of contributors.
4 
5 #include "db/db_impl.h"
6 #include "db/filename.h"
7 #include "db/version_set.h"
9 #include "leveldb/db.h"
10 #include "leveldb/env.h"
11 #include "leveldb/write_batch.h"
12 #include "util/logging.h"
13 #include "util/testharness.h"
14 #include "util/testutil.h"
15 
16 namespace leveldb {
17 
18 class RecoveryTest {
19  public:
20  RecoveryTest() : env_(Env::Default()), db_(NULL) {
21  dbname_ = test::TmpDir() + "/recovery_test";
23  Open();
24  }
25 
27  Close();
29  }
30 
31  DBImpl* dbfull() const { return reinterpret_cast<DBImpl*>(db_); }
32  Env* env() const { return env_; }
33 
34  bool CanAppend() {
35  WritableFile* tmp;
37  delete tmp;
38  if (s.IsNotSupportedError()) {
39  return false;
40  } else {
41  return true;
42  }
43  }
44 
45  void Close() {
46  delete db_;
47  db_ = NULL;
48  }
49 
50  void Open(Options* options = NULL) {
51  Close();
52  Options opts;
53  if (options != NULL) {
54  opts = *options;
55  } else {
56  opts.reuse_logs = true; // TODO(sanjay): test both ways
57  opts.create_if_missing = true;
58  }
59  if (opts.env == NULL) {
60  opts.env = env_;
61  }
62  ASSERT_OK(DB::Open(opts, dbname_, &db_));
63  ASSERT_EQ(1, NumLogs());
64  }
65 
66  Status Put(const std::string& k, const std::string& v) {
67  return db_->Put(WriteOptions(), k, v);
68  }
69 
70  std::string Get(const std::string& k, const Snapshot* snapshot = NULL) {
71  std::string result;
72  Status s = db_->Get(ReadOptions(), k, &result);
73  if (s.IsNotFound()) {
74  result = "NOT_FOUND";
75  } else if (!s.ok()) {
76  result = s.ToString();
77  }
78  return result;
79  }
80 
81  std::string ManifestFileName() {
82  std::string current;
84  size_t len = current.size();
85  if (len > 0 && current[len-1] == '\n') {
86  current.resize(len - 1);
87  }
88  return dbname_ + "/" + current;
89  }
90 
91  std::string LogName(uint64_t number) {
92  return LogFileName(dbname_, number);
93  }
94 
95  size_t DeleteLogFiles() {
96  std::vector<uint64_t> logs = GetFiles(kLogFile);
97  for (size_t i = 0; i < logs.size(); i++) {
98  ASSERT_OK(env_->DeleteFile(LogName(logs[i]))) << LogName(logs[i]);
99  }
100  return logs.size();
101  }
102 
103  uint64_t FirstLogFile() {
104  return GetFiles(kLogFile)[0];
105  }
106 
107  std::vector<uint64_t> GetFiles(FileType t) {
108  std::vector<std::string> filenames;
109  ASSERT_OK(env_->GetChildren(dbname_, &filenames));
110  std::vector<uint64_t> result;
111  for (size_t i = 0; i < filenames.size(); i++) {
112  uint64_t number;
113  FileType type;
114  if (ParseFileName(filenames[i], &number, &type) && type == t) {
115  result.push_back(number);
116  }
117  }
118  return result;
119  }
120 
121  int NumLogs() {
122  return GetFiles(kLogFile).size();
123  }
124 
125  int NumTables() {
126  return GetFiles(kTableFile).size();
127  }
128 
129  uint64_t FileSize(const std::string& fname) {
130  uint64_t result;
131  ASSERT_OK(env_->GetFileSize(fname, &result)) << fname;
132  return result;
133  }
134 
137  }
138 
139  // Directly construct a log file that sets key to val.
140  void MakeLogFile(uint64_t lognum, SequenceNumber seq, Slice key, Slice val) {
141  std::string fname = LogFileName(dbname_, lognum);
142  WritableFile* file;
143  ASSERT_OK(env_->NewWritableFile(fname, &file));
144  log::Writer writer(file);
145  WriteBatch batch;
146  batch.Put(key, val);
147  WriteBatchInternal::SetSequence(&batch, seq);
148  ASSERT_OK(writer.AddRecord(WriteBatchInternal::Contents(&batch)));
149  ASSERT_OK(file->Flush());
150  delete file;
151  }
152 
153  private:
154  std::string dbname_;
156  DB* db_;
157 };
158 
159 TEST(RecoveryTest, ManifestReused) {
160  if (!CanAppend()) {
161  fprintf(stderr, "skipping test because env does not support appending\n");
162  return;
163  }
164  ASSERT_OK(Put("foo", "bar"));
165  Close();
166  std::string old_manifest = ManifestFileName();
167  Open();
168  ASSERT_EQ(old_manifest, ManifestFileName());
169  ASSERT_EQ("bar", Get("foo"));
170  Open();
171  ASSERT_EQ(old_manifest, ManifestFileName());
172  ASSERT_EQ("bar", Get("foo"));
173 }
174 
175 TEST(RecoveryTest, LargeManifestCompacted) {
176  if (!CanAppend()) {
177  fprintf(stderr, "skipping test because env does not support appending\n");
178  return;
179  }
180  ASSERT_OK(Put("foo", "bar"));
181  Close();
182  std::string old_manifest = ManifestFileName();
183 
184  // Pad with zeroes to make manifest file very big.
185  {
186  uint64_t len = FileSize(old_manifest);
187  WritableFile* file;
188  ASSERT_OK(env()->NewAppendableFile(old_manifest, &file));
189  std::string zeroes(3*1048576 - static_cast<size_t>(len), 0);
190  ASSERT_OK(file->Append(zeroes));
191  ASSERT_OK(file->Flush());
192  delete file;
193  }
194 
195  Open();
196  std::string new_manifest = ManifestFileName();
197  ASSERT_NE(old_manifest, new_manifest);
198  ASSERT_GT(10000, FileSize(new_manifest));
199  ASSERT_EQ("bar", Get("foo"));
200 
201  Open();
202  ASSERT_EQ(new_manifest, ManifestFileName());
203  ASSERT_EQ("bar", Get("foo"));
204 }
205 
206 TEST(RecoveryTest, NoLogFiles) {
207  ASSERT_OK(Put("foo", "bar"));
209  Open();
210  ASSERT_EQ("NOT_FOUND", Get("foo"));
211  Open();
212  ASSERT_EQ("NOT_FOUND", Get("foo"));
213 }
214 
215 TEST(RecoveryTest, LogFileReuse) {
216  if (!CanAppend()) {
217  fprintf(stderr, "skipping test because env does not support appending\n");
218  return;
219  }
220  for (int i = 0; i < 2; i++) {
221  ASSERT_OK(Put("foo", "bar"));
222  if (i == 0) {
223  // Compact to ensure current log is empty
224  CompactMemTable();
225  }
226  Close();
227  ASSERT_EQ(1, NumLogs());
228  uint64_t number = FirstLogFile();
229  if (i == 0) {
230  ASSERT_EQ(0, FileSize(LogName(number)));
231  } else {
232  ASSERT_LT(0, FileSize(LogName(number)));
233  }
234  Open();
235  ASSERT_EQ(1, NumLogs());
236  ASSERT_EQ(number, FirstLogFile()) << "did not reuse log file";
237  ASSERT_EQ("bar", Get("foo"));
238  Open();
239  ASSERT_EQ(1, NumLogs());
240  ASSERT_EQ(number, FirstLogFile()) << "did not reuse log file";
241  ASSERT_EQ("bar", Get("foo"));
242  }
243 }
244 
245 TEST(RecoveryTest, MultipleMemTables) {
246  // Make a large log.
247  const int kNum = 1000;
248  for (int i = 0; i < kNum; i++) {
249  char buf[100];
250  snprintf(buf, sizeof(buf), "%050d", i);
251  ASSERT_OK(Put(buf, buf));
252  }
253  ASSERT_EQ(0, NumTables());
254  Close();
255  ASSERT_EQ(0, NumTables());
256  ASSERT_EQ(1, NumLogs());
257  uint64_t old_log_file = FirstLogFile();
258 
259  // Force creation of multiple memtables by reducing the write buffer size.
260  Options opt;
261  opt.reuse_logs = true;
262  opt.write_buffer_size = (kNum*100) / 2;
263  Open(&opt);
264  ASSERT_LE(2, NumTables());
265  ASSERT_EQ(1, NumLogs());
266  ASSERT_NE(old_log_file, FirstLogFile()) << "must not reuse log";
267  for (int i = 0; i < kNum; i++) {
268  char buf[100];
269  snprintf(buf, sizeof(buf), "%050d", i);
270  ASSERT_EQ(buf, Get(buf));
271  }
272 }
273 
274 TEST(RecoveryTest, MultipleLogFiles) {
275  ASSERT_OK(Put("foo", "bar"));
276  Close();
277  ASSERT_EQ(1, NumLogs());
278 
279  // Make a bunch of uncompacted log files.
280  uint64_t old_log = FirstLogFile();
281  MakeLogFile(old_log+1, 1000, "hello", "world");
282  MakeLogFile(old_log+2, 1001, "hi", "there");
283  MakeLogFile(old_log+3, 1002, "foo", "bar2");
284 
285  // Recover and check that all log files were processed.
286  Open();
287  ASSERT_LE(1, NumTables());
288  ASSERT_EQ(1, NumLogs());
289  uint64_t new_log = FirstLogFile();
290  ASSERT_LE(old_log+3, new_log);
291  ASSERT_EQ("bar2", Get("foo"));
292  ASSERT_EQ("world", Get("hello"));
293  ASSERT_EQ("there", Get("hi"));
294 
295  // Test that previous recovery produced recoverable state.
296  Open();
297  ASSERT_LE(1, NumTables());
298  ASSERT_EQ(1, NumLogs());
299  if (CanAppend()) {
300  ASSERT_EQ(new_log, FirstLogFile());
301  }
302  ASSERT_EQ("bar2", Get("foo"));
303  ASSERT_EQ("world", Get("hello"));
304  ASSERT_EQ("there", Get("hi"));
305 
306  // Check that introducing an older log file does not cause it to be re-read.
307  Close();
308  MakeLogFile(old_log+1, 2000, "hello", "stale write");
309  Open();
310  ASSERT_LE(1, NumTables());
311  ASSERT_EQ(1, NumLogs());
312  if (CanAppend()) {
313  ASSERT_EQ(new_log, FirstLogFile());
314  }
315  ASSERT_EQ("bar2", Get("foo"));
316  ASSERT_EQ("world", Get("hello"));
317  ASSERT_EQ("there", Get("hi"));
318 }
319 
320 } // namespace leveldb
321 
322 int main(int argc, char** argv) {
324 }
void MakeLogFile(uint64_t lognum, SequenceNumber seq, Slice key, Slice val)
virtual Status Flush()=0
Status Put(const std::string &k, const std::string &v)
void Open(Options *options=NULL)
bool ParseFileName(const std::string &fname, uint64_t *number, FileType *type)
Definition: filename.cc:80
size_t write_buffer_size
Definition: options.h:83
std::string TmpDir()
Definition: testharness.cc:60
#define ASSERT_GT(a, b)
Definition: testharness.h:110
#define ASSERT_LE(a, b)
Definition: testharness.h:111
#define ASSERT_OK(s)
Definition: testharness.h:106
static void SetSequence(WriteBatch *batch, SequenceNumber seq)
Definition: write_batch.cc:94
std::vector< uint64_t > GetFiles(FileType t)
int RunAllTests()
Definition: testharness.cc:36
virtual Status GetFileSize(const std::string &fname, uint64_t *file_size)=0
virtual Status NewWritableFile(const std::string &fname, WritableFile **result)=0
virtual Status Put(const WriteOptions &options, const Slice &key, const Slice &value)=0
Definition: db_impl.cc:1476
bool IsNotSupportedError() const
Definition: status.h:64
#define ASSERT_EQ(a, b)
Definition: testharness.h:107
Status DestroyDB(const std::string &dbname, const Options &options)
Definition: db_impl.cc:1537
uint64_t SequenceNumber
Definition: dbformat.h:63
std::string Get(const std::string &k, const Snapshot *snapshot=NULL)
std::string ManifestFileName()
std::string ToString() const
Definition: status.cc:36
virtual Status Get(const ReadOptions &options, const Slice &key, std::string *value)=0
static Status Open(const Options &options, const std::string &name, DB **dbptr)
Definition: db_impl.cc:1490
#define ASSERT_LT(a, b)
Definition: testharness.h:112
Status TEST_CompactMemTable()
Definition: db_impl.cc:621
std::string CurrentFileName(const std::string &dbname)
Definition: filename.cc:50
TEST(AutoCompactTest, ReadAll)
bool create_if_missing
Definition: options.h:45
#define ASSERT_NE(a, b)
Definition: testharness.h:108
virtual Status NewAppendableFile(const std::string &fname, WritableFile **result)
Definition: env.cc:12
int main(int argc, char **argv)
bool ok() const
Definition: status.h:52
std::string LogFileName(const std::string &name, uint64_t number)
Definition: filename.cc:27
static Slice Contents(const WriteBatch *batch)
std::string LogName(uint64_t number)
virtual Status Append(const Slice &data)=0
FileType
Definition: filename.h:20
DBImpl * dbfull() const
Definition: db.h:44
virtual Status DeleteFile(const std::string &fname)=0
bool IsNotFound() const
Definition: status.h:55
void Put(const Slice &key, const Slice &value)
Definition: write_batch.cc:98
uint64_t FileSize(const std::string &fname)
virtual Status GetChildren(const std::string &dir, std::vector< std::string > *result)=0
Status ReadFileToString(Env *env, const std::string &fname, std::string *data)
Definition: env.cc:72