Conflicts: db/db_impl.cc db/db_test.cc db/memtable.cc db/version_set.cc include/rocksdb/statistics.hmain
commit
f1cec73a76
@ -0,0 +1,133 @@ |
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once |
||||
#include "utilities/stackable_db.h" |
||||
#include "rocksdb/env.h" |
||||
#include "rocksdb/status.h" |
||||
|
||||
#include <string> |
||||
#include <map> |
||||
#include <vector> |
||||
|
||||
namespace rocksdb { |
||||
|
||||
struct BackupableDBOptions { |
||||
// Where to keep the backup files. Has to be different than dbname_
|
||||
// Best to set this to dbname_ + "/backups"
|
||||
// Required
|
||||
std::string backup_dir; |
||||
|
||||
// Backup Env object. It will be used for backup file I/O. If it's
|
||||
// nullptr, backups will be written out using DBs Env. If it's
|
||||
// non-nullptr, backup's I/O will be performed using this object.
|
||||
// If you want to have backups on HDFS, use HDFS Env here!
|
||||
// Default: nullptr
|
||||
Env* backup_env; |
||||
|
||||
// Backup info and error messages will be written to info_log
|
||||
// if non-nullptr.
|
||||
// Default: nullptr
|
||||
Logger* info_log; |
||||
|
||||
// If sync == true, we can guarantee you'll get consistent backup even
|
||||
// on a machine crash/reboot. Backup process is slower with sync enabled.
|
||||
// If sync == false, we don't guarantee anything on machine reboot. However,
|
||||
// chances are some of the backups are consistent.
|
||||
// Default: true
|
||||
bool sync; |
||||
|
||||
// If true, it will delete whatever backups there are already
|
||||
// Default: false
|
||||
bool destroy_old_data; |
||||
|
||||
explicit BackupableDBOptions(const std::string& _backup_dir, |
||||
Env* _backup_env = nullptr, |
||||
Logger* _info_log = nullptr, |
||||
bool _sync = true, |
||||
bool _destroy_old_data = false) : |
||||
backup_dir(_backup_dir), |
||||
backup_env(_backup_env), |
||||
info_log(_info_log), |
||||
sync(_sync), |
||||
destroy_old_data(_destroy_old_data) { } |
||||
}; |
||||
|
||||
class BackupEngine; |
||||
|
||||
typedef uint32_t BackupID; |
||||
|
||||
struct BackupInfo { |
||||
BackupID backup_id; |
||||
int64_t timestamp; |
||||
uint64_t size; |
||||
|
||||
BackupInfo() {} |
||||
BackupInfo(BackupID _backup_id, int64_t _timestamp, uint64_t _size) |
||||
: backup_id(_backup_id), timestamp(_timestamp), size(_size) {} |
||||
}; |
||||
|
||||
// Stack your DB with BackupableDB to be able to backup the DB
|
||||
class BackupableDB : public StackableDB { |
||||
public: |
||||
// BackupableDBOptions have to be the same as the ones used in a previous
|
||||
// incarnation of the DB
|
||||
//
|
||||
// BackupableDB ownes the pointer `DB* db` now. You should not delete it or
|
||||
// use it after the invocation of BackupableDB
|
||||
BackupableDB(DB* db, const BackupableDBOptions& options); |
||||
virtual ~BackupableDB(); |
||||
|
||||
// Captures the state of the database in the latest backup
|
||||
// NOT a thread safe call
|
||||
Status CreateNewBackup(bool flush_before_backup = false); |
||||
// Returns info about backups in backup_info
|
||||
void GetBackupInfo(std::vector<BackupInfo>* backup_info); |
||||
// deletes old backups, keeping latest num_backups_to_keep alive
|
||||
Status PurgeOldBackups(uint32_t num_backups_to_keep); |
||||
// deletes a specific backup
|
||||
Status DeleteBackup(BackupID backup_id); |
||||
|
||||
private: |
||||
BackupEngine* backup_engine_; |
||||
}; |
||||
|
||||
// Use this class to access information about backups and restore from them
|
||||
class RestoreBackupableDB { |
||||
public: |
||||
RestoreBackupableDB(Env* db_env, const BackupableDBOptions& options); |
||||
~RestoreBackupableDB(); |
||||
|
||||
// Returns info about backups in backup_info
|
||||
void GetBackupInfo(std::vector<BackupInfo>* backup_info); |
||||
|
||||
// restore from backup with backup_id
|
||||
// IMPORTANT -- if you restore from some backup that is not the latest,
|
||||
// and you start creating new backups from the new DB, all the backups
|
||||
// that were newer than the backup you restored from will be deleted
|
||||
//
|
||||
// Example: Let's say you have backups 1, 2, 3, 4, 5 and you restore 3.
|
||||
// If you try creating a new backup now, old backups 4 and 5 will be deleted
|
||||
// and new backup with ID 4 will be created.
|
||||
Status RestoreDBFromBackup(BackupID backup_id, const std::string& db_dir, |
||||
const std::string& wal_dir); |
||||
|
||||
// restore from the latest backup
|
||||
Status RestoreDBFromLatestBackup(const std::string& db_dir, |
||||
const std::string& wal_dir); |
||||
// deletes old backups, keeping latest num_backups_to_keep alive
|
||||
Status PurgeOldBackups(uint32_t num_backups_to_keep); |
||||
// deletes a specific backup
|
||||
Status DeleteBackup(BackupID backup_id); |
||||
|
||||
private: |
||||
BackupEngine* backup_engine_; |
||||
}; |
||||
|
||||
} // rocksdb namespace
|
@ -0,0 +1,329 @@ |
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
#pragma once |
||||
|
||||
#include <algorithm> |
||||
#include <cassert> |
||||
#include <stdexcept> |
||||
#include <iterator> |
||||
#include <vector> |
||||
|
||||
namespace rocksdb { |
||||
|
||||
// A vector that leverages pre-allocated stack-based array to achieve better
|
||||
// performance for array with small amount of items.
|
||||
//
|
||||
// The interface resembles that of vector, but with less features since we aim
|
||||
// to solve the problem that we have in hand, rather than implementing a
|
||||
// full-fledged generic container.
|
||||
//
|
||||
// Currently we don't support:
|
||||
// * reserve()/shrink_to_fit()/resize()
|
||||
// If used correctly, in most cases, people should not touch the
|
||||
// underlying vector at all.
|
||||
// * random insert()/erase(), please only use push_back()/pop_back().
|
||||
// * No move/swap operations. Each autovector instance has a
|
||||
// stack-allocated array and if we want support move/swap operations, we
|
||||
// need to copy the arrays other than just swapping the pointers. In this
|
||||
// case we'll just explicitly forbid these operations since they may
|
||||
// lead users to make false assumption by thinking they are inexpensive
|
||||
// operations.
|
||||
//
|
||||
// Naming style of public methods almost follows that of the STL's.
|
||||
template <class T, size_t kSize = 8> |
||||
class autovector { |
||||
public: |
||||
// General STL-style container member types.
|
||||
typedef T value_type; |
||||
typedef typename std::vector<T>::difference_type difference_type; |
||||
typedef typename std::vector<T>::size_type size_type; |
||||
typedef value_type& reference; |
||||
typedef const value_type& const_reference; |
||||
typedef value_type* pointer; |
||||
typedef const value_type* const_pointer; |
||||
|
||||
// This class is the base for regular/const iterator
|
||||
template <class TAutoVector, class TValueType> |
||||
class iterator_impl { |
||||
public: |
||||
// -- iterator traits
|
||||
typedef iterator_impl<TAutoVector, TValueType> self_type; |
||||
typedef TValueType value_type; |
||||
typedef TValueType& reference; |
||||
typedef TValueType* pointer; |
||||
typedef typename TAutoVector::difference_type difference_type; |
||||
typedef std::random_access_iterator_tag iterator_category; |
||||
|
||||
iterator_impl(TAutoVector* vect, size_t index) |
||||
: vect_(vect) |
||||
, index_(index) { |
||||
}; |
||||
iterator_impl(const iterator_impl&) = default; |
||||
~iterator_impl() { } |
||||
iterator_impl& operator=(const iterator_impl&) = default; |
||||
|
||||
// -- Advancement
|
||||
// iterator++
|
||||
self_type& operator++() { |
||||
++index_; |
||||
return *this; |
||||
} |
||||
|
||||
// ++iterator
|
||||
self_type operator++(int) { |
||||
auto old = *this; |
||||
++index_; |
||||
return old; |
||||
} |
||||
|
||||
// iterator--
|
||||
self_type& operator--() { |
||||
--index_; |
||||
return *this; |
||||
} |
||||
|
||||
// --iterator
|
||||
self_type operator--(int) { |
||||
auto old = *this; |
||||
--index_; |
||||
return old; |
||||
} |
||||
|
||||
self_type operator-(difference_type len) { |
||||
return self_type(vect_, index_ - len); |
||||
} |
||||
|
||||
difference_type operator-(const self_type& other) { |
||||
assert(vect_ == other.vect_); |
||||
return index_ - other.index_; |
||||
} |
||||
|
||||
self_type operator+(difference_type len) { |
||||
return self_type(vect_, index_ + len); |
||||
} |
||||
|
||||
self_type& operator+=(difference_type len) { |
||||
index_ += len; |
||||
return *this; |
||||
} |
||||
|
||||
self_type& operator-=(difference_type len) { |
||||
index_ -= len; |
||||
return *this; |
||||
} |
||||
|
||||
// -- Reference
|
||||
reference operator*() { |
||||
assert(vect_->size() >= index_); |
||||
return (*vect_)[index_]; |
||||
} |
||||
pointer operator->() { |
||||
assert(vect_->size() >= index_); |
||||
return &(*vect_)[index_]; |
||||
} |
||||
|
||||
// -- Logical Operators
|
||||
bool operator==(const self_type& other) const { |
||||
assert(vect_ == other.vect_); |
||||
return index_ == other.index_; |
||||
} |
||||
|
||||
bool operator!=(const self_type& other) const { |
||||
return !(*this == other); |
||||
} |
||||
|
||||
bool operator>(const self_type& other) const { |
||||
assert(vect_ == other.vect_); |
||||
return index_ > other.index_; |
||||
} |
||||
|
||||
bool operator<(const self_type& other) const { |
||||
assert(vect_ == other.vect_); |
||||
return index_ < other.index_; |
||||
} |
||||
|
||||
bool operator>=(const self_type& other) const { |
||||
assert(vect_ == other.vect_); |
||||
return index_ >= other.index_; |
||||
} |
||||
|
||||
bool operator<=(const self_type& other) const { |
||||
assert(vect_ == other.vect_); |
||||
return index_ <= other.index_; |
||||
} |
||||
|
||||
private: |
||||
TAutoVector* vect_ = nullptr; |
||||
size_t index_ = 0; |
||||
}; |
||||
|
||||
typedef iterator_impl<autovector, value_type> iterator; |
||||
typedef iterator_impl<const autovector, const value_type> const_iterator; |
||||
typedef std::reverse_iterator<iterator> reverse_iterator; |
||||
typedef std::reverse_iterator<const_iterator> const_reverse_iterator; |
||||
|
||||
autovector() = default; |
||||
~autovector() = default; |
||||
|
||||
// -- Immutable operations
|
||||
// Indicate if all data resides in in-stack data structure.
|
||||
bool only_in_stack() const { |
||||
// If no element was inserted at all, the vector's capacity will be `0`.
|
||||
return vect_.capacity() == 0; |
||||
} |
||||
|
||||
size_type size() const { |
||||
return num_stack_items_ + vect_.size(); |
||||
} |
||||
|
||||
bool empty() const { |
||||
return size() == 0; |
||||
} |
||||
|
||||
// will not check boundry
|
||||
const_reference operator[](size_type n) const { |
||||
return n < kSize ? values_[n] : vect_[n - kSize]; |
||||
} |
||||
|
||||
reference operator[](size_type n) { |
||||
return n < kSize ? values_[n] : vect_[n - kSize]; |
||||
} |
||||
|
||||
// will check boundry
|
||||
const_reference at(size_type n) const { |
||||
if (n >= size()) { |
||||
throw std::out_of_range("autovector: index out of range"); |
||||
} |
||||
return (*this)[n]; |
||||
} |
||||
|
||||
reference at(size_type n) { |
||||
if (n >= size()) { |
||||
throw std::out_of_range("autovector: index out of range"); |
||||
} |
||||
return (*this)[n]; |
||||
} |
||||
|
||||
reference front() { |
||||
assert(!empty()); |
||||
return *begin(); |
||||
} |
||||
|
||||
const_reference front() const { |
||||
assert(!empty()); |
||||
return *begin(); |
||||
} |
||||
|
||||
reference back() { |
||||
assert(!empty()); |
||||
return *(end() - 1); |
||||
} |
||||
|
||||
const_reference back() const { |
||||
assert(!empty()); |
||||
return *(end() - 1); |
||||
} |
||||
|
||||
// -- Mutable Operations
|
||||
void push_back(T&& item) { |
||||
if (num_stack_items_ < kSize) { |
||||
values_[num_stack_items_++] = std::move(item); |
||||
} else { |
||||
vect_.push_back(item); |
||||
} |
||||
} |
||||
|
||||
void push_back(const T& item) { |
||||
push_back(value_type(item)); |
||||
} |
||||
|
||||
template<class... Args> |
||||
void emplace_back(Args&&... args) { |
||||
push_back(value_type(args...)); |
||||
} |
||||
|
||||
void pop_back() { |
||||
assert(!empty()); |
||||
if (!vect_.empty()) { |
||||
vect_.pop_back(); |
||||
} else { |
||||
--num_stack_items_; |
||||
} |
||||
} |
||||
|
||||
void clear() { |
||||
num_stack_items_ = 0; |
||||
vect_.clear(); |
||||
} |
||||
|
||||
// -- Copy and Assignment
|
||||
autovector& assign(const autovector& other); |
||||
|
||||
autovector(const autovector& other) { |
||||
assign(other); |
||||
} |
||||
|
||||
autovector& operator=(const autovector& other) { |
||||
return assign(other); |
||||
} |
||||
|
||||
// move operation are disallowed since it is very hard to make sure both
|
||||
// autovectors are allocated from the same function stack.
|
||||
autovector& operator=(autovector&& other) = delete; |
||||
autovector(autovector&& other) = delete; |
||||
|
||||
// -- Iterator Operations
|
||||
iterator begin() { |
||||
return iterator(this, 0); |
||||
} |
||||
|
||||
const_iterator begin() const { |
||||
return const_iterator(this, 0); |
||||
} |
||||
|
||||
iterator end() { |
||||
return iterator(this, this->size()); |
||||
} |
||||
|
||||
const_iterator end() const { |
||||
return const_iterator(this, this->size()); |
||||
} |
||||
|
||||
reverse_iterator rbegin() { |
||||
return reverse_iterator(end()); |
||||
} |
||||
|
||||
const_reverse_iterator rbegin() const { |
||||
return const_reverse_iterator(end()); |
||||
} |
||||
|
||||
reverse_iterator rend() { |
||||
return reverse_iterator(begin()); |
||||
} |
||||
|
||||
const_reverse_iterator rend() const { |
||||
return const_reverse_iterator(begin()); |
||||
} |
||||
|
||||
private: |
||||
size_type num_stack_items_ = 0; // current number of items
|
||||
value_type values_[kSize]; // the first `kSize` items
|
||||
// used only if there are more than `kSize` items.
|
||||
std::vector<T> vect_; |
||||
}; |
||||
|
||||
template <class T, size_t kSize> |
||||
autovector<T, kSize>& autovector<T, kSize>::assign(const autovector& other) { |
||||
// copy the internal vector
|
||||
vect_.assign(other.vect_.begin(), other.vect_.end()); |
||||
|
||||
// copy array
|
||||
num_stack_items_ = other.num_stack_items_; |
||||
std::copy(other.values_, other.values_ + num_stack_items_, values_); |
||||
|
||||
return *this; |
||||
} |
||||
|
||||
} // rocksdb
|
@ -0,0 +1,290 @@ |
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include <atomic> |
||||
#include <iostream> |
||||
|
||||
#include "rocksdb/env.h" |
||||
#include "util/autovector.h" |
||||
#include "util/testharness.h" |
||||
#include "util/testutil.h" |
||||
|
||||
namespace rocksdb { |
||||
|
||||
using namespace std; |
||||
|
||||
class AutoVectorTest { }; |
||||
|
||||
const size_t kSize = 8; |
||||
TEST(AutoVectorTest, PushBackAndPopBack) { |
||||
autovector<size_t, kSize> vec; |
||||
ASSERT_TRUE(vec.empty()); |
||||
ASSERT_EQ(0ul, vec.size()); |
||||
|
||||
for (size_t i = 0; i < 1000 * kSize; ++i) { |
||||
vec.push_back(i); |
||||
ASSERT_TRUE(!vec.empty()); |
||||
if (i < kSize) { |
||||
ASSERT_TRUE(vec.only_in_stack()); |
||||
} else { |
||||
ASSERT_TRUE(!vec.only_in_stack()); |
||||
} |
||||
ASSERT_EQ(i + 1, vec.size()); |
||||
ASSERT_EQ(i, vec[i]); |
||||
ASSERT_EQ(i, vec.at(i)); |
||||
} |
||||
|
||||
size_t size = vec.size(); |
||||
while (size != 0) { |
||||
vec.pop_back(); |
||||
// will always be in heap
|
||||
ASSERT_TRUE(!vec.only_in_stack()); |
||||
ASSERT_EQ(--size, vec.size()); |
||||
} |
||||
|
||||
ASSERT_TRUE(vec.empty()); |
||||
} |
||||
|
||||
TEST(AutoVectorTest, EmplaceBack) { |
||||
typedef std::pair<size_t, std::string> ValueType; |
||||
autovector<ValueType, kSize> vec; |
||||
|
||||
for (size_t i = 0; i < 1000 * kSize; ++i) { |
||||
vec.emplace_back(i, std::to_string(i + 123)); |
||||
ASSERT_TRUE(!vec.empty()); |
||||
if (i < kSize) { |
||||
ASSERT_TRUE(vec.only_in_stack()); |
||||
} else { |
||||
ASSERT_TRUE(!vec.only_in_stack()); |
||||
} |
||||
|
||||
ASSERT_EQ(i + 1, vec.size()); |
||||
ASSERT_EQ(i, vec[i].first); |
||||
ASSERT_EQ(std::to_string(i + 123), vec[i].second); |
||||
} |
||||
|
||||
vec.clear(); |
||||
ASSERT_TRUE(vec.empty()); |
||||
ASSERT_TRUE(!vec.only_in_stack()); |
||||
} |
||||
|
||||
void AssertEqual( |
||||
const autovector<size_t, kSize>& a, const autovector<size_t, kSize>& b) { |
||||
ASSERT_EQ(a.size(), b.size()); |
||||
ASSERT_EQ(a.empty(), b.empty()); |
||||
ASSERT_EQ(a.only_in_stack(), b.only_in_stack()); |
||||
for (size_t i = 0; i < a.size(); ++i) { |
||||
ASSERT_EQ(a[i], b[i]); |
||||
} |
||||
} |
||||
|
||||
TEST(AutoVectorTest, CopyAndAssignment) { |
||||
// Test both heap-allocated and stack-allocated cases.
|
||||
for (auto size : { kSize / 2, kSize * 1000 }) { |
||||
autovector<size_t, kSize> vec; |
||||
for (size_t i = 0; i < size; ++i) { |
||||
vec.push_back(i); |
||||
} |
||||
|
||||
{ |
||||
autovector<size_t, kSize> other; |
||||
other = vec; |
||||
AssertEqual(other, vec); |
||||
} |
||||
|
||||
{ |
||||
autovector<size_t, kSize> other(vec); |
||||
AssertEqual(other, vec); |
||||
} |
||||
} |
||||
} |
||||
|
||||
TEST(AutoVectorTest, Iterators) { |
||||
autovector<std::string, kSize> vec; |
||||
for (size_t i = 0; i < kSize * 1000; ++i) { |
||||
vec.push_back(std::to_string(i)); |
||||
} |
||||
|
||||
// basic operator test
|
||||
ASSERT_EQ(vec.front(), *vec.begin()); |
||||
ASSERT_EQ(vec.back(), *(vec.end() - 1)); |
||||
ASSERT_TRUE(vec.begin() < vec.end()); |
||||
|
||||
// non-const iterator
|
||||
size_t index = 0; |
||||
for (const auto& item : vec) { |
||||
ASSERT_EQ(vec[index++], item); |
||||
} |
||||
|
||||
index = vec.size() - 1; |
||||
for (auto pos = vec.rbegin(); pos != vec.rend(); ++pos) { |
||||
ASSERT_EQ(vec[index--], *pos); |
||||
} |
||||
|
||||
// const iterator
|
||||
const auto& cvec = vec; |
||||
index = 0; |
||||
for (const auto& item : cvec) { |
||||
ASSERT_EQ(cvec[index++], item); |
||||
} |
||||
|
||||
index = vec.size() - 1; |
||||
for (auto pos = cvec.rbegin(); pos != cvec.rend(); ++pos) { |
||||
ASSERT_EQ(cvec[index--], *pos); |
||||
} |
||||
|
||||
// forward and backward
|
||||
auto pos = vec.begin(); |
||||
while (pos != vec.end()) { |
||||
auto old_val = *pos; |
||||
auto old = pos++; |
||||
// HACK: make sure -> works
|
||||
ASSERT_TRUE(!old->empty()); |
||||
ASSERT_EQ(old_val, *old); |
||||
ASSERT_TRUE(pos == vec.end() || old_val != *pos); |
||||
} |
||||
|
||||
pos = vec.begin(); |
||||
for (size_t i = 0; i < vec.size(); i += 2) { |
||||
// Cannot use ASSERT_EQ since that macro depends on iostream serialization
|
||||
ASSERT_TRUE(pos + 2 - 2 == pos); |
||||
pos += 2; |
||||
ASSERT_TRUE(pos >= vec.begin()); |
||||
ASSERT_TRUE(pos <= vec.end()); |
||||
|
||||
size_t diff = static_cast<size_t>(pos - vec.begin()); |
||||
ASSERT_EQ(i + 2, diff); |
||||
} |
||||
} |
||||
|
||||
vector<string> GetTestKeys(size_t size) { |
||||
vector<string> keys; |
||||
keys.resize(size); |
||||
|
||||
int index = 0; |
||||
for (auto& key : keys) { |
||||
key = "item-" + to_string(index++); |
||||
} |
||||
return keys; |
||||
} |
||||
|
||||
template<class TVector> |
||||
void BenchmarkVectorCreationAndInsertion( |
||||
string name, size_t ops, size_t item_size, |
||||
const std::vector<typename TVector::value_type>& items) { |
||||
auto env = Env::Default(); |
||||
|
||||
int index = 0; |
||||
auto start_time = env->NowNanos(); |
||||
auto ops_remaining = ops; |
||||
while(ops_remaining--) { |
||||
TVector v; |
||||
for (size_t i = 0; i < item_size; ++i) { |
||||
v.push_back(items[index++]); |
||||
} |
||||
} |
||||
auto elapsed = env->NowNanos() - start_time; |
||||
cout << "created " << ops << " " << name << " instances:\n\t" |
||||
<< "each was inserted with " << item_size << " elements\n\t" |
||||
<< "total time elapsed: " << elapsed << " (ns)" << endl; |
||||
} |
||||
|
||||
template <class TVector> |
||||
size_t BenchmarkSequenceAccess(string name, size_t ops, size_t elem_size) { |
||||
TVector v; |
||||
for (const auto& item : GetTestKeys(elem_size)) { |
||||
v.push_back(item); |
||||
} |
||||
auto env = Env::Default(); |
||||
|
||||
auto ops_remaining = ops; |
||||
auto start_time = env->NowNanos(); |
||||
size_t total = 0; |
||||
while (ops_remaining--) { |
||||
auto end = v.end(); |
||||
for (auto pos = v.begin(); pos != end; ++pos) { |
||||
total += pos->size(); |
||||
} |
||||
} |
||||
auto elapsed = env->NowNanos() - start_time; |
||||
cout << "performed " << ops << " sequence access against " << name << "\n\t" |
||||
<< "size: " << elem_size << "\n\t" |
||||
<< "total time elapsed: " << elapsed << " (ns)" << endl; |
||||
// HACK avoid compiler's optimization to ignore total
|
||||
return total; |
||||
} |
||||
|
||||
// This test case only reports the performance between std::vector<string>
|
||||
// and autovector<string>. We chose string for comparison because in most
|
||||
// o our use cases we used std::vector<string>.
|
||||
TEST(AutoVectorTest, PerfBench) { |
||||
// We run same operations for kOps times in order to get a more fair result.
|
||||
size_t kOps = 100000; |
||||
|
||||
// Creation and insertion test
|
||||
// Test the case when there is:
|
||||
// * no element inserted: internal array of std::vector may not really get
|
||||
// initialize.
|
||||
// * one element inserted: internal array of std::vector must have
|
||||
// initialized.
|
||||
// * kSize elements inserted. This shows the most time we'll spend if we
|
||||
// keep everything in stack.
|
||||
// * 2 * kSize elements inserted. The internal vector of
|
||||
// autovector must have been initialized.
|
||||
cout << "=====================================================" << endl; |
||||
cout << "Creation and Insertion Test (value type: std::string)" << endl; |
||||
cout << "=====================================================" << endl; |
||||
|
||||
// pre-generated unique keys
|
||||
auto string_keys = GetTestKeys(kOps * 2 * kSize); |
||||
for (auto insertions : { 0ul, 1ul, kSize / 2, kSize, 2 * kSize }) { |
||||
BenchmarkVectorCreationAndInsertion<vector<string>>( |
||||
"vector<string>", kOps, insertions, string_keys |
||||
); |
||||
BenchmarkVectorCreationAndInsertion<autovector<string, kSize>>( |
||||
"autovector<string>", kOps, insertions, string_keys |
||||
); |
||||
cout << "-----------------------------------" << endl; |
||||
} |
||||
|
||||
cout << "=====================================================" << endl; |
||||
cout << "Creation and Insertion Test (value type: uint64_t)" << endl; |
||||
cout << "=====================================================" << endl; |
||||
|
||||
// pre-generated unique keys
|
||||
vector<uint64_t> int_keys(kOps * 2 * kSize); |
||||
for (size_t i = 0; i < kOps * 2 * kSize; ++i) { |
||||
int_keys[i] = i; |
||||
} |
||||
for (auto insertions : { 0ul, 1ul, kSize / 2, kSize, 2 * kSize }) { |
||||
BenchmarkVectorCreationAndInsertion<vector<uint64_t>>( |
||||
"vector<uint64_t>", kOps, insertions, int_keys |
||||
); |
||||
BenchmarkVectorCreationAndInsertion<autovector<uint64_t, kSize>>( |
||||
"autovector<uint64_t>", kOps, insertions, int_keys |
||||
); |
||||
cout << "-----------------------------------" << endl; |
||||
} |
||||
|
||||
// Sequence Access Test
|
||||
cout << "=====================================================" << endl; |
||||
cout << "Sequence Access Test" << endl; |
||||
cout << "=====================================================" << endl; |
||||
for (auto elem_size : { kSize / 2, kSize, 2 * kSize }) { |
||||
BenchmarkSequenceAccess<vector<string>>( |
||||
"vector", kOps, elem_size |
||||
); |
||||
BenchmarkSequenceAccess<autovector<string, kSize>>( |
||||
"autovector", kOps, elem_size |
||||
); |
||||
cout << "-----------------------------------" << endl; |
||||
} |
||||
} |
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) { |
||||
return rocksdb::test::RunAllTests(); |
||||
} |
Binary file not shown.
@ -0,0 +1,874 @@ |
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "utilities/backupable_db.h" |
||||
#include "db/filename.h" |
||||
#include "util/coding.h" |
||||
#include "rocksdb/transaction_log.h" |
||||
|
||||
#define __STDC_FORMAT_MACROS |
||||
|
||||
#include <inttypes.h> |
||||
#include <algorithm> |
||||
#include <vector> |
||||
#include <map> |
||||
#include <string> |
||||
#include <limits> |
||||
|
||||
namespace rocksdb { |
||||
|
||||
// -------- BackupEngine class ---------
|
||||
class BackupEngine { |
||||
public: |
||||
BackupEngine(Env* db_env, const BackupableDBOptions& options); |
||||
~BackupEngine(); |
||||
Status CreateNewBackup(DB* db, bool flush_before_backup = false); |
||||
Status PurgeOldBackups(uint32_t num_backups_to_keep); |
||||
Status DeleteBackup(BackupID backup_id); |
||||
|
||||
void GetBackupInfo(std::vector<BackupInfo>* backup_info); |
||||
Status RestoreDBFromBackup(BackupID backup_id, const std::string &db_dir, |
||||
const std::string &wal_dir); |
||||
Status RestoreDBFromLatestBackup(const std::string &db_dir, |
||||
const std::string &wal_dir) { |
||||
return RestoreDBFromBackup(latest_backup_id_, db_dir, wal_dir); |
||||
} |
||||
|
||||
void DeleteBackupsNewerThan(uint64_t sequence_number); |
||||
|
||||
private: |
||||
class BackupMeta { |
||||
public: |
||||
BackupMeta(const std::string& meta_filename, |
||||
std::unordered_map<std::string, int>* file_refs, Env* env) |
||||
: timestamp_(0), size_(0), meta_filename_(meta_filename), |
||||
file_refs_(file_refs), env_(env) {} |
||||
|
||||
~BackupMeta() {} |
||||
|
||||
void RecordTimestamp() { |
||||
env_->GetCurrentTime(×tamp_); |
||||
} |
||||
int64_t GetTimestamp() const { |
||||
return timestamp_; |
||||
} |
||||
uint64_t GetSize() const { |
||||
return size_; |
||||
} |
||||
void SetSequenceNumber(uint64_t sequence_number) { |
||||
sequence_number_ = sequence_number; |
||||
} |
||||
uint64_t GetSequenceNumber() { |
||||
return sequence_number_; |
||||
} |
||||
|
||||
void AddFile(const std::string& filename, uint64_t size); |
||||
void Delete(); |
||||
|
||||
bool Empty() { |
||||
return files_.empty(); |
||||
} |
||||
|
||||
const std::vector<std::string>& GetFiles() { |
||||
return files_; |
||||
} |
||||
|
||||
Status LoadFromFile(const std::string& backup_dir); |
||||
Status StoreToFile(bool sync); |
||||
|
||||
private: |
||||
int64_t timestamp_; |
||||
// sequence number is only approximate, should not be used
|
||||
// by clients
|
||||
uint64_t sequence_number_; |
||||
uint64_t size_; |
||||
std::string const meta_filename_; |
||||
// files with relative paths (without "/" prefix!!)
|
||||
std::vector<std::string> files_; |
||||
std::unordered_map<std::string, int>* file_refs_; |
||||
Env* env_; |
||||
|
||||
static const size_t max_backup_meta_file_size_ = 10 * 1024 * 1024; // 10MB
|
||||
}; // BackupMeta
|
||||
|
||||
inline std::string GetAbsolutePath( |
||||
const std::string &relative_path = "") const { |
||||
assert(relative_path.size() == 0 || relative_path[0] != '/'); |
||||
return options_.backup_dir + "/" + relative_path; |
||||
} |
||||
inline std::string GetPrivateDirRel() const { |
||||
return "private"; |
||||
} |
||||
inline std::string GetPrivateFileRel(BackupID backup_id, |
||||
const std::string &file = "") const { |
||||
assert(file.size() == 0 || file[0] != '/'); |
||||
return GetPrivateDirRel() + "/" + std::to_string(backup_id) + "/" + file; |
||||
} |
||||
inline std::string GetSharedFileRel(const std::string& file = "") const { |
||||
assert(file.size() == 0 || file[0] != '/'); |
||||
return "shared/" + file; |
||||
} |
||||
inline std::string GetLatestBackupFile(bool tmp = false) const { |
||||
return GetAbsolutePath(std::string("LATEST_BACKUP") + (tmp ? ".tmp" : "")); |
||||
} |
||||
inline std::string GetBackupMetaDir() const { |
||||
return GetAbsolutePath("meta"); |
||||
} |
||||
inline std::string GetBackupMetaFile(BackupID backup_id) const { |
||||
return GetBackupMetaDir() + "/" + std::to_string(backup_id); |
||||
} |
||||
|
||||
Status GetLatestBackupFileContents(uint32_t* latest_backup); |
||||
Status PutLatestBackupFileContents(uint32_t latest_backup); |
||||
// if size_limit == 0, there is no size limit, copy everything
|
||||
Status CopyFile(const std::string& src, |
||||
const std::string& dst, |
||||
Env* src_env, |
||||
Env* dst_env, |
||||
bool sync, |
||||
uint64_t* size = nullptr, |
||||
uint64_t size_limit = 0); |
||||
// if size_limit == 0, there is no size limit, copy everything
|
||||
Status BackupFile(BackupID backup_id, |
||||
BackupMeta* backup, |
||||
bool shared, |
||||
const std::string& src_dir, |
||||
const std::string& src_fname, // starts with "/"
|
||||
uint64_t size_limit = 0); |
||||
// Will delete all the files we don't need anymore
|
||||
// If full_scan == true, it will do the full scan of files/ directory
|
||||
// and delete all the files that are not referenced from backuped_file_refs_
|
||||
void GarbageCollection(bool full_scan); |
||||
|
||||
// backup state data
|
||||
BackupID latest_backup_id_; |
||||
std::map<BackupID, BackupMeta> backups_; |
||||
std::unordered_map<std::string, int> backuped_file_refs_; |
||||
std::vector<BackupID> obsolete_backups_; |
||||
|
||||
// options data
|
||||
BackupableDBOptions options_; |
||||
Env* db_env_; |
||||
Env* backup_env_; |
||||
|
||||
static const size_t copy_file_buffer_size_ = 5 * 1024 * 1024LL; // 5MB
|
||||
}; |
||||
|
||||
BackupEngine::BackupEngine(Env* db_env, const BackupableDBOptions& options) |
||||
: options_(options), |
||||
db_env_(db_env), |
||||
backup_env_(options.backup_env != nullptr ? options.backup_env : db_env_) { |
||||
|
||||
// create all the dirs we need
|
||||
backup_env_->CreateDirIfMissing(GetAbsolutePath()); |
||||
backup_env_->CreateDirIfMissing(GetAbsolutePath(GetSharedFileRel())); |
||||
backup_env_->CreateDirIfMissing(GetAbsolutePath(GetPrivateDirRel())); |
||||
backup_env_->CreateDirIfMissing(GetBackupMetaDir()); |
||||
|
||||
std::vector<std::string> backup_meta_files; |
||||
backup_env_->GetChildren(GetBackupMetaDir(), &backup_meta_files); |
||||
// create backups_ structure
|
||||
for (auto& file : backup_meta_files) { |
||||
BackupID backup_id = 0; |
||||
sscanf(file.c_str(), "%u", &backup_id); |
||||
if (backup_id == 0 || file != std::to_string(backup_id)) { |
||||
// invalid file name, delete that
|
||||
backup_env_->DeleteFile(GetBackupMetaDir() + "/" + file); |
||||
continue; |
||||
} |
||||
assert(backups_.find(backup_id) == backups_.end()); |
||||
backups_.insert(std::make_pair( |
||||
backup_id, BackupMeta(GetBackupMetaFile(backup_id), |
||||
&backuped_file_refs_, backup_env_))); |
||||
} |
||||
|
||||
if (options_.destroy_old_data) { // Destory old data
|
||||
for (auto& backup : backups_) { |
||||
backup.second.Delete(); |
||||
obsolete_backups_.push_back(backup.first); |
||||
} |
||||
backups_.clear(); |
||||
// start from beginning
|
||||
latest_backup_id_ = 0; |
||||
// GarbageCollection() will do the actual deletion
|
||||
} else { // Load data from storage
|
||||
// load the backups if any
|
||||
for (auto& backup : backups_) { |
||||
Status s = backup.second.LoadFromFile(options_.backup_dir); |
||||
if (!s.ok()) { |
||||
Log(options_.info_log, "Backup %u corrupted - deleting -- %s", |
||||
backup.first, s.ToString().c_str()); |
||||
backup.second.Delete(); |
||||
obsolete_backups_.push_back(backup.first); |
||||
} |
||||
} |
||||
// delete obsolete backups from the structure
|
||||
for (auto ob : obsolete_backups_) { |
||||
backups_.erase(ob); |
||||
} |
||||
|
||||
Status s = GetLatestBackupFileContents(&latest_backup_id_); |
||||
// If latest backup file is corrupted or non-existent
|
||||
// set latest backup as the biggest backup we have
|
||||
// or 0 if we have no backups
|
||||
if (!s.ok() || |
||||
backups_.find(latest_backup_id_) == backups_.end()) { |
||||
auto itr = backups_.end(); |
||||
latest_backup_id_ = (itr == backups_.begin()) ? 0 : (--itr)->first; |
||||
} |
||||
} |
||||
|
||||
// delete any backups that claim to be later than latest
|
||||
for (auto itr = backups_.upper_bound(latest_backup_id_); |
||||
itr != backups_.end();) { |
||||
itr->second.Delete(); |
||||
obsolete_backups_.push_back(itr->first); |
||||
itr = backups_.erase(itr); |
||||
} |
||||
|
||||
PutLatestBackupFileContents(latest_backup_id_); // Ignore errors
|
||||
GarbageCollection(true); |
||||
Log(options_.info_log, |
||||
"Initialized BackupEngine, the latest backup is %u.", |
||||
latest_backup_id_); |
||||
} |
||||
|
||||
BackupEngine::~BackupEngine() { |
||||
LogFlush(options_.info_log); |
||||
} |
||||
|
||||
void BackupEngine::DeleteBackupsNewerThan(uint64_t sequence_number) { |
||||
for (auto backup : backups_) { |
||||
if (backup.second.GetSequenceNumber() > sequence_number) { |
||||
Log(options_.info_log, |
||||
"Deleting backup %u because sequence number (%" PRIu64 |
||||
") is newer than %" PRIu64 "", |
||||
backup.first, backup.second.GetSequenceNumber(), sequence_number); |
||||
backup.second.Delete(); |
||||
obsolete_backups_.push_back(backup.first); |
||||
} |
||||
} |
||||
for (auto ob : obsolete_backups_) { |
||||
backups_.erase(backups_.find(ob)); |
||||
} |
||||
auto itr = backups_.end(); |
||||
latest_backup_id_ = (itr == backups_.begin()) ? 0 : (--itr)->first; |
||||
PutLatestBackupFileContents(latest_backup_id_); // Ignore errors
|
||||
GarbageCollection(false); |
||||
} |
||||
|
||||
Status BackupEngine::CreateNewBackup(DB* db, bool flush_before_backup) { |
||||
Status s; |
||||
std::vector<std::string> live_files; |
||||
VectorLogPtr live_wal_files; |
||||
uint64_t manifest_file_size = 0; |
||||
uint64_t sequence_number = db->GetLatestSequenceNumber(); |
||||
|
||||
s = db->DisableFileDeletions(); |
||||
if (s.ok()) { |
||||
// this will return live_files prefixed with "/"
|
||||
s = db->GetLiveFiles(live_files, &manifest_file_size, flush_before_backup); |
||||
} |
||||
// if we didn't flush before backup, we need to also get WAL files
|
||||
if (s.ok() && !flush_before_backup) { |
||||
// returns file names prefixed with "/"
|
||||
s = db->GetSortedWalFiles(live_wal_files); |
||||
} |
||||
if (!s.ok()) { |
||||
db->EnableFileDeletions(); |
||||
return s; |
||||
} |
||||
|
||||
BackupID new_backup_id = latest_backup_id_ + 1; |
||||
assert(backups_.find(new_backup_id) == backups_.end()); |
||||
auto ret = backups_.insert(std::make_pair( |
||||
new_backup_id, BackupMeta(GetBackupMetaFile(new_backup_id), |
||||
&backuped_file_refs_, backup_env_))); |
||||
assert(ret.second == true); |
||||
auto& new_backup = ret.first->second; |
||||
new_backup.RecordTimestamp(); |
||||
new_backup.SetSequenceNumber(sequence_number); |
||||
|
||||
Log(options_.info_log, "Started the backup process -- creating backup %u", |
||||
new_backup_id); |
||||
|
||||
// create private dir
|
||||
s = backup_env_->CreateDir(GetAbsolutePath(GetPrivateFileRel(new_backup_id))); |
||||
|
||||
// copy live_files
|
||||
for (size_t i = 0; s.ok() && i < live_files.size(); ++i) { |
||||
uint64_t number; |
||||
FileType type; |
||||
bool ok = ParseFileName(live_files[i], &number, &type); |
||||
if (!ok) { |
||||
assert(false); |
||||
return Status::Corruption("Can't parse file name. This is very bad"); |
||||
} |
||||
// we should only get sst, manifest and current files here
|
||||
assert(type == kTableFile || |
||||
type == kDescriptorFile || |
||||
type == kCurrentFile); |
||||
|
||||
// rules:
|
||||
// * if it's kTableFile, than it's shared
|
||||
// * if it's kDescriptorFile, limit the size to manifest_file_size
|
||||
s = BackupFile(new_backup_id, |
||||
&new_backup, |
||||
type == kTableFile, /* shared */ |
||||
db->GetName(), /* src_dir */ |
||||
live_files[i], /* src_fname */ |
||||
(type == kDescriptorFile) ? manifest_file_size : 0); |
||||
} |
||||
|
||||
// copy WAL files
|
||||
for (size_t i = 0; s.ok() && i < live_wal_files.size(); ++i) { |
||||
if (live_wal_files[i]->Type() == kAliveLogFile) { |
||||
// we only care about live log files
|
||||
// copy the file into backup_dir/files/<new backup>/
|
||||
s = BackupFile(new_backup_id, |
||||
&new_backup, |
||||
false, /* not shared */ |
||||
db->GetOptions().wal_dir, |
||||
live_wal_files[i]->PathName()); |
||||
} |
||||
} |
||||
|
||||
// we copied all the files, enable file deletions
|
||||
db->EnableFileDeletions(); |
||||
|
||||
if (s.ok()) { |
||||
// persist the backup metadata on the disk
|
||||
s = new_backup.StoreToFile(options_.sync); |
||||
} |
||||
if (s.ok()) { |
||||
// install the newly created backup meta! (atomic)
|
||||
s = PutLatestBackupFileContents(new_backup_id); |
||||
} |
||||
if (!s.ok()) { |
||||
// clean all the files we might have created
|
||||
Log(options_.info_log, "Backup failed -- %s", s.ToString().c_str()); |
||||
backups_.erase(new_backup_id); |
||||
GarbageCollection(true); |
||||
return s; |
||||
} |
||||
|
||||
// here we know that we succeeded and installed the new backup
|
||||
// in the LATEST_BACKUP file
|
||||
latest_backup_id_ = new_backup_id; |
||||
Log(options_.info_log, "Backup DONE. All is good"); |
||||
return s; |
||||
} |
||||
|
||||
Status BackupEngine::PurgeOldBackups(uint32_t num_backups_to_keep) { |
||||
Log(options_.info_log, "Purging old backups, keeping %u", |
||||
num_backups_to_keep); |
||||
while (num_backups_to_keep < backups_.size()) { |
||||
Log(options_.info_log, "Deleting backup %u", backups_.begin()->first); |
||||
backups_.begin()->second.Delete(); |
||||
obsolete_backups_.push_back(backups_.begin()->first); |
||||
backups_.erase(backups_.begin()); |
||||
} |
||||
GarbageCollection(false); |
||||
return Status::OK(); |
||||
} |
||||
|
||||
Status BackupEngine::DeleteBackup(BackupID backup_id) { |
||||
Log(options_.info_log, "Deleting backup %u", backup_id); |
||||
auto backup = backups_.find(backup_id); |
||||
if (backup == backups_.end()) { |
||||
return Status::NotFound("Backup not found"); |
||||
} |
||||
backup->second.Delete(); |
||||
obsolete_backups_.push_back(backup_id); |
||||
backups_.erase(backup); |
||||
GarbageCollection(false); |
||||
return Status::OK(); |
||||
} |
||||
|
||||
void BackupEngine::GetBackupInfo(std::vector<BackupInfo>* backup_info) { |
||||
backup_info->reserve(backups_.size()); |
||||
for (auto& backup : backups_) { |
||||
if (!backup.second.Empty()) { |
||||
backup_info->push_back(BackupInfo( |
||||
backup.first, backup.second.GetTimestamp(), backup.second.GetSize())); |
||||
} |
||||
} |
||||
} |
||||
|
||||
Status BackupEngine::RestoreDBFromBackup(BackupID backup_id, |
||||
const std::string &db_dir, |
||||
const std::string &wal_dir) { |
||||
auto backup_itr = backups_.find(backup_id); |
||||
if (backup_itr == backups_.end()) { |
||||
return Status::NotFound("Backup not found"); |
||||
} |
||||
auto& backup = backup_itr->second; |
||||
if (backup.Empty()) { |
||||
return Status::NotFound("Backup not found"); |
||||
} |
||||
|
||||
Log(options_.info_log, "Restoring backup id %u\n", backup_id); |
||||
|
||||
// just in case. Ignore errors
|
||||
db_env_->CreateDirIfMissing(db_dir); |
||||
db_env_->CreateDirIfMissing(wal_dir); |
||||
|
||||
// delete log files that might have been already in wal_dir.
|
||||
// This is important since they might get replayed to the restored DB,
|
||||
// which will then differ from the backuped DB
|
||||
std::vector<std::string> delete_children; |
||||
db_env_->GetChildren(wal_dir, &delete_children); // ignore errors
|
||||
for (auto f : delete_children) { |
||||
db_env_->DeleteFile(wal_dir + "/" + f); // ignore errors
|
||||
} |
||||
// Also delete all the db_dir children. This is not so important
|
||||
// because obsolete files will be deleted by DBImpl::PurgeObsoleteFiles()
|
||||
delete_children.clear(); |
||||
db_env_->GetChildren(db_dir, &delete_children); // ignore errors
|
||||
for (auto f : delete_children) { |
||||
db_env_->DeleteFile(db_dir + "/" + f); // ignore errors
|
||||
} |
||||
|
||||
Status s; |
||||
for (auto& file : backup.GetFiles()) { |
||||
std::string dst; |
||||
// 1. extract the filename
|
||||
size_t slash = file.find_last_of('/'); |
||||
// file will either be shared/<file> or private/<number>/<file>
|
||||
assert(slash != std::string::npos); |
||||
dst = file.substr(slash + 1); |
||||
|
||||
// 2. find the filetype
|
||||
uint64_t number; |
||||
FileType type; |
||||
bool ok = ParseFileName(dst, &number, &type); |
||||
if (!ok) { |
||||
return Status::Corruption("Backup corrupted"); |
||||
} |
||||
// 3. Construct the final path
|
||||
// kLogFile lives in wal_dir and all the rest live in db_dir
|
||||
dst = ((type == kLogFile) ? wal_dir : db_dir) + |
||||
"/" + dst; |
||||
|
||||
Log(options_.info_log, "Restoring %s to %s\n", file.c_str(), dst.c_str()); |
||||
s = CopyFile(GetAbsolutePath(file), dst, backup_env_, db_env_, false); |
||||
if (!s.ok()) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
Log(options_.info_log, "Restoring done -- %s\n", s.ToString().c_str()); |
||||
return s; |
||||
} |
||||
|
||||
// latest backup id is an ASCII representation of latest backup id
|
||||
Status BackupEngine::GetLatestBackupFileContents(uint32_t* latest_backup) { |
||||
Status s; |
||||
unique_ptr<SequentialFile> file; |
||||
s = backup_env_->NewSequentialFile(GetLatestBackupFile(), |
||||
&file, |
||||
EnvOptions()); |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
|
||||
char buf[11]; |
||||
Slice data; |
||||
s = file->Read(10, &data, buf); |
||||
if (!s.ok() || data.size() == 0) { |
||||
return s.ok() ? Status::Corruption("Latest backup file corrupted") : s; |
||||
} |
||||
buf[data.size()] = 0; |
||||
|
||||
*latest_backup = 0; |
||||
sscanf(data.data(), "%u", latest_backup); |
||||
if (backup_env_->FileExists(GetBackupMetaFile(*latest_backup)) == false) { |
||||
s = Status::Corruption("Latest backup file corrupted"); |
||||
} |
||||
return Status::OK(); |
||||
} |
||||
|
||||
// this operation HAS to be atomic
|
||||
// writing 4 bytes to the file is atomic alright, but we should *never*
|
||||
// do something like 1. delete file, 2. write new file
|
||||
// We write to a tmp file and then atomically rename
|
||||
Status BackupEngine::PutLatestBackupFileContents(uint32_t latest_backup) { |
||||
Status s; |
||||
unique_ptr<WritableFile> file; |
||||
EnvOptions env_options; |
||||
env_options.use_mmap_writes = false; |
||||
s = backup_env_->NewWritableFile(GetLatestBackupFile(true), |
||||
&file, |
||||
env_options); |
||||
if (!s.ok()) { |
||||
backup_env_->DeleteFile(GetLatestBackupFile(true)); |
||||
return s; |
||||
} |
||||
|
||||
char file_contents[10]; |
||||
int len = sprintf(file_contents, "%u\n", latest_backup); |
||||
s = file->Append(Slice(file_contents, len)); |
||||
if (s.ok() && options_.sync) { |
||||
file->Sync(); |
||||
} |
||||
if (s.ok()) { |
||||
s = file->Close(); |
||||
} |
||||
if (s.ok()) { |
||||
// atomically replace real file with new tmp
|
||||
s = backup_env_->RenameFile(GetLatestBackupFile(true), |
||||
GetLatestBackupFile(false)); |
||||
} |
||||
return s; |
||||
} |
||||
|
||||
Status BackupEngine::CopyFile(const std::string& src, |
||||
const std::string& dst, |
||||
Env* src_env, |
||||
Env* dst_env, |
||||
bool sync, |
||||
uint64_t* size, |
||||
uint64_t size_limit) { |
||||
Status s; |
||||
unique_ptr<WritableFile> dst_file; |
||||
unique_ptr<SequentialFile> src_file; |
||||
EnvOptions env_options; |
||||
env_options.use_mmap_writes = false; |
||||
if (size != nullptr) { |
||||
*size = 0; |
||||
} |
||||
|
||||
// Check if size limit is set. if not, set it to very big number
|
||||
if (size_limit == 0) { |
||||
size_limit = std::numeric_limits<uint64_t>::max(); |
||||
} |
||||
|
||||
s = src_env->NewSequentialFile(src, &src_file, env_options); |
||||
if (s.ok()) { |
||||
s = dst_env->NewWritableFile(dst, &dst_file, env_options); |
||||
} |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
|
||||
unique_ptr<char[]> buf(new char[copy_file_buffer_size_]); |
||||
Slice data; |
||||
|
||||
do { |
||||
size_t buffer_to_read = (copy_file_buffer_size_ < size_limit) ? |
||||
copy_file_buffer_size_ : size_limit; |
||||
s = src_file->Read(buffer_to_read, &data, buf.get()); |
||||
size_limit -= data.size(); |
||||
if (size != nullptr) { |
||||
*size += data.size(); |
||||
} |
||||
if (s.ok()) { |
||||
s = dst_file->Append(data); |
||||
} |
||||
} while (s.ok() && data.size() > 0 && size_limit > 0); |
||||
|
||||
if (s.ok() && sync) { |
||||
s = dst_file->Sync(); |
||||
} |
||||
|
||||
return s; |
||||
} |
||||
|
||||
// src_fname will always start with "/"
|
||||
Status BackupEngine::BackupFile(BackupID backup_id, |
||||
BackupMeta* backup, |
||||
bool shared, |
||||
const std::string& src_dir, |
||||
const std::string& src_fname, |
||||
uint64_t size_limit) { |
||||
|
||||
assert(src_fname.size() > 0 && src_fname[0] == '/'); |
||||
std::string dst_relative = src_fname.substr(1); |
||||
if (shared) { |
||||
dst_relative = GetSharedFileRel(dst_relative); |
||||
} else { |
||||
dst_relative = GetPrivateFileRel(backup_id, dst_relative); |
||||
} |
||||
std::string dst_path = GetAbsolutePath(dst_relative); |
||||
Status s; |
||||
uint64_t size; |
||||
|
||||
// if it's shared, we also need to check if it exists -- if it does,
|
||||
// no need to copy it again
|
||||
if (shared && backup_env_->FileExists(dst_path)) { |
||||
backup_env_->GetFileSize(dst_path, &size); // Ignore error
|
||||
Log(options_.info_log, "%s already present", src_fname.c_str()); |
||||
} else { |
||||
Log(options_.info_log, "Copying %s", src_fname.c_str()); |
||||
s = CopyFile(src_dir + src_fname, |
||||
dst_path, |
||||
db_env_, |
||||
backup_env_, |
||||
options_.sync, |
||||
&size, |
||||
size_limit); |
||||
} |
||||
if (s.ok()) { |
||||
backup->AddFile(dst_relative, size); |
||||
} |
||||
return s; |
||||
} |
||||
|
||||
void BackupEngine::GarbageCollection(bool full_scan) { |
||||
Log(options_.info_log, "Starting garbage collection"); |
||||
std::vector<std::string> to_delete; |
||||
for (auto& itr : backuped_file_refs_) { |
||||
if (itr.second == 0) { |
||||
Status s = backup_env_->DeleteFile(GetAbsolutePath(itr.first)); |
||||
Log(options_.info_log, "Deleting %s -- %s", itr.first.c_str(), |
||||
s.ToString().c_str()); |
||||
to_delete.push_back(itr.first); |
||||
} |
||||
} |
||||
for (auto& td : to_delete) { |
||||
backuped_file_refs_.erase(td); |
||||
} |
||||
if (!full_scan) { |
||||
// take care of private dirs -- if full_scan == true, then full_scan will
|
||||
// take care of them
|
||||
for (auto backup_id : obsolete_backups_) { |
||||
std::string private_dir = GetPrivateFileRel(backup_id); |
||||
Status s = backup_env_->DeleteDir(GetAbsolutePath(private_dir)); |
||||
Log(options_.info_log, "Deleting private dir %s -- %s", |
||||
private_dir.c_str(), s.ToString().c_str()); |
||||
} |
||||
} |
||||
obsolete_backups_.clear(); |
||||
|
||||
if (full_scan) { |
||||
Log(options_.info_log, "Starting full scan garbage collection"); |
||||
// delete obsolete shared files
|
||||
std::vector<std::string> shared_children; |
||||
backup_env_->GetChildren(GetAbsolutePath(GetSharedFileRel()), |
||||
&shared_children); |
||||
for (auto& child : shared_children) { |
||||
std::string rel_fname = GetSharedFileRel(child); |
||||
// if it's not refcounted, delete it
|
||||
if (backuped_file_refs_.find(rel_fname) == backuped_file_refs_.end()) { |
||||
// this might be a directory, but DeleteFile will just fail in that
|
||||
// case, so we're good
|
||||
Status s = backup_env_->DeleteFile(GetAbsolutePath(rel_fname)); |
||||
if (s.ok()) { |
||||
Log(options_.info_log, "Deleted %s", rel_fname.c_str()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// delete obsolete private files
|
||||
std::vector<std::string> private_children; |
||||
backup_env_->GetChildren(GetAbsolutePath(GetPrivateDirRel()), |
||||
&private_children); |
||||
for (auto& child : private_children) { |
||||
BackupID backup_id = 0; |
||||
sscanf(child.c_str(), "%u", &backup_id); |
||||
if (backup_id == 0 || backups_.find(backup_id) != backups_.end()) { |
||||
// it's either not a number or it's still alive. continue
|
||||
continue; |
||||
} |
||||
// here we have to delete the dir and all its children
|
||||
std::string full_private_path = |
||||
GetAbsolutePath(GetPrivateFileRel(backup_id)); |
||||
std::vector<std::string> subchildren; |
||||
backup_env_->GetChildren(full_private_path, &subchildren); |
||||
for (auto& subchild : subchildren) { |
||||
Status s = backup_env_->DeleteFile(full_private_path + subchild); |
||||
if (s.ok()) { |
||||
Log(options_.info_log, "Deleted %s", |
||||
(full_private_path + subchild).c_str()); |
||||
} |
||||
} |
||||
// finally delete the private dir
|
||||
Status s = backup_env_->DeleteDir(full_private_path); |
||||
Log(options_.info_log, "Deleted dir %s -- %s", full_private_path.c_str(), |
||||
s.ToString().c_str()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// ------- BackupMeta class --------
|
||||
|
||||
void BackupEngine::BackupMeta::AddFile(const std::string& filename, |
||||
uint64_t size) { |
||||
size_ += size; |
||||
files_.push_back(filename); |
||||
auto itr = file_refs_->find(filename); |
||||
if (itr == file_refs_->end()) { |
||||
file_refs_->insert(std::make_pair(filename, 1)); |
||||
} else { |
||||
++itr->second; // increase refcount if already present
|
||||
} |
||||
} |
||||
|
||||
void BackupEngine::BackupMeta::Delete() { |
||||
for (auto& file : files_) { |
||||
auto itr = file_refs_->find(file); |
||||
assert(itr != file_refs_->end()); |
||||
--(itr->second); // decrease refcount
|
||||
} |
||||
files_.clear(); |
||||
// delete meta file
|
||||
env_->DeleteFile(meta_filename_); |
||||
timestamp_ = 0; |
||||
} |
||||
|
||||
// each backup meta file is of the format:
|
||||
// <timestamp>
|
||||
// <seq number>
|
||||
// <number of files>
|
||||
// <file1>
|
||||
// <file2>
|
||||
// ...
|
||||
// TODO: maybe add checksum?
|
||||
Status BackupEngine::BackupMeta::LoadFromFile(const std::string& backup_dir) { |
||||
assert(Empty()); |
||||
Status s; |
||||
unique_ptr<SequentialFile> backup_meta_file; |
||||
s = env_->NewSequentialFile(meta_filename_, &backup_meta_file, EnvOptions()); |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
|
||||
unique_ptr<char[]> buf(new char[max_backup_meta_file_size_ + 1]); |
||||
Slice data; |
||||
s = backup_meta_file->Read(max_backup_meta_file_size_, &data, buf.get()); |
||||
|
||||
if (!s.ok() || data.size() == max_backup_meta_file_size_) { |
||||
return s.ok() ? Status::IOError("File size too big") : s; |
||||
} |
||||
buf[data.size()] = 0; |
||||
|
||||
uint32_t num_files = 0; |
||||
int bytes_read = 0; |
||||
sscanf(data.data(), "%" PRId64 "%n", ×tamp_, &bytes_read); |
||||
data.remove_prefix(bytes_read + 1); // +1 for '\n'
|
||||
sscanf(data.data(), "%" PRIu64 "%n", &sequence_number_, &bytes_read); |
||||
data.remove_prefix(bytes_read + 1); // +1 for '\n'
|
||||
sscanf(data.data(), "%u%n", &num_files, &bytes_read); |
||||
data.remove_prefix(bytes_read + 1); // +1 for '\n'
|
||||
|
||||
std::vector<std::pair<std::string, uint64_t>> files; |
||||
|
||||
for (uint32_t i = 0; s.ok() && i < num_files; ++i) { |
||||
std::string filename = GetSliceUntil(&data, '\n').ToString(); |
||||
uint64_t size; |
||||
s = env_->GetFileSize(backup_dir + "/" + filename, &size); |
||||
files.push_back(std::make_pair(filename, size)); |
||||
} |
||||
|
||||
if (s.ok()) { |
||||
for (auto file : files) { |
||||
AddFile(file.first, file.second); |
||||
} |
||||
} |
||||
|
||||
return s; |
||||
} |
||||
|
||||
Status BackupEngine::BackupMeta::StoreToFile(bool sync) { |
||||
Status s; |
||||
unique_ptr<WritableFile> backup_meta_file; |
||||
EnvOptions env_options; |
||||
env_options.use_mmap_writes = false; |
||||
s = env_->NewWritableFile(meta_filename_ + ".tmp", &backup_meta_file, |
||||
env_options); |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
|
||||
unique_ptr<char[]> buf(new char[max_backup_meta_file_size_]); |
||||
int len = 0, buf_size = max_backup_meta_file_size_; |
||||
len += snprintf(buf.get(), buf_size, "%" PRId64 "\n", timestamp_); |
||||
len += snprintf(buf.get() + len, buf_size - len, "%" PRIu64 "\n", |
||||
sequence_number_); |
||||
len += snprintf(buf.get() + len, buf_size - len, "%zu\n", files_.size()); |
||||
for (size_t i = 0; i < files_.size(); ++i) { |
||||
len += snprintf(buf.get() + len, buf_size - len, "%s\n", files_[i].c_str()); |
||||
} |
||||
|
||||
s = backup_meta_file->Append(Slice(buf.get(), (size_t)len)); |
||||
if (s.ok() && sync) { |
||||
s = backup_meta_file->Sync(); |
||||
} |
||||
if (s.ok()) { |
||||
s = backup_meta_file->Close(); |
||||
} |
||||
if (s.ok()) { |
||||
s = env_->RenameFile(meta_filename_ + ".tmp", meta_filename_); |
||||
} |
||||
return s; |
||||
} |
||||
|
||||
// --- BackupableDB methods --------
|
||||
|
||||
BackupableDB::BackupableDB(DB* db, const BackupableDBOptions& options) |
||||
: StackableDB(db), backup_engine_(new BackupEngine(db->GetEnv(), options)) { |
||||
backup_engine_->DeleteBackupsNewerThan(GetLatestSequenceNumber()); |
||||
} |
||||
|
||||
BackupableDB::~BackupableDB() { |
||||
delete backup_engine_; |
||||
} |
||||
|
||||
Status BackupableDB::CreateNewBackup(bool flush_before_backup) { |
||||
return backup_engine_->CreateNewBackup(this, flush_before_backup); |
||||
} |
||||
|
||||
void BackupableDB::GetBackupInfo(std::vector<BackupInfo>* backup_info) { |
||||
backup_engine_->GetBackupInfo(backup_info); |
||||
} |
||||
|
||||
Status BackupableDB::PurgeOldBackups(uint32_t num_backups_to_keep) { |
||||
return backup_engine_->PurgeOldBackups(num_backups_to_keep); |
||||
} |
||||
|
||||
Status BackupableDB::DeleteBackup(BackupID backup_id) { |
||||
return backup_engine_->DeleteBackup(backup_id); |
||||
} |
||||
|
||||
// --- RestoreBackupableDB methods ------
|
||||
|
||||
RestoreBackupableDB::RestoreBackupableDB(Env* db_env, |
||||
const BackupableDBOptions& options) |
||||
: backup_engine_(new BackupEngine(db_env, options)) {} |
||||
|
||||
RestoreBackupableDB::~RestoreBackupableDB() { |
||||
delete backup_engine_; |
||||
} |
||||
|
||||
void |
||||
RestoreBackupableDB::GetBackupInfo(std::vector<BackupInfo>* backup_info) { |
||||
backup_engine_->GetBackupInfo(backup_info); |
||||
} |
||||
|
||||
Status RestoreBackupableDB::RestoreDBFromBackup(BackupID backup_id, |
||||
const std::string& db_dir, |
||||
const std::string& wal_dir) { |
||||
return backup_engine_->RestoreDBFromBackup(backup_id, db_dir, wal_dir); |
||||
} |
||||
|
||||
Status |
||||
RestoreBackupableDB::RestoreDBFromLatestBackup(const std::string& db_dir, |
||||
const std::string& wal_dir) { |
||||
return backup_engine_->RestoreDBFromLatestBackup(db_dir, wal_dir); |
||||
} |
||||
|
||||
Status RestoreBackupableDB::PurgeOldBackups(uint32_t num_backups_to_keep) { |
||||
return backup_engine_->PurgeOldBackups(num_backups_to_keep); |
||||
} |
||||
|
||||
Status RestoreBackupableDB::DeleteBackup(BackupID backup_id) { |
||||
return backup_engine_->DeleteBackup(backup_id); |
||||
} |
||||
|
||||
} // namespace rocksdb
|
@ -0,0 +1,668 @@ |
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "rocksdb/types.h" |
||||
#include "rocksdb/transaction_log.h" |
||||
#include "utilities/utility_db.h" |
||||
#include "utilities/backupable_db.h" |
||||
#include "util/testharness.h" |
||||
#include "util/random.h" |
||||
#include "util/testutil.h" |
||||
#include "util/auto_roll_logger.h" |
||||
|
||||
#include <string> |
||||
#include <algorithm> |
||||
|
||||
namespace rocksdb { |
||||
|
||||
namespace { |
||||
|
||||
using std::unique_ptr; |
||||
|
||||
class DummyDB : public StackableDB { |
||||
public: |
||||
/* implicit */ |
||||
DummyDB(const Options& options, const std::string& dbname) |
||||
: StackableDB(nullptr), options_(options), dbname_(dbname), |
||||
deletions_enabled_(true), sequence_number_(0) {} |
||||
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const { |
||||
return ++sequence_number_; |
||||
} |
||||
|
||||
virtual const std::string& GetName() const override { |
||||
return dbname_; |
||||
} |
||||
|
||||
virtual Env* GetEnv() const override { |
||||
return options_.env; |
||||
} |
||||
|
||||
virtual const Options& GetOptions() const override { |
||||
return options_; |
||||
} |
||||
|
||||
virtual Status EnableFileDeletions() override { |
||||
ASSERT_TRUE(!deletions_enabled_); |
||||
deletions_enabled_ = true; |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status DisableFileDeletions() override { |
||||
ASSERT_TRUE(deletions_enabled_); |
||||
deletions_enabled_ = false; |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status GetLiveFiles(std::vector<std::string>& vec, uint64_t* mfs, |
||||
bool flush_memtable = true) override { |
||||
ASSERT_TRUE(!deletions_enabled_); |
||||
vec = live_files_; |
||||
*mfs = 100; |
||||
return Status::OK(); |
||||
} |
||||
|
||||
class DummyLogFile : public LogFile { |
||||
public: |
||||
/* implicit */ |
||||
DummyLogFile(const std::string& path, bool alive = true) |
||||
: path_(path), alive_(alive) {} |
||||
|
||||
virtual std::string PathName() const override { |
||||
return path_; |
||||
} |
||||
|
||||
virtual uint64_t LogNumber() const { |
||||
// what business do you have calling this method?
|
||||
ASSERT_TRUE(false); |
||||
return 0; |
||||
} |
||||
|
||||
virtual WalFileType Type() const override { |
||||
return alive_ ? kAliveLogFile : kArchivedLogFile; |
||||
} |
||||
|
||||
virtual SequenceNumber StartSequence() const { |
||||
// backupabledb should not need this method
|
||||
ASSERT_TRUE(false); |
||||
return 0; |
||||
} |
||||
|
||||
virtual uint64_t SizeFileBytes() const { |
||||
// backupabledb should not need this method
|
||||
ASSERT_TRUE(false); |
||||
return 0; |
||||
} |
||||
|
||||
private: |
||||
std::string path_; |
||||
bool alive_; |
||||
}; // DummyLogFile
|
||||
|
||||
virtual Status GetSortedWalFiles(VectorLogPtr& files) override { |
||||
ASSERT_TRUE(!deletions_enabled_); |
||||
files.resize(wal_files_.size()); |
||||
for (size_t i = 0; i < files.size(); ++i) { |
||||
files[i].reset( |
||||
new DummyLogFile(wal_files_[i].first, wal_files_[i].second)); |
||||
} |
||||
return Status::OK(); |
||||
} |
||||
|
||||
std::vector<std::string> live_files_; |
||||
// pair<filename, alive?>
|
||||
std::vector<std::pair<std::string, bool>> wal_files_; |
||||
private: |
||||
Options options_; |
||||
std::string dbname_; |
||||
bool deletions_enabled_; |
||||
mutable SequenceNumber sequence_number_; |
||||
}; // DummyDB
|
||||
|
||||
class TestEnv : public EnvWrapper { |
||||
public: |
||||
explicit TestEnv(Env* t) : EnvWrapper(t) {} |
||||
|
||||
class DummySequentialFile : public SequentialFile { |
||||
public: |
||||
DummySequentialFile() : SequentialFile(), rnd_(5) {} |
||||
virtual Status Read(size_t n, Slice* result, char* scratch) { |
||||
size_t read_size = (n > size_left) ? size_left : n; |
||||
for (size_t i = 0; i < read_size; ++i) { |
||||
scratch[i] = rnd_.Next() & 255; |
||||
} |
||||
*result = Slice(scratch, read_size); |
||||
size_left -= read_size; |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status Skip(uint64_t n) { |
||||
size_left = (n > size_left) ? size_left - n : 0; |
||||
return Status::OK(); |
||||
} |
||||
private: |
||||
size_t size_left = 200; |
||||
Random rnd_; |
||||
}; |
||||
|
||||
Status NewSequentialFile(const std::string& f, |
||||
unique_ptr<SequentialFile>* r, |
||||
const EnvOptions& options) { |
||||
opened_files_.push_back(f); |
||||
if (dummy_sequential_file_) { |
||||
r->reset(new TestEnv::DummySequentialFile()); |
||||
return Status::OK(); |
||||
} else { |
||||
return EnvWrapper::NewSequentialFile(f, r, options); |
||||
} |
||||
} |
||||
|
||||
Status NewWritableFile(const std::string& f, unique_ptr<WritableFile>* r, |
||||
const EnvOptions& options) { |
||||
if (limit_written_files_ <= 0) { |
||||
return Status::IOError("Sorry, can't do this"); |
||||
} |
||||
limit_written_files_--; |
||||
return EnvWrapper::NewWritableFile(f, r, options); |
||||
} |
||||
|
||||
void AssertOpenedFiles(std::vector<std::string>& should_have_opened) { |
||||
sort(should_have_opened.begin(), should_have_opened.end()); |
||||
sort(opened_files_.begin(), opened_files_.end()); |
||||
ASSERT_TRUE(opened_files_ == should_have_opened); |
||||
} |
||||
|
||||
void ClearOpenedFiles() { |
||||
opened_files_.clear(); |
||||
} |
||||
|
||||
void SetLimitWrittenFiles(uint64_t limit) { |
||||
limit_written_files_ = limit; |
||||
} |
||||
|
||||
void SetDummySequentialFile(bool dummy_sequential_file) { |
||||
dummy_sequential_file_ = dummy_sequential_file; |
||||
} |
||||
|
||||
private: |
||||
bool dummy_sequential_file_ = false; |
||||
std::vector<std::string> opened_files_; |
||||
uint64_t limit_written_files_ = 1000000; |
||||
}; // TestEnv
|
||||
|
||||
class FileManager : public EnvWrapper { |
||||
public: |
||||
explicit FileManager(Env* t) : EnvWrapper(t), rnd_(5) {} |
||||
|
||||
Status DeleteRandomFileInDir(const std::string dir) { |
||||
std::vector<std::string> children; |
||||
GetChildren(dir, &children); |
||||
if (children.size() <= 2) { // . and ..
|
||||
return Status::NotFound(""); |
||||
} |
||||
while (true) { |
||||
int i = rnd_.Next() % children.size(); |
||||
if (children[i] != "." && children[i] != "..") { |
||||
return DeleteFile(dir + "/" + children[i]); |
||||
} |
||||
} |
||||
// should never get here
|
||||
assert(false); |
||||
return Status::NotFound(""); |
||||
} |
||||
|
||||
Status CorruptFile(const std::string& fname, uint64_t bytes_to_corrupt) { |
||||
uint64_t size; |
||||
Status s = GetFileSize(fname, &size); |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
unique_ptr<RandomRWFile> file; |
||||
EnvOptions env_options; |
||||
env_options.use_mmap_writes = false; |
||||
s = NewRandomRWFile(fname, &file, env_options); |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
|
||||
for (uint64_t i = 0; s.ok() && i < bytes_to_corrupt; ++i) { |
||||
std::string tmp; |
||||
// write one random byte to a random position
|
||||
s = file->Write(rnd_.Next() % size, test::RandomString(&rnd_, 1, &tmp)); |
||||
} |
||||
return s; |
||||
} |
||||
|
||||
Status WriteToFile(const std::string& fname, const std::string& data) { |
||||
unique_ptr<WritableFile> file; |
||||
EnvOptions env_options; |
||||
env_options.use_mmap_writes = false; |
||||
Status s = EnvWrapper::NewWritableFile(fname, &file, env_options); |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
return file->Append(Slice(data)); |
||||
} |
||||
private: |
||||
Random rnd_; |
||||
}; // FileManager
|
||||
|
||||
// utility functions
|
||||
static void FillDB(DB* db, int from, int to) { |
||||
for (int i = from; i < to; ++i) { |
||||
std::string key = "testkey" + std::to_string(i); |
||||
std::string value = "testvalue" + std::to_string(i); |
||||
|
||||
ASSERT_OK(db->Put(WriteOptions(), Slice(key), Slice(value))); |
||||
} |
||||
} |
||||
|
||||
static void AssertExists(DB* db, int from, int to) { |
||||
for (int i = from; i < to; ++i) { |
||||
std::string key = "testkey" + std::to_string(i); |
||||
std::string value; |
||||
Status s = db->Get(ReadOptions(), Slice(key), &value); |
||||
ASSERT_EQ(value, "testvalue" + std::to_string(i)); |
||||
} |
||||
} |
||||
|
||||
static void AssertEmpty(DB* db, int from, int to) { |
||||
for (int i = from; i < to; ++i) { |
||||
std::string key = "testkey" + std::to_string(i); |
||||
std::string value = "testvalue" + std::to_string(i); |
||||
|
||||
Status s = db->Get(ReadOptions(), Slice(key), &value); |
||||
ASSERT_TRUE(s.IsNotFound()); |
||||
} |
||||
} |
||||
|
||||
class BackupableDBTest { |
||||
public: |
||||
BackupableDBTest() { |
||||
// set up files
|
||||
dbname_ = test::TmpDir() + "/backupable_db"; |
||||
backupdir_ = test::TmpDir() + "/backupable_db_backup"; |
||||
|
||||
// set up envs
|
||||
env_ = Env::Default(); |
||||
test_db_env_.reset(new TestEnv(env_)); |
||||
test_backup_env_.reset(new TestEnv(env_)); |
||||
file_manager_.reset(new FileManager(env_)); |
||||
|
||||
// set up db options
|
||||
options_.create_if_missing = true; |
||||
options_.paranoid_checks = true; |
||||
options_.write_buffer_size = 1 << 17; // 128KB
|
||||
options_.env = test_db_env_.get(); |
||||
options_.wal_dir = dbname_; |
||||
// set up backup db options
|
||||
CreateLoggerFromOptions(dbname_, backupdir_, env_, |
||||
Options(), &logger_); |
||||
backupable_options_.reset(new BackupableDBOptions( |
||||
backupdir_, test_backup_env_.get(), logger_.get(), true)); |
||||
|
||||
// delete old files in db
|
||||
DestroyDB(dbname_, Options()); |
||||
} |
||||
|
||||
DB* OpenDB() { |
||||
DB* db; |
||||
ASSERT_OK(DB::Open(options_, dbname_, &db)); |
||||
return db; |
||||
} |
||||
|
||||
void OpenBackupableDB(bool destroy_old_data = false, bool dummy = false) { |
||||
// reset all the defaults
|
||||
test_backup_env_->SetLimitWrittenFiles(1000000); |
||||
test_db_env_->SetLimitWrittenFiles(1000000); |
||||
test_db_env_->SetDummySequentialFile(dummy); |
||||
|
||||
DB* db; |
||||
if (dummy) { |
||||
dummy_db_ = new DummyDB(options_, dbname_); |
||||
db = dummy_db_; |
||||
} else { |
||||
ASSERT_OK(DB::Open(options_, dbname_, &db)); |
||||
} |
||||
backupable_options_->destroy_old_data = destroy_old_data; |
||||
db_.reset(new BackupableDB(db, *backupable_options_)); |
||||
} |
||||
|
||||
void CloseBackupableDB() { |
||||
db_.reset(nullptr); |
||||
} |
||||
|
||||
void OpenRestoreDB() { |
||||
backupable_options_->destroy_old_data = false; |
||||
restore_db_.reset( |
||||
new RestoreBackupableDB(test_db_env_.get(), *backupable_options_)); |
||||
} |
||||
|
||||
void CloseRestoreDB() { |
||||
restore_db_.reset(nullptr); |
||||
} |
||||
|
||||
// restores backup backup_id and asserts the existence of
|
||||
// [start_exist, end_exist> and not-existence of
|
||||
// [end_exist, end>
|
||||
//
|
||||
// if backup_id == 0, it means restore from latest
|
||||
// if end == 0, don't check AssertEmpty
|
||||
void AssertBackupConsistency(BackupID backup_id, uint32_t start_exist, |
||||
uint32_t end_exist, uint32_t end = 0) { |
||||
bool opened_restore = false; |
||||
if (restore_db_.get() == nullptr) { |
||||
opened_restore = true; |
||||
OpenRestoreDB(); |
||||
} |
||||
if (backup_id > 0) { |
||||
ASSERT_OK(restore_db_->RestoreDBFromBackup(backup_id, dbname_, dbname_)); |
||||
} else { |
||||
ASSERT_OK(restore_db_->RestoreDBFromLatestBackup(dbname_, dbname_)); |
||||
} |
||||
DB* db = OpenDB(); |
||||
AssertExists(db, start_exist, end_exist); |
||||
if (end != 0) { |
||||
AssertEmpty(db, end_exist, end); |
||||
} |
||||
delete db; |
||||
if (opened_restore) { |
||||
CloseRestoreDB(); |
||||
} |
||||
} |
||||
|
||||
// files
|
||||
std::string dbname_; |
||||
std::string backupdir_; |
||||
|
||||
// envs
|
||||
Env* env_; |
||||
unique_ptr<TestEnv> test_db_env_; |
||||
unique_ptr<TestEnv> test_backup_env_; |
||||
unique_ptr<FileManager> file_manager_; |
||||
|
||||
// all the dbs!
|
||||
DummyDB* dummy_db_; // BackupableDB owns dummy_db_
|
||||
unique_ptr<BackupableDB> db_; |
||||
unique_ptr<RestoreBackupableDB> restore_db_; |
||||
|
||||
// options
|
||||
Options options_; |
||||
unique_ptr<BackupableDBOptions> backupable_options_; |
||||
std::shared_ptr<Logger> logger_; |
||||
}; // BackupableDBTest
|
||||
|
||||
void AppendPath(const std::string& path, std::vector<std::string>& v) { |
||||
for (auto& f : v) { |
||||
f = path + f; |
||||
} |
||||
} |
||||
|
||||
// this will make sure that backup does not copy the same file twice
|
||||
TEST(BackupableDBTest, NoDoubleCopy) { |
||||
OpenBackupableDB(true, true); |
||||
|
||||
// should write 5 DB files + LATEST_BACKUP + one meta file
|
||||
test_backup_env_->SetLimitWrittenFiles(7); |
||||
test_db_env_->ClearOpenedFiles(); |
||||
test_db_env_->SetLimitWrittenFiles(0); |
||||
dummy_db_->live_files_ = { "/00010.sst", "/00011.sst", |
||||
"/CURRENT", "/MANIFEST-01" }; |
||||
dummy_db_->wal_files_ = {{"/00011.log", true}, {"/00012.log", false}}; |
||||
ASSERT_OK(db_->CreateNewBackup(false)); |
||||
std::vector<std::string> should_have_openened = dummy_db_->live_files_; |
||||
should_have_openened.push_back("/00011.log"); |
||||
AppendPath(dbname_, should_have_openened); |
||||
test_db_env_->AssertOpenedFiles(should_have_openened); |
||||
|
||||
// should write 4 new DB files + LATEST_BACKUP + one meta file
|
||||
// should not write/copy 00010.sst, since it's already there!
|
||||
test_backup_env_->SetLimitWrittenFiles(6); |
||||
test_db_env_->ClearOpenedFiles(); |
||||
dummy_db_->live_files_ = { "/00010.sst", "/00015.sst", |
||||
"/CURRENT", "/MANIFEST-01" }; |
||||
dummy_db_->wal_files_ = {{"/00011.log", true}, {"/00012.log", false}}; |
||||
ASSERT_OK(db_->CreateNewBackup(false)); |
||||
// should not open 00010.sst - it's already there
|
||||
should_have_openened = { "/00015.sst", "/CURRENT", |
||||
"/MANIFEST-01", "/00011.log" }; |
||||
AppendPath(dbname_, should_have_openened); |
||||
test_db_env_->AssertOpenedFiles(should_have_openened); |
||||
|
||||
ASSERT_OK(db_->DeleteBackup(1)); |
||||
ASSERT_EQ(true, |
||||
test_backup_env_->FileExists(backupdir_ + "/shared/00010.sst")); |
||||
// 00011.sst was only in backup 1, should be deleted
|
||||
ASSERT_EQ(false, |
||||
test_backup_env_->FileExists(backupdir_ + "/shared/00011.sst")); |
||||
ASSERT_EQ(true, |
||||
test_backup_env_->FileExists(backupdir_ + "/shared/00015.sst")); |
||||
|
||||
// MANIFEST file size should be only 100
|
||||
uint64_t size; |
||||
test_backup_env_->GetFileSize(backupdir_ + "/private/2/MANIFEST-01", &size); |
||||
ASSERT_EQ(100UL, size); |
||||
test_backup_env_->GetFileSize(backupdir_ + "/shared/00015.sst", &size); |
||||
ASSERT_EQ(200UL, size); |
||||
|
||||
CloseBackupableDB(); |
||||
} |
||||
|
||||
// test various kind of corruptions that may happen:
|
||||
// 1. Not able to write a file for backup - that backup should fail,
|
||||
// everything else should work
|
||||
// 2. Corrupted/deleted LATEST_BACKUP - everything should work fine
|
||||
// 3. Corrupted backup meta file or missing backuped file - we should
|
||||
// not be able to open that backup, but all other backups should be
|
||||
// fine
|
||||
TEST(BackupableDBTest, CorruptionsTest) { |
||||
const int keys_iteration = 5000; |
||||
Random rnd(6); |
||||
Status s; |
||||
|
||||
OpenBackupableDB(true); |
||||
// create five backups
|
||||
for (int i = 0; i < 5; ++i) { |
||||
FillDB(db_.get(), keys_iteration * i, keys_iteration * (i + 1)); |
||||
ASSERT_OK(db_->CreateNewBackup(!!(rnd.Next() % 2))); |
||||
} |
||||
|
||||
// ---------- case 1. - fail a write -----------
|
||||
// try creating backup 6, but fail a write
|
||||
FillDB(db_.get(), keys_iteration * 5, keys_iteration * 6); |
||||
test_backup_env_->SetLimitWrittenFiles(2); |
||||
// should fail
|
||||
s = db_->CreateNewBackup(!!(rnd.Next() % 2)); |
||||
ASSERT_TRUE(!s.ok()); |
||||
test_backup_env_->SetLimitWrittenFiles(1000000); |
||||
// latest backup should have all the keys
|
||||
CloseBackupableDB(); |
||||
AssertBackupConsistency(0, 0, keys_iteration * 5, keys_iteration * 6); |
||||
|
||||
// ---------- case 2. - corrupt/delete latest backup -----------
|
||||
ASSERT_OK(file_manager_->CorruptFile(backupdir_ + "/LATEST_BACKUP", 2)); |
||||
AssertBackupConsistency(0, 0, keys_iteration * 5); |
||||
ASSERT_OK(file_manager_->DeleteFile(backupdir_ + "/LATEST_BACKUP")); |
||||
AssertBackupConsistency(0, 0, keys_iteration * 5); |
||||
// create backup 6, point LATEST_BACKUP to 5
|
||||
OpenBackupableDB(); |
||||
FillDB(db_.get(), keys_iteration * 5, keys_iteration * 6); |
||||
ASSERT_OK(db_->CreateNewBackup(false)); |
||||
CloseBackupableDB(); |
||||
ASSERT_OK(file_manager_->WriteToFile(backupdir_ + "/LATEST_BACKUP", "5")); |
||||
AssertBackupConsistency(0, 0, keys_iteration * 5, keys_iteration * 6); |
||||
// assert that all 6 data is gone!
|
||||
ASSERT_TRUE(file_manager_->FileExists(backupdir_ + "/meta/6") == false); |
||||
ASSERT_TRUE(file_manager_->FileExists(backupdir_ + "/private/6") == false); |
||||
|
||||
// --------- case 3. corrupted backup meta or missing backuped file ----
|
||||
ASSERT_OK(file_manager_->CorruptFile(backupdir_ + "/meta/5", 3)); |
||||
// since 5 meta is now corrupted, latest backup should be 4
|
||||
AssertBackupConsistency(0, 0, keys_iteration * 4, keys_iteration * 5); |
||||
OpenRestoreDB(); |
||||
s = restore_db_->RestoreDBFromBackup(5, dbname_, dbname_); |
||||
ASSERT_TRUE(!s.ok()); |
||||
CloseRestoreDB(); |
||||
ASSERT_OK(file_manager_->DeleteRandomFileInDir(backupdir_ + "/private/4")); |
||||
// 4 is corrupted, 3 is the latest backup now
|
||||
AssertBackupConsistency(0, 0, keys_iteration * 3, keys_iteration * 5); |
||||
OpenRestoreDB(); |
||||
s = restore_db_->RestoreDBFromBackup(4, dbname_, dbname_); |
||||
CloseRestoreDB(); |
||||
ASSERT_TRUE(!s.ok()); |
||||
|
||||
// new backup should be 4!
|
||||
OpenBackupableDB(); |
||||
FillDB(db_.get(), keys_iteration * 3, keys_iteration * 4); |
||||
ASSERT_OK(db_->CreateNewBackup(!!(rnd.Next() % 2))); |
||||
CloseBackupableDB(); |
||||
AssertBackupConsistency(4, 0, keys_iteration * 4, keys_iteration * 5); |
||||
} |
||||
|
||||
// open DB, write, close DB, backup, restore, repeat
|
||||
TEST(BackupableDBTest, OfflineIntegrationTest) { |
||||
// has to be a big number, so that it triggers the memtable flush
|
||||
const int keys_iteration = 5000; |
||||
const int max_key = keys_iteration * 4 + 10; |
||||
// first iter -- flush before backup
|
||||
// second iter -- don't flush before backup
|
||||
for (int iter = 0; iter < 2; ++iter) { |
||||
// delete old data
|
||||
DestroyDB(dbname_, Options()); |
||||
bool destroy_data = true; |
||||
|
||||
// every iteration --
|
||||
// 1. insert new data in the DB
|
||||
// 2. backup the DB
|
||||
// 3. destroy the db
|
||||
// 4. restore the db, check everything is still there
|
||||
for (int i = 0; i < 5; ++i) { |
||||
// in last iteration, put smaller amount of data,
|
||||
int fill_up_to = std::min(keys_iteration * (i + 1), max_key); |
||||
// ---- insert new data and back up ----
|
||||
OpenBackupableDB(destroy_data); |
||||
destroy_data = false; |
||||
FillDB(db_.get(), keys_iteration * i, fill_up_to); |
||||
ASSERT_OK(db_->CreateNewBackup(iter == 0)); |
||||
CloseBackupableDB(); |
||||
DestroyDB(dbname_, Options()); |
||||
|
||||
// ---- make sure it's empty ----
|
||||
DB* db = OpenDB(); |
||||
AssertEmpty(db, 0, fill_up_to); |
||||
delete db; |
||||
|
||||
// ---- restore the DB ----
|
||||
OpenRestoreDB(); |
||||
if (i >= 3) { // test purge old backups
|
||||
// when i == 4, purge to only 1 backup
|
||||
// when i == 3, purge to 2 backups
|
||||
ASSERT_OK(restore_db_->PurgeOldBackups(5 - i)); |
||||
} |
||||
// ---- make sure the data is there ---
|
||||
AssertBackupConsistency(0, 0, fill_up_to, max_key); |
||||
CloseRestoreDB(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// open DB, write, backup, write, backup, close, restore
|
||||
TEST(BackupableDBTest, OnlineIntegrationTest) { |
||||
// has to be a big number, so that it triggers the memtable flush
|
||||
const int keys_iteration = 5000; |
||||
const int max_key = keys_iteration * 4 + 10; |
||||
Random rnd(7); |
||||
// delete old data
|
||||
DestroyDB(dbname_, Options()); |
||||
|
||||
OpenBackupableDB(true); |
||||
// write some data, backup, repeat
|
||||
for (int i = 0; i < 5; ++i) { |
||||
if (i == 4) { |
||||
// delete backup number 2, online delete!
|
||||
OpenRestoreDB(); |
||||
ASSERT_OK(restore_db_->DeleteBackup(2)); |
||||
CloseRestoreDB(); |
||||
} |
||||
// in last iteration, put smaller amount of data,
|
||||
// so that backups can share sst files
|
||||
int fill_up_to = std::min(keys_iteration * (i + 1), max_key); |
||||
FillDB(db_.get(), keys_iteration * i, fill_up_to); |
||||
// we should get consistent results with flush_before_backup
|
||||
// set to both true and false
|
||||
ASSERT_OK(db_->CreateNewBackup(!!(rnd.Next() % 2))); |
||||
} |
||||
// close and destroy
|
||||
CloseBackupableDB(); |
||||
DestroyDB(dbname_, Options()); |
||||
|
||||
// ---- make sure it's empty ----
|
||||
DB* db = OpenDB(); |
||||
AssertEmpty(db, 0, max_key); |
||||
delete db; |
||||
|
||||
// ---- restore every backup and verify all the data is there ----
|
||||
OpenRestoreDB(); |
||||
for (int i = 1; i <= 5; ++i) { |
||||
if (i == 2) { |
||||
// we deleted backup 2
|
||||
Status s = restore_db_->RestoreDBFromBackup(2, dbname_, dbname_); |
||||
ASSERT_TRUE(!s.ok()); |
||||
} else { |
||||
int fill_up_to = std::min(keys_iteration * i, max_key); |
||||
AssertBackupConsistency(i, 0, fill_up_to, max_key); |
||||
} |
||||
} |
||||
|
||||
// delete some backups -- this should leave only backups 3 and 5 alive
|
||||
ASSERT_OK(restore_db_->DeleteBackup(4)); |
||||
ASSERT_OK(restore_db_->PurgeOldBackups(2)); |
||||
|
||||
std::vector<BackupInfo> backup_info; |
||||
restore_db_->GetBackupInfo(&backup_info); |
||||
ASSERT_EQ(2UL, backup_info.size()); |
||||
|
||||
// check backup 3
|
||||
AssertBackupConsistency(3, 0, 3 * keys_iteration, max_key); |
||||
// check backup 5
|
||||
AssertBackupConsistency(5, 0, max_key); |
||||
|
||||
CloseRestoreDB(); |
||||
} |
||||
|
||||
TEST(BackupableDBTest, DeleteNewerBackups) { |
||||
// create backups 1, 2, 3, 4, 5
|
||||
OpenBackupableDB(true); |
||||
for (int i = 0; i < 5; ++i) { |
||||
FillDB(db_.get(), 100 * i, 100 * (i + 1)); |
||||
ASSERT_OK(db_->CreateNewBackup(!!(i % 2))); |
||||
} |
||||
CloseBackupableDB(); |
||||
|
||||
// backup 3 is fine
|
||||
AssertBackupConsistency(3, 0, 300, 500); |
||||
// this should delete backups 4 and 5
|
||||
OpenBackupableDB(); |
||||
CloseBackupableDB(); |
||||
// backups 4 and 5 don't exist
|
||||
OpenRestoreDB(); |
||||
Status s = restore_db_->RestoreDBFromBackup(4, dbname_, dbname_); |
||||
ASSERT_TRUE(s.IsNotFound()); |
||||
s = restore_db_->RestoreDBFromBackup(5, dbname_, dbname_); |
||||
ASSERT_TRUE(s.IsNotFound()); |
||||
CloseRestoreDB(); |
||||
} |
||||
|
||||
} // anon namespace
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) { |
||||
return rocksdb::test::RunAllTests(); |
||||
} |
Binary file not shown.
Loading…
Reference in new issue