Summary: Decoupling code that deals with archived log files outside of DBImpl. That will make this code easier to reason about and test. It will also make the code easier to improve, because an improver doesn't have to understand DBImpl code in entirety. Test Plan: added test Reviewers: ljin, yhchiang, rven, sdong Reviewed By: sdong Subscribers: dhruba, leveldb Differential Revision: https://reviews.facebook.net/D27873main
parent
fd95745a59
commit
635905481d
@ -0,0 +1,445 @@ |
|||||||
|
// 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 "db/wal_manager.h" |
||||||
|
|
||||||
|
#ifndef __STDC_FORMAT_MACROS |
||||||
|
#define __STDC_FORMAT_MACROS |
||||||
|
#endif |
||||||
|
|
||||||
|
#include <inttypes.h> |
||||||
|
#include <algorithm> |
||||||
|
#include <vector> |
||||||
|
#include <memory> |
||||||
|
|
||||||
|
#include "db/filename.h" |
||||||
|
#include "db/transaction_log_impl.h" |
||||||
|
#include "db/log_reader.h" |
||||||
|
#include "db/log_writer.h" |
||||||
|
#include "db/write_batch_internal.h" |
||||||
|
#include "port/port.h" |
||||||
|
#include "rocksdb/env.h" |
||||||
|
#include "rocksdb/options.h" |
||||||
|
#include "rocksdb/write_batch.h" |
||||||
|
#include "util/coding.h" |
||||||
|
#include "util/logging.h" |
||||||
|
#include "util/mutexlock.h" |
||||||
|
#include "util/sync_point.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
#ifndef ROCKSDB_LITE |
||||||
|
|
||||||
|
Status WalManager::GetSortedWalFiles(VectorLogPtr& files) { |
||||||
|
// First get sorted files in db dir, then get sorted files from archived
|
||||||
|
// dir, to avoid a race condition where a log file is moved to archived
|
||||||
|
// dir in between.
|
||||||
|
Status s; |
||||||
|
// list wal files in main db dir.
|
||||||
|
VectorLogPtr logs; |
||||||
|
s = GetSortedWalsOfType(db_options_.wal_dir, logs, kAliveLogFile); |
||||||
|
if (!s.ok()) { |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
// Reproduce the race condition where a log file is moved
|
||||||
|
// to archived dir, between these two sync points, used in
|
||||||
|
// (DBTest,TransactionLogIteratorRace)
|
||||||
|
TEST_SYNC_POINT("WalManager::GetSortedWalFiles:1"); |
||||||
|
TEST_SYNC_POINT("WalManager::GetSortedWalFiles:2"); |
||||||
|
|
||||||
|
files.clear(); |
||||||
|
// list wal files in archive dir.
|
||||||
|
std::string archivedir = ArchivalDirectory(db_options_.wal_dir); |
||||||
|
if (env_->FileExists(archivedir)) { |
||||||
|
s = GetSortedWalsOfType(archivedir, files, kArchivedLogFile); |
||||||
|
if (!s.ok()) { |
||||||
|
return s; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
uint64_t latest_archived_log_number = 0; |
||||||
|
if (!files.empty()) { |
||||||
|
latest_archived_log_number = files.back()->LogNumber(); |
||||||
|
Log(db_options_.info_log, "Latest Archived log: %" PRIu64, |
||||||
|
latest_archived_log_number); |
||||||
|
} |
||||||
|
|
||||||
|
files.reserve(files.size() + logs.size()); |
||||||
|
for (auto& log : logs) { |
||||||
|
if (log->LogNumber() > latest_archived_log_number) { |
||||||
|
files.push_back(std::move(log)); |
||||||
|
} else { |
||||||
|
// When the race condition happens, we could see the
|
||||||
|
// same log in both db dir and archived dir. Simply
|
||||||
|
// ignore the one in db dir. Note that, if we read
|
||||||
|
// archived dir first, we would have missed the log file.
|
||||||
|
Log(db_options_.info_log, "%s already moved to archive", |
||||||
|
log->PathName().c_str()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
Status WalManager::GetUpdatesSince( |
||||||
|
SequenceNumber seq, std::unique_ptr<TransactionLogIterator>* iter, |
||||||
|
const TransactionLogIterator::ReadOptions& read_options, |
||||||
|
VersionSet* version_set) { |
||||||
|
|
||||||
|
// Get all sorted Wal Files.
|
||||||
|
// Do binary search and open files and find the seq number.
|
||||||
|
|
||||||
|
std::unique_ptr<VectorLogPtr> wal_files(new VectorLogPtr); |
||||||
|
Status s = GetSortedWalFiles(*wal_files); |
||||||
|
if (!s.ok()) { |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
s = RetainProbableWalFiles(*wal_files, seq); |
||||||
|
if (!s.ok()) { |
||||||
|
return s; |
||||||
|
} |
||||||
|
iter->reset(new TransactionLogIteratorImpl( |
||||||
|
db_options_.wal_dir, &db_options_, read_options, env_options_, seq, |
||||||
|
std::move(wal_files), version_set)); |
||||||
|
return (*iter)->status(); |
||||||
|
} |
||||||
|
|
||||||
|
// 1. Go through all archived files and
|
||||||
|
// a. if ttl is enabled, delete outdated files
|
||||||
|
// b. if archive size limit is enabled, delete empty files,
|
||||||
|
// compute file number and size.
|
||||||
|
// 2. If size limit is enabled:
|
||||||
|
// a. compute how many files should be deleted
|
||||||
|
// b. get sorted non-empty archived logs
|
||||||
|
// c. delete what should be deleted
|
||||||
|
void WalManager::PurgeObsoleteWALFiles() { |
||||||
|
bool const ttl_enabled = db_options_.WAL_ttl_seconds > 0; |
||||||
|
bool const size_limit_enabled = db_options_.WAL_size_limit_MB > 0; |
||||||
|
if (!ttl_enabled && !size_limit_enabled) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
int64_t current_time; |
||||||
|
Status s = env_->GetCurrentTime(¤t_time); |
||||||
|
if (!s.ok()) { |
||||||
|
Log(db_options_.info_log, "Can't get current time: %s", |
||||||
|
s.ToString().c_str()); |
||||||
|
assert(false); |
||||||
|
return; |
||||||
|
} |
||||||
|
uint64_t const now_seconds = static_cast<uint64_t>(current_time); |
||||||
|
uint64_t const time_to_check = (ttl_enabled && !size_limit_enabled) |
||||||
|
? db_options_.WAL_ttl_seconds / 2 |
||||||
|
: kDefaultIntervalToDeleteObsoleteWAL; |
||||||
|
|
||||||
|
if (purge_wal_files_last_run_ + time_to_check > now_seconds) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
purge_wal_files_last_run_ = now_seconds; |
||||||
|
|
||||||
|
std::string archival_dir = ArchivalDirectory(db_options_.wal_dir); |
||||||
|
std::vector<std::string> files; |
||||||
|
s = env_->GetChildren(archival_dir, &files); |
||||||
|
if (!s.ok()) { |
||||||
|
Log(db_options_.info_log, "Can't get archive files: %s", |
||||||
|
s.ToString().c_str()); |
||||||
|
assert(false); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
size_t log_files_num = 0; |
||||||
|
uint64_t log_file_size = 0; |
||||||
|
|
||||||
|
for (auto& f : files) { |
||||||
|
uint64_t number; |
||||||
|
FileType type; |
||||||
|
if (ParseFileName(f, &number, &type) && type == kLogFile) { |
||||||
|
std::string const file_path = archival_dir + "/" + f; |
||||||
|
if (ttl_enabled) { |
||||||
|
uint64_t file_m_time; |
||||||
|
Status const s = env_->GetFileModificationTime(file_path, &file_m_time); |
||||||
|
if (!s.ok()) { |
||||||
|
Log(db_options_.info_log, "Can't get file mod time: %s: %s", |
||||||
|
file_path.c_str(), s.ToString().c_str()); |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (now_seconds - file_m_time > db_options_.WAL_ttl_seconds) { |
||||||
|
Status const s = env_->DeleteFile(file_path); |
||||||
|
if (!s.ok()) { |
||||||
|
Log(db_options_.info_log, "Can't delete file: %s: %s", |
||||||
|
file_path.c_str(), s.ToString().c_str()); |
||||||
|
continue; |
||||||
|
} else { |
||||||
|
MutexLock l(&read_first_record_cache_mutex_); |
||||||
|
read_first_record_cache_.erase(number); |
||||||
|
} |
||||||
|
continue; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (size_limit_enabled) { |
||||||
|
uint64_t file_size; |
||||||
|
Status const s = env_->GetFileSize(file_path, &file_size); |
||||||
|
if (!s.ok()) { |
||||||
|
Log(db_options_.info_log, "Can't get file size: %s: %s", |
||||||
|
file_path.c_str(), s.ToString().c_str()); |
||||||
|
return; |
||||||
|
} else { |
||||||
|
if (file_size > 0) { |
||||||
|
log_file_size = std::max(log_file_size, file_size); |
||||||
|
++log_files_num; |
||||||
|
} else { |
||||||
|
Status s = env_->DeleteFile(file_path); |
||||||
|
if (!s.ok()) { |
||||||
|
Log(db_options_.info_log, "Can't delete file: %s: %s", |
||||||
|
file_path.c_str(), s.ToString().c_str()); |
||||||
|
continue; |
||||||
|
} else { |
||||||
|
MutexLock l(&read_first_record_cache_mutex_); |
||||||
|
read_first_record_cache_.erase(number); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (0 == log_files_num || !size_limit_enabled) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
size_t const files_keep_num = |
||||||
|
db_options_.WAL_size_limit_MB * 1024 * 1024 / log_file_size; |
||||||
|
if (log_files_num <= files_keep_num) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
size_t files_del_num = log_files_num - files_keep_num; |
||||||
|
VectorLogPtr archived_logs; |
||||||
|
GetSortedWalsOfType(archival_dir, archived_logs, kArchivedLogFile); |
||||||
|
|
||||||
|
if (files_del_num > archived_logs.size()) { |
||||||
|
Log(db_options_.info_log, |
||||||
|
"Trying to delete more archived log files than " |
||||||
|
"exist. Deleting all"); |
||||||
|
files_del_num = archived_logs.size(); |
||||||
|
} |
||||||
|
|
||||||
|
for (size_t i = 0; i < files_del_num; ++i) { |
||||||
|
std::string const file_path = archived_logs[i]->PathName(); |
||||||
|
Status const s = env_->DeleteFile(db_options_.wal_dir + "/" + file_path); |
||||||
|
if (!s.ok()) { |
||||||
|
Log(db_options_.info_log, "Can't delete file: %s: %s", file_path.c_str(), |
||||||
|
s.ToString().c_str()); |
||||||
|
continue; |
||||||
|
} else { |
||||||
|
MutexLock l(&read_first_record_cache_mutex_); |
||||||
|
read_first_record_cache_.erase(archived_logs[i]->LogNumber()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void WalManager::ArchiveWALFile(const std::string& fname, uint64_t number) { |
||||||
|
auto archived_log_name = ArchivedLogFileName(db_options_.wal_dir, number); |
||||||
|
// The sync point below is used in (DBTest,TransactionLogIteratorRace)
|
||||||
|
TEST_SYNC_POINT("WalManager::PurgeObsoleteFiles:1"); |
||||||
|
Status s = env_->RenameFile(fname, archived_log_name); |
||||||
|
// The sync point below is used in (DBTest,TransactionLogIteratorRace)
|
||||||
|
TEST_SYNC_POINT("WalManager::PurgeObsoleteFiles:2"); |
||||||
|
Log(db_options_.info_log, "Move log file %s to %s -- %s\n", fname.c_str(), |
||||||
|
archived_log_name.c_str(), s.ToString().c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
namespace { |
||||||
|
struct CompareLogByPointer { |
||||||
|
bool operator()(const std::unique_ptr<LogFile>& a, |
||||||
|
const std::unique_ptr<LogFile>& b) { |
||||||
|
LogFileImpl* a_impl = dynamic_cast<LogFileImpl*>(a.get()); |
||||||
|
LogFileImpl* b_impl = dynamic_cast<LogFileImpl*>(b.get()); |
||||||
|
return *a_impl < *b_impl; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
Status WalManager::GetSortedWalsOfType(const std::string& path, |
||||||
|
VectorLogPtr& log_files, |
||||||
|
WalFileType log_type) { |
||||||
|
std::vector<std::string> all_files; |
||||||
|
const Status status = env_->GetChildren(path, &all_files); |
||||||
|
if (!status.ok()) { |
||||||
|
return status; |
||||||
|
} |
||||||
|
log_files.reserve(all_files.size()); |
||||||
|
for (const auto& f : all_files) { |
||||||
|
uint64_t number; |
||||||
|
FileType type; |
||||||
|
if (ParseFileName(f, &number, &type) && type == kLogFile) { |
||||||
|
SequenceNumber sequence; |
||||||
|
Status s = ReadFirstRecord(log_type, number, &sequence); |
||||||
|
if (!s.ok()) { |
||||||
|
return s; |
||||||
|
} |
||||||
|
if (sequence == 0) { |
||||||
|
// empty file
|
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// Reproduce the race condition where a log file is moved
|
||||||
|
// to archived dir, between these two sync points, used in
|
||||||
|
// (DBTest,TransactionLogIteratorRace)
|
||||||
|
TEST_SYNC_POINT("WalManager::GetSortedWalsOfType:1"); |
||||||
|
TEST_SYNC_POINT("WalManager::GetSortedWalsOfType:2"); |
||||||
|
|
||||||
|
uint64_t size_bytes; |
||||||
|
s = env_->GetFileSize(LogFileName(path, number), &size_bytes); |
||||||
|
// re-try in case the alive log file has been moved to archive.
|
||||||
|
if (!s.ok() && log_type == kAliveLogFile && |
||||||
|
env_->FileExists(ArchivedLogFileName(path, number))) { |
||||||
|
s = env_->GetFileSize(ArchivedLogFileName(path, number), &size_bytes); |
||||||
|
} |
||||||
|
if (!s.ok()) { |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
log_files.push_back(std::move(std::unique_ptr<LogFile>( |
||||||
|
new LogFileImpl(number, log_type, sequence, size_bytes)))); |
||||||
|
} |
||||||
|
} |
||||||
|
CompareLogByPointer compare_log_files; |
||||||
|
std::sort(log_files.begin(), log_files.end(), compare_log_files); |
||||||
|
return status; |
||||||
|
} |
||||||
|
|
||||||
|
Status WalManager::RetainProbableWalFiles(VectorLogPtr& all_logs, |
||||||
|
const SequenceNumber target) { |
||||||
|
int64_t start = 0; // signed to avoid overflow when target is < first file.
|
||||||
|
int64_t end = static_cast<int64_t>(all_logs.size()) - 1; |
||||||
|
// Binary Search. avoid opening all files.
|
||||||
|
while (end >= start) { |
||||||
|
int64_t mid = start + (end - start) / 2; // Avoid overflow.
|
||||||
|
SequenceNumber current_seq_num = all_logs.at(mid)->StartSequence(); |
||||||
|
if (current_seq_num == target) { |
||||||
|
end = mid; |
||||||
|
break; |
||||||
|
} else if (current_seq_num < target) { |
||||||
|
start = mid + 1; |
||||||
|
} else { |
||||||
|
end = mid - 1; |
||||||
|
} |
||||||
|
} |
||||||
|
// end could be -ve.
|
||||||
|
size_t start_index = std::max(static_cast<int64_t>(0), end); |
||||||
|
// The last wal file is always included
|
||||||
|
all_logs.erase(all_logs.begin(), all_logs.begin() + start_index); |
||||||
|
return Status::OK(); |
||||||
|
} |
||||||
|
|
||||||
|
Status WalManager::ReadFirstRecord(const WalFileType type, |
||||||
|
const uint64_t number, |
||||||
|
SequenceNumber* sequence) { |
||||||
|
if (type != kAliveLogFile && type != kArchivedLogFile) { |
||||||
|
return Status::NotSupported("File Type Not Known " + std::to_string(type)); |
||||||
|
} |
||||||
|
{ |
||||||
|
MutexLock l(&read_first_record_cache_mutex_); |
||||||
|
auto itr = read_first_record_cache_.find(number); |
||||||
|
if (itr != read_first_record_cache_.end()) { |
||||||
|
*sequence = itr->second; |
||||||
|
return Status::OK(); |
||||||
|
} |
||||||
|
} |
||||||
|
Status s; |
||||||
|
if (type == kAliveLogFile) { |
||||||
|
std::string fname = LogFileName(db_options_.wal_dir, number); |
||||||
|
s = ReadFirstLine(fname, sequence); |
||||||
|
if (env_->FileExists(fname) && !s.ok()) { |
||||||
|
// return any error that is not caused by non-existing file
|
||||||
|
return s; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (type == kArchivedLogFile || !s.ok()) { |
||||||
|
// check if the file got moved to archive.
|
||||||
|
std::string archived_file = |
||||||
|
ArchivedLogFileName(db_options_.wal_dir, number); |
||||||
|
s = ReadFirstLine(archived_file, sequence); |
||||||
|
} |
||||||
|
|
||||||
|
if (s.ok() && *sequence != 0) { |
||||||
|
MutexLock l(&read_first_record_cache_mutex_); |
||||||
|
read_first_record_cache_.insert({number, *sequence}); |
||||||
|
} |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
// the function returns status.ok() and sequence == 0 if the file exists, but is
|
||||||
|
// empty
|
||||||
|
Status WalManager::ReadFirstLine(const std::string& fname, |
||||||
|
SequenceNumber* sequence) { |
||||||
|
struct LogReporter : public log::Reader::Reporter { |
||||||
|
Env* env; |
||||||
|
Logger* info_log; |
||||||
|
const char* fname; |
||||||
|
|
||||||
|
Status* status; |
||||||
|
bool ignore_error; // true if db_options_.paranoid_checks==false
|
||||||
|
virtual void Corruption(size_t bytes, const Status& s) { |
||||||
|
Log(info_log, "%s%s: dropping %d bytes; %s", |
||||||
|
(this->ignore_error ? "(ignoring error) " : ""), fname, |
||||||
|
static_cast<int>(bytes), s.ToString().c_str()); |
||||||
|
if (this->status->ok()) { |
||||||
|
// only keep the first error
|
||||||
|
*this->status = s; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
std::unique_ptr<SequentialFile> file; |
||||||
|
Status status = env_->NewSequentialFile(fname, &file, env_options_); |
||||||
|
|
||||||
|
if (!status.ok()) { |
||||||
|
return status; |
||||||
|
} |
||||||
|
|
||||||
|
LogReporter reporter; |
||||||
|
reporter.env = env_; |
||||||
|
reporter.info_log = db_options_.info_log.get(); |
||||||
|
reporter.fname = fname.c_str(); |
||||||
|
reporter.status = &status; |
||||||
|
reporter.ignore_error = !db_options_.paranoid_checks; |
||||||
|
log::Reader reader(std::move(file), &reporter, true /*checksum*/, |
||||||
|
0 /*initial_offset*/); |
||||||
|
std::string scratch; |
||||||
|
Slice record; |
||||||
|
|
||||||
|
if (reader.ReadRecord(&record, &scratch) && |
||||||
|
(status.ok() || !db_options_.paranoid_checks)) { |
||||||
|
if (record.size() < 12) { |
||||||
|
reporter.Corruption(record.size(), |
||||||
|
Status::Corruption("log record too small")); |
||||||
|
// TODO read record's till the first no corrupt entry?
|
||||||
|
} else { |
||||||
|
WriteBatch batch; |
||||||
|
WriteBatchInternal::SetContents(&batch, record); |
||||||
|
*sequence = WriteBatchInternal::Sequence(&batch); |
||||||
|
return Status::OK(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ReadRecord returns false on EOF, which means that the log file is empty. we
|
||||||
|
// return status.ok() in that case and set sequence number to 0
|
||||||
|
*sequence = 0; |
||||||
|
return status; |
||||||
|
} |
||||||
|
|
||||||
|
#endif // ROCKSDB_LITE
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,95 @@ |
|||||||
|
// 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 <atomic> |
||||||
|
#include <deque> |
||||||
|
#include <limits> |
||||||
|
#include <set> |
||||||
|
#include <utility> |
||||||
|
#include <vector> |
||||||
|
#include <string> |
||||||
|
#include <memory> |
||||||
|
|
||||||
|
#include "port/port.h" |
||||||
|
|
||||||
|
#include "rocksdb/env.h" |
||||||
|
#include "rocksdb/options.h" |
||||||
|
#include "rocksdb/types.h" |
||||||
|
#include "rocksdb/transaction_log.h" |
||||||
|
#include "rocksdb/status.h" |
||||||
|
|
||||||
|
#include "db/version_set.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
#ifndef ROCKSDB_LITE |
||||||
|
class WalManager { |
||||||
|
public: |
||||||
|
WalManager(const DBOptions& db_options, const EnvOptions& env_options) |
||||||
|
: db_options_(db_options), |
||||||
|
env_options_(env_options), |
||||||
|
env_(db_options.env), |
||||||
|
purge_wal_files_last_run_(0) {} |
||||||
|
|
||||||
|
virtual Status GetSortedWalFiles(VectorLogPtr& files); |
||||||
|
|
||||||
|
virtual Status GetUpdatesSince( |
||||||
|
SequenceNumber seq_number, std::unique_ptr<TransactionLogIterator>* iter, |
||||||
|
const TransactionLogIterator::ReadOptions& read_options, |
||||||
|
VersionSet* version_set); |
||||||
|
|
||||||
|
void PurgeObsoleteWALFiles(); |
||||||
|
|
||||||
|
void ArchiveWALFile(const std::string& fname, uint64_t number); |
||||||
|
|
||||||
|
Status TEST_ReadFirstRecord(const WalFileType type, const uint64_t number, |
||||||
|
SequenceNumber* sequence) { |
||||||
|
return ReadFirstRecord(type, number, sequence); |
||||||
|
} |
||||||
|
|
||||||
|
Status TEST_ReadFirstLine(const std::string& fname, |
||||||
|
SequenceNumber* sequence) { |
||||||
|
return ReadFirstLine(fname, sequence); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
Status GetSortedWalsOfType(const std::string& path, VectorLogPtr& log_files, |
||||||
|
WalFileType type); |
||||||
|
// Requires: all_logs should be sorted with earliest log file first
|
||||||
|
// Retains all log files in all_logs which contain updates with seq no.
|
||||||
|
// Greater Than or Equal to the requested SequenceNumber.
|
||||||
|
Status RetainProbableWalFiles(VectorLogPtr& all_logs, |
||||||
|
const SequenceNumber target); |
||||||
|
|
||||||
|
Status ReadFirstRecord(const WalFileType type, const uint64_t number, |
||||||
|
SequenceNumber* sequence); |
||||||
|
|
||||||
|
Status ReadFirstLine(const std::string& fname, SequenceNumber* sequence); |
||||||
|
|
||||||
|
// ------- state from DBImpl ------
|
||||||
|
const DBOptions& db_options_; |
||||||
|
const EnvOptions& env_options_; |
||||||
|
Env* env_; |
||||||
|
|
||||||
|
// ------- WalManager state -------
|
||||||
|
// cache for ReadFirstRecord() calls
|
||||||
|
std::unordered_map<uint64_t, SequenceNumber> read_first_record_cache_; |
||||||
|
port::Mutex read_first_record_cache_mutex_; |
||||||
|
|
||||||
|
// last time when PurgeObsoleteWALFiles ran.
|
||||||
|
uint64_t purge_wal_files_last_run_; |
||||||
|
|
||||||
|
// obsolete files will be deleted every this seconds if ttl deletion is
|
||||||
|
// enabled and archive size_limit is disabled.
|
||||||
|
static const uint64_t kDefaultIntervalToDeleteObsoleteWAL = 600; |
||||||
|
}; |
||||||
|
|
||||||
|
#endif // ROCKSDB_LITE
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,279 @@ |
|||||||
|
// 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 <map> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "rocksdb/cache.h" |
||||||
|
#include "rocksdb/write_batch.h" |
||||||
|
|
||||||
|
#include "db/wal_manager.h" |
||||||
|
#include "db/log_writer.h" |
||||||
|
#include "db/column_family.h" |
||||||
|
#include "db/version_set.h" |
||||||
|
#include "util/testharness.h" |
||||||
|
#include "util/testutil.h" |
||||||
|
#include "table/mock_table.h" |
||||||
|
#include "db/db_impl.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
// TODO(icanadi) mock out VersionSet
|
||||||
|
// TODO(icanadi) move other WalManager-specific tests from db_test here
|
||||||
|
class WalManagerTest { |
||||||
|
public: |
||||||
|
WalManagerTest() |
||||||
|
: env_(Env::Default()), |
||||||
|
dbname_(test::TmpDir() + "/wal_manager_test"), |
||||||
|
table_cache_(NewLRUCache(50000, 16, 8)), |
||||||
|
current_log_number_(0) { |
||||||
|
DestroyDB(dbname_, Options()); |
||||||
|
} |
||||||
|
|
||||||
|
void Init() { |
||||||
|
ASSERT_OK(env_->CreateDirIfMissing(dbname_)); |
||||||
|
ASSERT_OK(env_->CreateDirIfMissing(ArchivalDirectory(dbname_))); |
||||||
|
db_options_.db_paths.emplace_back(dbname_, |
||||||
|
std::numeric_limits<uint64_t>::max()); |
||||||
|
db_options_.wal_dir = dbname_; |
||||||
|
|
||||||
|
versions_.reset(new VersionSet(dbname_, &db_options_, env_options_, |
||||||
|
table_cache_.get(), &write_controller_)); |
||||||
|
|
||||||
|
wal_manager_.reset(new WalManager(db_options_, env_options_)); |
||||||
|
} |
||||||
|
|
||||||
|
void Reopen() { |
||||||
|
wal_manager_.reset(new WalManager(db_options_, env_options_)); |
||||||
|
} |
||||||
|
|
||||||
|
// NOT thread safe
|
||||||
|
void Put(const std::string& key, const std::string& value) { |
||||||
|
assert(current_log_writer_.get() != nullptr); |
||||||
|
uint64_t seq = versions_->LastSequence() + 1; |
||||||
|
WriteBatch batch; |
||||||
|
batch.Put(key, value); |
||||||
|
WriteBatchInternal::SetSequence(&batch, seq); |
||||||
|
current_log_writer_->AddRecord(WriteBatchInternal::Contents(&batch)); |
||||||
|
versions_->SetLastSequence(seq); |
||||||
|
} |
||||||
|
|
||||||
|
// NOT thread safe
|
||||||
|
void RollTheLog(bool archived) { |
||||||
|
current_log_number_++; |
||||||
|
std::string fname = ArchivedLogFileName(dbname_, current_log_number_); |
||||||
|
unique_ptr<WritableFile> file; |
||||||
|
ASSERT_OK(env_->NewWritableFile(fname, &file, env_options_)); |
||||||
|
current_log_writer_.reset(new log::Writer(std::move(file))); |
||||||
|
} |
||||||
|
|
||||||
|
void CreateArchiveLogs(int num_logs, int entries_per_log) { |
||||||
|
for (int i = 1; i <= num_logs; ++i) { |
||||||
|
RollTheLog(true); |
||||||
|
for (int k = 0; k < entries_per_log; ++k) { |
||||||
|
Put(std::to_string(k), std::string(1024, 'a')); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
std::unique_ptr<TransactionLogIterator> OpenTransactionLogIter( |
||||||
|
const SequenceNumber seq) { |
||||||
|
unique_ptr<TransactionLogIterator> iter; |
||||||
|
Status status = wal_manager_->GetUpdatesSince( |
||||||
|
seq, &iter, TransactionLogIterator::ReadOptions(), versions_.get()); |
||||||
|
ASSERT_OK(status); |
||||||
|
return std::move(iter); |
||||||
|
} |
||||||
|
|
||||||
|
Env* env_; |
||||||
|
std::string dbname_; |
||||||
|
WriteController write_controller_; |
||||||
|
EnvOptions env_options_; |
||||||
|
std::shared_ptr<Cache> table_cache_; |
||||||
|
DBOptions db_options_; |
||||||
|
std::unique_ptr<VersionSet> versions_; |
||||||
|
std::unique_ptr<WalManager> wal_manager_; |
||||||
|
|
||||||
|
std::unique_ptr<log::Writer> current_log_writer_; |
||||||
|
uint64_t current_log_number_; |
||||||
|
}; |
||||||
|
|
||||||
|
TEST(WalManagerTest, ReadFirstRecordCache) { |
||||||
|
Init(); |
||||||
|
std::string path = dbname_ + "/000001.log"; |
||||||
|
unique_ptr<WritableFile> file; |
||||||
|
ASSERT_OK(env_->NewWritableFile(path, &file, EnvOptions())); |
||||||
|
|
||||||
|
SequenceNumber s; |
||||||
|
ASSERT_OK(wal_manager_->TEST_ReadFirstLine(path, &s)); |
||||||
|
ASSERT_EQ(s, 0U); |
||||||
|
|
||||||
|
ASSERT_OK(wal_manager_->TEST_ReadFirstRecord(kAliveLogFile, 1, &s)); |
||||||
|
ASSERT_EQ(s, 0U); |
||||||
|
|
||||||
|
log::Writer writer(std::move(file)); |
||||||
|
WriteBatch batch; |
||||||
|
batch.Put("foo", "bar"); |
||||||
|
WriteBatchInternal::SetSequence(&batch, 10); |
||||||
|
writer.AddRecord(WriteBatchInternal::Contents(&batch)); |
||||||
|
|
||||||
|
// TODO(icanadi) move SpecialEnv outside of db_test, so we can reuse it here.
|
||||||
|
// Waiting for lei to finish with db_test
|
||||||
|
// env_->count_sequential_reads_ = true;
|
||||||
|
// sequential_read_counter_ sanity test
|
||||||
|
// ASSERT_EQ(env_->sequential_read_counter_.Read(), 0);
|
||||||
|
|
||||||
|
ASSERT_OK(wal_manager_->TEST_ReadFirstRecord(kAliveLogFile, 1, &s)); |
||||||
|
ASSERT_EQ(s, 10U); |
||||||
|
// did a read
|
||||||
|
// TODO(icanadi) move SpecialEnv outside of db_test, so we can reuse it here
|
||||||
|
// ASSERT_EQ(env_->sequential_read_counter_.Read(), 1);
|
||||||
|
|
||||||
|
ASSERT_OK(wal_manager_->TEST_ReadFirstRecord(kAliveLogFile, 1, &s)); |
||||||
|
ASSERT_EQ(s, 10U); |
||||||
|
// no new reads since the value is cached
|
||||||
|
// TODO(icanadi) move SpecialEnv outside of db_test, so we can reuse it here
|
||||||
|
// ASSERT_EQ(env_->sequential_read_counter_.Read(), 1);
|
||||||
|
} |
||||||
|
|
||||||
|
namespace { |
||||||
|
uint64_t GetLogDirSize(std::string dir_path, Env* env) { |
||||||
|
uint64_t dir_size = 0; |
||||||
|
std::vector<std::string> files; |
||||||
|
env->GetChildren(dir_path, &files); |
||||||
|
for (auto& f : files) { |
||||||
|
uint64_t number; |
||||||
|
FileType type; |
||||||
|
if (ParseFileName(f, &number, &type) && type == kLogFile) { |
||||||
|
std::string const file_path = dir_path + "/" + f; |
||||||
|
uint64_t file_size; |
||||||
|
env->GetFileSize(file_path, &file_size); |
||||||
|
dir_size += file_size; |
||||||
|
} |
||||||
|
} |
||||||
|
return dir_size; |
||||||
|
} |
||||||
|
std::vector<std::uint64_t> ListSpecificFiles( |
||||||
|
Env* env, const std::string& path, const FileType expected_file_type) { |
||||||
|
std::vector<std::string> files; |
||||||
|
std::vector<uint64_t> file_numbers; |
||||||
|
env->GetChildren(path, &files); |
||||||
|
uint64_t number; |
||||||
|
FileType type; |
||||||
|
for (size_t i = 0; i < files.size(); ++i) { |
||||||
|
if (ParseFileName(files[i], &number, &type)) { |
||||||
|
if (type == expected_file_type) { |
||||||
|
file_numbers.push_back(number); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return std::move(file_numbers); |
||||||
|
} |
||||||
|
|
||||||
|
int CountRecords(TransactionLogIterator* iter) { |
||||||
|
int count = 0; |
||||||
|
SequenceNumber lastSequence = 0; |
||||||
|
BatchResult res; |
||||||
|
while (iter->Valid()) { |
||||||
|
res = iter->GetBatch(); |
||||||
|
ASSERT_TRUE(res.sequence > lastSequence); |
||||||
|
++count; |
||||||
|
lastSequence = res.sequence; |
||||||
|
ASSERT_OK(iter->status()); |
||||||
|
iter->Next(); |
||||||
|
} |
||||||
|
return count; |
||||||
|
} |
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(WalManagerTest, WALArchivalSizeLimit) { |
||||||
|
db_options_.WAL_ttl_seconds = 0; |
||||||
|
db_options_.WAL_size_limit_MB = 1000; |
||||||
|
Init(); |
||||||
|
|
||||||
|
// TEST : Create WalManager with huge size limit and no ttl.
|
||||||
|
// Create some archived files and call PurgeObsoleteWALFiles().
|
||||||
|
// Count the archived log files that survived.
|
||||||
|
// Assert that all of them did.
|
||||||
|
// Change size limit. Re-open WalManager.
|
||||||
|
// Assert that archive is not greater than WAL_size_limit_MB after
|
||||||
|
// PurgeObsoleteWALFiles()
|
||||||
|
// Set ttl and time_to_check_ to small values. Re-open db.
|
||||||
|
// Assert that there are no archived logs left.
|
||||||
|
|
||||||
|
std::string archive_dir = ArchivalDirectory(dbname_); |
||||||
|
CreateArchiveLogs(20, 5000); |
||||||
|
|
||||||
|
std::vector<std::uint64_t> log_files = |
||||||
|
ListSpecificFiles(env_, archive_dir, kLogFile); |
||||||
|
ASSERT_EQ(log_files.size(), 20U); |
||||||
|
|
||||||
|
db_options_.WAL_size_limit_MB = 8; |
||||||
|
Reopen(); |
||||||
|
wal_manager_->PurgeObsoleteWALFiles(); |
||||||
|
|
||||||
|
uint64_t archive_size = GetLogDirSize(archive_dir, env_); |
||||||
|
ASSERT_TRUE(archive_size <= db_options_.WAL_size_limit_MB * 1024 * 1024); |
||||||
|
|
||||||
|
db_options_.WAL_ttl_seconds = 1; |
||||||
|
env_->SleepForMicroseconds(2 * 1000 * 1000); |
||||||
|
Reopen(); |
||||||
|
wal_manager_->PurgeObsoleteWALFiles(); |
||||||
|
|
||||||
|
log_files = ListSpecificFiles(env_, archive_dir, kLogFile); |
||||||
|
ASSERT_TRUE(log_files.empty()); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(WalManagerTest, WALArchivalTtl) { |
||||||
|
db_options_.WAL_ttl_seconds = 1000; |
||||||
|
Init(); |
||||||
|
|
||||||
|
// TEST : Create WalManager with a ttl and no size limit.
|
||||||
|
// Create some archived log files and call PurgeObsoleteWALFiles().
|
||||||
|
// Assert that files are not deleted
|
||||||
|
// Reopen db with small ttl.
|
||||||
|
// Assert that all archived logs was removed.
|
||||||
|
|
||||||
|
std::string archive_dir = ArchivalDirectory(dbname_); |
||||||
|
CreateArchiveLogs(20, 5000); |
||||||
|
|
||||||
|
std::vector<uint64_t> log_files = |
||||||
|
ListSpecificFiles(env_, archive_dir, kLogFile); |
||||||
|
ASSERT_GT(log_files.size(), 0U); |
||||||
|
|
||||||
|
db_options_.WAL_ttl_seconds = 1; |
||||||
|
env_->SleepForMicroseconds(3 * 1000 * 1000); |
||||||
|
Reopen(); |
||||||
|
wal_manager_->PurgeObsoleteWALFiles(); |
||||||
|
|
||||||
|
log_files = ListSpecificFiles(env_, archive_dir, kLogFile); |
||||||
|
ASSERT_TRUE(log_files.empty()); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(WalManagerTest, TransactionLogIteratorMoveOverZeroFiles) { |
||||||
|
Init(); |
||||||
|
RollTheLog(false); |
||||||
|
Put("key1", std::string(1024, 'a')); |
||||||
|
// Create a zero record WAL file.
|
||||||
|
RollTheLog(false); |
||||||
|
RollTheLog(false); |
||||||
|
|
||||||
|
Put("key2", std::string(1024, 'a')); |
||||||
|
|
||||||
|
auto iter = OpenTransactionLogIter(0); |
||||||
|
ASSERT_EQ(2, CountRecords(iter.get())); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(WalManagerTest, TransactionLogIteratorJustEmptyFile) { |
||||||
|
Init(); |
||||||
|
RollTheLog(false); |
||||||
|
auto iter = OpenTransactionLogIter(0); |
||||||
|
// Check that an empty iterator is returned
|
||||||
|
ASSERT_TRUE(!iter->Valid()); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { return rocksdb::test::RunAllTests(); } |
Loading…
Reference in new issue