/ src / leveldb / db / recovery_test.cc
recovery_test.cc
  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"
  8  #include "db/write_batch_internal.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_(nullptr) {
 21      dbname_ = test::TmpDir() + "/recovery_test";
 22      DestroyDB(dbname_, Options());
 23      Open();
 24    }
 25  
 26    ~RecoveryTest() {
 27      Close();
 28      DestroyDB(dbname_, Options());
 29    }
 30  
 31    DBImpl* dbfull() const { return reinterpret_cast<DBImpl*>(db_); }
 32    Env* env() const { return env_; }
 33  
 34    bool CanAppend() {
 35      WritableFile* tmp;
 36      Status s = env_->NewAppendableFile(CurrentFileName(dbname_), &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_ = nullptr;
 48    }
 49  
 50    Status OpenWithStatus(Options* options = nullptr) {
 51      Close();
 52      Options opts;
 53      if (options != nullptr) {
 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 == nullptr) {
 60        opts.env = env_;
 61      }
 62      return DB::Open(opts, dbname_, &db_);
 63    }
 64  
 65    void Open(Options* options = nullptr) {
 66      ASSERT_OK(OpenWithStatus(options));
 67      ASSERT_EQ(1, NumLogs());
 68    }
 69  
 70    Status Put(const std::string& k, const std::string& v) {
 71      return db_->Put(WriteOptions(), k, v);
 72    }
 73  
 74    std::string Get(const std::string& k, const Snapshot* snapshot = nullptr) {
 75      std::string result;
 76      Status s = db_->Get(ReadOptions(), k, &result);
 77      if (s.IsNotFound()) {
 78        result = "NOT_FOUND";
 79      } else if (!s.ok()) {
 80        result = s.ToString();
 81      }
 82      return result;
 83    }
 84  
 85    std::string ManifestFileName() {
 86      std::string current;
 87      ASSERT_OK(ReadFileToString(env_, CurrentFileName(dbname_), &current));
 88      size_t len = current.size();
 89      if (len > 0 && current[len - 1] == '\n') {
 90        current.resize(len - 1);
 91      }
 92      return dbname_ + "/" + current;
 93    }
 94  
 95    std::string LogName(uint64_t number) { return LogFileName(dbname_, number); }
 96  
 97    size_t DeleteLogFiles() {
 98      // Linux allows unlinking open files, but Windows does not.
 99      // Closing the db allows for file deletion.
100      Close();
101      std::vector<uint64_t> logs = GetFiles(kLogFile);
102      for (size_t i = 0; i < logs.size(); i++) {
103        ASSERT_OK(env_->DeleteFile(LogName(logs[i]))) << LogName(logs[i]);
104      }
105      return logs.size();
106    }
107  
108    void DeleteManifestFile() { ASSERT_OK(env_->DeleteFile(ManifestFileName())); }
109  
110    uint64_t FirstLogFile() { return GetFiles(kLogFile)[0]; }
111  
112    std::vector<uint64_t> GetFiles(FileType t) {
113      std::vector<std::string> filenames;
114      ASSERT_OK(env_->GetChildren(dbname_, &filenames));
115      std::vector<uint64_t> result;
116      for (size_t i = 0; i < filenames.size(); i++) {
117        uint64_t number;
118        FileType type;
119        if (ParseFileName(filenames[i], &number, &type) && type == t) {
120          result.push_back(number);
121        }
122      }
123      return result;
124    }
125  
126    int NumLogs() { return GetFiles(kLogFile).size(); }
127  
128    int NumTables() { return GetFiles(kTableFile).size(); }
129  
130    uint64_t FileSize(const std::string& fname) {
131      uint64_t result;
132      ASSERT_OK(env_->GetFileSize(fname, &result)) << fname;
133      return result;
134    }
135  
136    void CompactMemTable() { dbfull()->TEST_CompactMemTable(); }
137  
138    // Directly construct a log file that sets key to val.
139    void MakeLogFile(uint64_t lognum, SequenceNumber seq, Slice key, Slice val) {
140      std::string fname = LogFileName(dbname_, lognum);
141      WritableFile* file;
142      ASSERT_OK(env_->NewWritableFile(fname, &file));
143      log::Writer writer(file);
144      WriteBatch batch;
145      batch.Put(key, val);
146      WriteBatchInternal::SetSequence(&batch, seq);
147      ASSERT_OK(writer.AddRecord(WriteBatchInternal::Contents(&batch)));
148      ASSERT_OK(file->Flush());
149      delete file;
150    }
151  
152   private:
153    std::string dbname_;
154    Env* env_;
155    DB* db_;
156  };
157  
158  TEST(RecoveryTest, ManifestReused) {
159    if (!CanAppend()) {
160      fprintf(stderr, "skipping test because env does not support appending\n");
161      return;
162    }
163    ASSERT_OK(Put("foo", "bar"));
164    Close();
165    std::string old_manifest = ManifestFileName();
166    Open();
167    ASSERT_EQ(old_manifest, ManifestFileName());
168    ASSERT_EQ("bar", Get("foo"));
169    Open();
170    ASSERT_EQ(old_manifest, ManifestFileName());
171    ASSERT_EQ("bar", Get("foo"));
172  }
173  
174  TEST(RecoveryTest, LargeManifestCompacted) {
175    if (!CanAppend()) {
176      fprintf(stderr, "skipping test because env does not support appending\n");
177      return;
178    }
179    ASSERT_OK(Put("foo", "bar"));
180    Close();
181    std::string old_manifest = ManifestFileName();
182  
183    // Pad with zeroes to make manifest file very big.
184    {
185      uint64_t len = FileSize(old_manifest);
186      WritableFile* file;
187      ASSERT_OK(env()->NewAppendableFile(old_manifest, &file));
188      std::string zeroes(3 * 1048576 - static_cast<size_t>(len), 0);
189      ASSERT_OK(file->Append(zeroes));
190      ASSERT_OK(file->Flush());
191      delete file;
192    }
193  
194    Open();
195    std::string new_manifest = ManifestFileName();
196    ASSERT_NE(old_manifest, new_manifest);
197    ASSERT_GT(10000, FileSize(new_manifest));
198    ASSERT_EQ("bar", Get("foo"));
199  
200    Open();
201    ASSERT_EQ(new_manifest, ManifestFileName());
202    ASSERT_EQ("bar", Get("foo"));
203  }
204  
205  TEST(RecoveryTest, NoLogFiles) {
206    ASSERT_OK(Put("foo", "bar"));
207    ASSERT_EQ(1, DeleteLogFiles());
208    Open();
209    ASSERT_EQ("NOT_FOUND", Get("foo"));
210    Open();
211    ASSERT_EQ("NOT_FOUND", Get("foo"));
212  }
213  
214  TEST(RecoveryTest, LogFileReuse) {
215    if (!CanAppend()) {
216      fprintf(stderr, "skipping test because env does not support appending\n");
217      return;
218    }
219    for (int i = 0; i < 2; i++) {
220      ASSERT_OK(Put("foo", "bar"));
221      if (i == 0) {
222        // Compact to ensure current log is empty
223        CompactMemTable();
224      }
225      Close();
226      ASSERT_EQ(1, NumLogs());
227      uint64_t number = FirstLogFile();
228      if (i == 0) {
229        ASSERT_EQ(0, FileSize(LogName(number)));
230      } else {
231        ASSERT_LT(0, FileSize(LogName(number)));
232      }
233      Open();
234      ASSERT_EQ(1, NumLogs());
235      ASSERT_EQ(number, FirstLogFile()) << "did not reuse log file";
236      ASSERT_EQ("bar", Get("foo"));
237      Open();
238      ASSERT_EQ(1, NumLogs());
239      ASSERT_EQ(number, FirstLogFile()) << "did not reuse log file";
240      ASSERT_EQ("bar", Get("foo"));
241    }
242  }
243  
244  TEST(RecoveryTest, MultipleMemTables) {
245    // Make a large log.
246    const int kNum = 1000;
247    for (int i = 0; i < kNum; i++) {
248      char buf[100];
249      snprintf(buf, sizeof(buf), "%050d", i);
250      ASSERT_OK(Put(buf, buf));
251    }
252    ASSERT_EQ(0, NumTables());
253    Close();
254    ASSERT_EQ(0, NumTables());
255    ASSERT_EQ(1, NumLogs());
256    uint64_t old_log_file = FirstLogFile();
257  
258    // Force creation of multiple memtables by reducing the write buffer size.
259    Options opt;
260    opt.reuse_logs = true;
261    opt.write_buffer_size = (kNum * 100) / 2;
262    Open(&opt);
263    ASSERT_LE(2, NumTables());
264    ASSERT_EQ(1, NumLogs());
265    ASSERT_NE(old_log_file, FirstLogFile()) << "must not reuse log";
266    for (int i = 0; i < kNum; i++) {
267      char buf[100];
268      snprintf(buf, sizeof(buf), "%050d", i);
269      ASSERT_EQ(buf, Get(buf));
270    }
271  }
272  
273  TEST(RecoveryTest, MultipleLogFiles) {
274    ASSERT_OK(Put("foo", "bar"));
275    Close();
276    ASSERT_EQ(1, NumLogs());
277  
278    // Make a bunch of uncompacted log files.
279    uint64_t old_log = FirstLogFile();
280    MakeLogFile(old_log + 1, 1000, "hello", "world");
281    MakeLogFile(old_log + 2, 1001, "hi", "there");
282    MakeLogFile(old_log + 3, 1002, "foo", "bar2");
283  
284    // Recover and check that all log files were processed.
285    Open();
286    ASSERT_LE(1, NumTables());
287    ASSERT_EQ(1, NumLogs());
288    uint64_t new_log = FirstLogFile();
289    ASSERT_LE(old_log + 3, new_log);
290    ASSERT_EQ("bar2", Get("foo"));
291    ASSERT_EQ("world", Get("hello"));
292    ASSERT_EQ("there", Get("hi"));
293  
294    // Test that previous recovery produced recoverable state.
295    Open();
296    ASSERT_LE(1, NumTables());
297    ASSERT_EQ(1, NumLogs());
298    if (CanAppend()) {
299      ASSERT_EQ(new_log, FirstLogFile());
300    }
301    ASSERT_EQ("bar2", Get("foo"));
302    ASSERT_EQ("world", Get("hello"));
303    ASSERT_EQ("there", Get("hi"));
304  
305    // Check that introducing an older log file does not cause it to be re-read.
306    Close();
307    MakeLogFile(old_log + 1, 2000, "hello", "stale write");
308    Open();
309    ASSERT_LE(1, NumTables());
310    ASSERT_EQ(1, NumLogs());
311    if (CanAppend()) {
312      ASSERT_EQ(new_log, FirstLogFile());
313    }
314    ASSERT_EQ("bar2", Get("foo"));
315    ASSERT_EQ("world", Get("hello"));
316    ASSERT_EQ("there", Get("hi"));
317  }
318  
319  TEST(RecoveryTest, ManifestMissing) {
320    ASSERT_OK(Put("foo", "bar"));
321    Close();
322    DeleteManifestFile();
323  
324    Status status = OpenWithStatus();
325    ASSERT_TRUE(status.IsCorruption());
326  }
327  
328  }  // namespace leveldb
329  
330  int main(int argc, char** argv) { return leveldb::test::RunAllTests(); }