From eb56228e66b99b6d7a9b78bc2e0a31328a698dc4 Mon Sep 17 00:00:00 2001 From: hellolutar Date: Fri, 11 Oct 2024 18:00:44 +0800 Subject: [PATCH 01/22] chore: add some comments --- .gitignore | 2 + CMakeLists.txt | 3 + db/builder.h | 10 + db/db_impl.cc | 511 ++++++++++++++++---------------- db/db_impl.h | 145 +++++---- db/db_iter.h | 5 + db/dbformat.h | 136 +++++---- db/log_writer.h | 18 ++ db/memtable.h | 30 ++ db/skiplist.h | 250 ++++++++++------ db/snapshot.h | 8 + db/table_cache.h | 51 ++-- db/version_edit.h | 8 + db/version_set.h | 298 ++++++++++++------- db/write_batch_internal.h | 52 ++-- demo/.gitignore | 1 + demo/CMakeLists.txt | 8 + demo/case/CMakeLists.txt | 20 ++ demo/case/get.cc | 0 demo/case/open_close.cc | 20 ++ demo/case/put.cc | 0 doc/learn/db.md | 203 +++++++++++++ include/leveldb/cache.h | 83 ++++-- include/leveldb/comparator.h | 63 +++- include/leveldb/db.h | 157 +++++++--- include/leveldb/env.h | 284 ++++++++++++------ include/leveldb/iterator.h | 76 ++++- include/leveldb/options.h | 78 ++++- include/leveldb/table.h | 67 +++-- include/leveldb/table_builder.h | 73 ++++- include/leveldb/write_batch.h | 74 ++++- 31 files changed, 1894 insertions(+), 840 deletions(-) create mode 100644 demo/.gitignore create mode 100644 demo/CMakeLists.txt create mode 100644 demo/case/CMakeLists.txt create mode 100644 demo/case/get.cc create mode 100644 demo/case/open_close.cc create mode 100644 demo/case/put.cc create mode 100644 doc/learn/db.md diff --git a/.gitignore b/.gitignore index c4b2425..f36d979 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ # Build directory. build/ out/ + +.cache \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 97953ab..beec4f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,9 @@ project(leveldb VERSION 1.21.0 LANGUAGES C CXX) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED OFF) set(CMAKE_C_EXTENSIONS OFF) +set(CMAKE_INSTALL_PREFIX "/home/lutar/code/c/leveldb/demo/lib") +set(CMAKE_BUILD_TYPE "Debug") +set(CMAKE_EXPORT_COMPILE_COMMANDS on) # This project requires C++11. set(CMAKE_CXX_STANDARD 11) diff --git a/db/builder.h b/db/builder.h index 0289730..3d4ceee 100644 --- a/db/builder.h +++ b/db/builder.h @@ -22,6 +22,16 @@ class VersionEdit; // *meta will be filled with metadata about the generated table. // If no data is present in *iter, meta->file_size will be set to // zero, and no Table file will be produced. + +/** + * @brief 根据 *iter 的内容构建一个表文件。生成的文件将根据 meta->number 命名。 + * + * 更新FileMetaData信息:如file_size, smallest, largest + * + * 成功后,*meta 的其余部分将填充有关生成的表的 metadata。 + * + * 如果 *iter中没有数据,meta->file_size 将设置为零,并且不会生成任何表文件。 + */ Status BuildTable(const std::string& dbname, Env* env, const Options& options, diff --git a/db/db_impl.cc b/db/db_impl.cc index 3468862..a826cd2 100644 --- a/db/db_impl.cc +++ b/db/db_impl.cc @@ -43,16 +43,16 @@ const int kNumNonTableCacheFiles = 10; // Information kept for every waiting writer struct DBImpl::Writer { Status status; - WriteBatch* batch; + WriteBatch *batch; bool sync; bool done; port::CondVar cv; - explicit Writer(port::Mutex* mu) : cv(mu) { } + explicit Writer(port::Mutex *mu) : cv(mu) {} }; struct DBImpl::CompactionState { - Compaction* const compaction; + Compaction *const compaction; // Sequence numbers < smallest_snapshot are not significant since we // will never have to service a snapshot below smallest_snapshot. @@ -69,41 +69,39 @@ struct DBImpl::CompactionState { std::vector outputs; // State kept for output being generated - WritableFile* outfile; - TableBuilder* builder; + WritableFile *outfile; + TableBuilder *builder; uint64_t total_bytes; - Output* current_output() { return &outputs[outputs.size()-1]; } + Output *current_output() { return &outputs[outputs.size() - 1]; } - explicit CompactionState(Compaction* c) - : compaction(c), - outfile(nullptr), - builder(nullptr), - total_bytes(0) { - } + explicit CompactionState(Compaction *c) + : compaction(c), outfile(nullptr), builder(nullptr), total_bytes(0) {} }; // Fix user-supplied options to be reasonable template -static void ClipToRange(T* ptr, V minvalue, V maxvalue) { - if (static_cast(*ptr) > maxvalue) *ptr = maxvalue; - if (static_cast(*ptr) < minvalue) *ptr = minvalue; +static void ClipToRange(T *ptr, V minvalue, V maxvalue) { + if (static_cast(*ptr) > maxvalue) + *ptr = maxvalue; + if (static_cast(*ptr) < minvalue) + *ptr = minvalue; } -Options SanitizeOptions(const std::string& dbname, - const InternalKeyComparator* icmp, - const InternalFilterPolicy* ipolicy, - const Options& src) { +Options SanitizeOptions(const std::string &dbname, + const InternalKeyComparator *icmp, + const InternalFilterPolicy *ipolicy, + const Options &src) { Options result = src; result.comparator = icmp; result.filter_policy = (src.filter_policy != nullptr) ? ipolicy : nullptr; - ClipToRange(&result.max_open_files, 64 + kNumNonTableCacheFiles, 50000); - ClipToRange(&result.write_buffer_size, 64<<10, 1<<30); - ClipToRange(&result.max_file_size, 1<<20, 1<<30); - ClipToRange(&result.block_size, 1<<10, 4<<20); + ClipToRange(&result.max_open_files, 64 + kNumNonTableCacheFiles, 50000); + ClipToRange(&result.write_buffer_size, 64 << 10, 1 << 30); + ClipToRange(&result.max_file_size, 1 << 20, 1 << 30); + ClipToRange(&result.block_size, 1 << 10, 4 << 20); if (result.info_log == nullptr) { // Open a log file in the same directory as the db - src.env->CreateDir(dbname); // In case it does not exist + src.env->CreateDir(dbname); // In case it does not exist src.env->RenameFile(InfoLogFileName(dbname), OldInfoLogFileName(dbname)); Status s = src.env->NewLogger(InfoLogFileName(dbname), &result.info_log); if (!s.ok()) { @@ -117,14 +115,13 @@ Options SanitizeOptions(const std::string& dbname, return result; } -static int TableCacheSize(const Options& sanitized_options) { +static int TableCacheSize(const Options &sanitized_options) { // Reserve ten files or so for other uses and give the rest to TableCache. return sanitized_options.max_open_files - kNumNonTableCacheFiles; } -DBImpl::DBImpl(const Options& raw_options, const std::string& dbname) - : env_(raw_options.env), - internal_comparator_(raw_options.comparator), +DBImpl::DBImpl(const Options &raw_options, const std::string &dbname) + : env_(raw_options.env), internal_comparator_(raw_options.comparator), internal_filter_policy_(raw_options.filter_policy), options_(SanitizeOptions(dbname, &internal_comparator_, &internal_filter_policy_, raw_options)), @@ -132,19 +129,11 @@ DBImpl::DBImpl(const Options& raw_options, const std::string& dbname) owns_cache_(options_.block_cache != raw_options.block_cache), dbname_(dbname), table_cache_(new TableCache(dbname_, options_, TableCacheSize(options_))), - db_lock_(nullptr), - shutting_down_(false), - background_work_finished_signal_(&mutex_), - mem_(nullptr), - imm_(nullptr), - has_imm_(false), - logfile_(nullptr), - logfile_number_(0), - log_(nullptr), - seed_(0), - tmp_batch_(new WriteBatch), - background_compaction_scheduled_(false), - manual_compaction_(nullptr), + db_lock_(nullptr), shutting_down_(false), + background_work_finished_signal_(&mutex_), mem_(nullptr), imm_(nullptr), + has_imm_(false), logfile_(nullptr), logfile_number_(0), log_(nullptr), + seed_(0), tmp_batch_(new WriteBatch), + background_compaction_scheduled_(false), manual_compaction_(nullptr), versions_(new VersionSet(dbname_, &options_, table_cache_, &internal_comparator_)) {} @@ -162,8 +151,10 @@ DBImpl::~DBImpl() { } delete versions_; - if (mem_ != nullptr) mem_->Unref(); - if (imm_ != nullptr) imm_->Unref(); + if (mem_ != nullptr) + mem_->Unref(); + if (imm_ != nullptr) + imm_->Unref(); delete tmp_batch_; delete log_; delete logfile_; @@ -185,7 +176,7 @@ Status DBImpl::NewDB() { new_db.SetLastSequence(0); const std::string manifest = DescriptorFileName(dbname_, 1); - WritableFile* file; + WritableFile *file; Status s = env_->NewWritableFile(manifest, &file); if (!s.ok()) { return s; @@ -209,7 +200,7 @@ Status DBImpl::NewDB() { return s; } -void DBImpl::MaybeIgnoreError(Status* s) const { +void DBImpl::MaybeIgnoreError(Status *s) const { if (s->ok() || options_.paranoid_checks) { // No change needed } else { @@ -232,43 +223,42 @@ void DBImpl::DeleteObsoleteFiles() { versions_->AddLiveFiles(&live); std::vector filenames; - env_->GetChildren(dbname_, &filenames); // Ignoring errors on purpose + env_->GetChildren(dbname_, &filenames); // Ignoring errors on purpose uint64_t number; FileType type; for (size_t i = 0; i < filenames.size(); i++) { if (ParseFileName(filenames[i], &number, &type)) { bool keep = true; switch (type) { - case kLogFile: - keep = ((number >= versions_->LogNumber()) || - (number == versions_->PrevLogNumber())); - break; - case kDescriptorFile: - // Keep my manifest file, and any newer incarnations' - // (in case there is a race that allows other incarnations) - keep = (number >= versions_->ManifestFileNumber()); - break; - case kTableFile: - keep = (live.find(number) != live.end()); - break; - case kTempFile: - // Any temp files that are currently being written to must - // be recorded in pending_outputs_, which is inserted into "live" - keep = (live.find(number) != live.end()); - break; - case kCurrentFile: - case kDBLockFile: - case kInfoLogFile: - keep = true; - break; + case kLogFile: + keep = ((number >= versions_->LogNumber()) || + (number == versions_->PrevLogNumber())); + break; + case kDescriptorFile: + // Keep my manifest file, and any newer incarnations' + // (in case there is a race that allows other incarnations) + keep = (number >= versions_->ManifestFileNumber()); + break; + case kTableFile: + keep = (live.find(number) != live.end()); + break; + case kTempFile: + // Any temp files that are currently being written to must + // be recorded in pending_outputs_, which is inserted into "live" + keep = (live.find(number) != live.end()); + break; + case kCurrentFile: + case kDBLockFile: + case kInfoLogFile: + keep = true; + break; } if (!keep) { if (type == kTableFile) { table_cache_->Evict(number); } - Log(options_.info_log, "Delete type=%d #%lld\n", - static_cast(type), + Log(options_.info_log, "Delete type=%d #%lld\n", static_cast(type), static_cast(number)); env_->DeleteFile(dbname_ + "/" + filenames[i]); } @@ -276,12 +266,13 @@ void DBImpl::DeleteObsoleteFiles() { } } -Status DBImpl::Recover(VersionEdit* edit, bool *save_manifest) { +Status DBImpl::Recover(VersionEdit *edit, bool *save_manifest) { mutex_.AssertHeld(); // Ignore error from CreateDir since the creation of the DB is // committed only when the descriptor is created, and this directory // may already exist from a previous failed creation attempt. + // 忽略CreateDir的错误,因为只有在创建描述符时才会提交DB的创建,并且该目录可能在之前失败的创建尝试中已经存在。 env_->CreateDir(dbname_); assert(db_lock_ == nullptr); Status s = env_->LockFile(LockFileName(dbname_), &db_lock_); @@ -301,8 +292,8 @@ Status DBImpl::Recover(VersionEdit* edit, bool *save_manifest) { } } else { if (options_.error_if_exists) { - return Status::InvalidArgument( - dbname_, "exists (error_if_exists is true)"); + return Status::InvalidArgument(dbname_, + "exists (error_if_exists is true)"); } } @@ -368,18 +359,19 @@ Status DBImpl::Recover(VersionEdit* edit, bool *save_manifest) { } Status DBImpl::RecoverLogFile(uint64_t log_number, bool last_log, - bool* save_manifest, VersionEdit* edit, - SequenceNumber* max_sequence) { + bool *save_manifest, VersionEdit *edit, + SequenceNumber *max_sequence) { struct LogReporter : public log::Reader::Reporter { - Env* env; - Logger* info_log; - const char* fname; - Status* status; // null if options_.paranoid_checks==false - virtual void Corruption(size_t bytes, const Status& s) { + Env *env; + Logger *info_log; + const char *fname; + Status *status; // null if options_.paranoid_checks==false + virtual void Corruption(size_t bytes, const Status &s) { Log(info_log, "%s%s: dropping %d bytes; %s", - (this->status == nullptr ? "(ignoring error) " : ""), - fname, static_cast(bytes), s.ToString().c_str()); - if (this->status != nullptr && this->status->ok()) *this->status = s; + (this->status == nullptr ? "(ignoring error) " : ""), fname, + static_cast(bytes), s.ToString().c_str()); + if (this->status != nullptr && this->status->ok()) + *this->status = s; } }; @@ -387,7 +379,7 @@ Status DBImpl::RecoverLogFile(uint64_t log_number, bool last_log, // Open the log file std::string fname = LogFileName(dbname_, log_number); - SequentialFile* file; + SequentialFile *file; Status status = env_->NewSequentialFile(fname, &file); if (!status.ok()) { MaybeIgnoreError(&status); @@ -404,22 +396,20 @@ Status DBImpl::RecoverLogFile(uint64_t log_number, bool last_log, // paranoid_checks==false so that corruptions cause entire commits // to be skipped instead of propagating bad information (like overly // large sequence numbers). - log::Reader reader(file, &reporter, true/*checksum*/, - 0/*initial_offset*/); + log::Reader reader(file, &reporter, true /*checksum*/, 0 /*initial_offset*/); Log(options_.info_log, "Recovering log #%llu", - (unsigned long long) log_number); + (unsigned long long)log_number); // Read all the records and add to a memtable std::string scratch; Slice record; WriteBatch batch; int compactions = 0; - MemTable* mem = nullptr; - while (reader.ReadRecord(&record, &scratch) && - status.ok()) { + MemTable *mem = nullptr; + while (reader.ReadRecord(&record, &scratch) && status.ok()) { if (record.size() < 12) { - reporter.Corruption( - record.size(), Status::Corruption("log record too small")); + reporter.Corruption(record.size(), + Status::Corruption("log record too small")); continue; } WriteBatchInternal::SetContents(&batch, record); @@ -433,9 +423,8 @@ Status DBImpl::RecoverLogFile(uint64_t log_number, bool last_log, if (!status.ok()) { break; } - const SequenceNumber last_seq = - WriteBatchInternal::Sequence(&batch) + - WriteBatchInternal::Count(&batch) - 1; + const SequenceNumber last_seq = WriteBatchInternal::Sequence(&batch) + + WriteBatchInternal::Count(&batch) - 1; if (last_seq > *max_sequence) { *max_sequence = last_seq; } @@ -490,16 +479,16 @@ Status DBImpl::RecoverLogFile(uint64_t log_number, bool last_log, return status; } -Status DBImpl::WriteLevel0Table(MemTable* mem, VersionEdit* edit, - Version* base) { +Status DBImpl::WriteLevel0Table(MemTable *mem, VersionEdit *edit, + Version *base) { mutex_.AssertHeld(); const uint64_t start_micros = env_->NowMicros(); FileMetaData meta; meta.number = versions_->NewFileNumber(); pending_outputs_.insert(meta.number); - Iterator* iter = mem->NewIterator(); + Iterator *iter = mem->NewIterator(); Log(options_.info_log, "Level-0 table #%llu: started", - (unsigned long long) meta.number); + (unsigned long long)meta.number); Status s; { @@ -509,13 +498,11 @@ Status DBImpl::WriteLevel0Table(MemTable* mem, VersionEdit* edit, } Log(options_.info_log, "Level-0 table #%llu: %lld bytes %s", - (unsigned long long) meta.number, - (unsigned long long) meta.file_size, + (unsigned long long)meta.number, (unsigned long long)meta.file_size, s.ToString().c_str()); delete iter; pending_outputs_.erase(meta.number); - // Note that if file_size is zero, the file has been deleted and // should not be added to the manifest. int level = 0; @@ -525,8 +512,8 @@ Status DBImpl::WriteLevel0Table(MemTable* mem, VersionEdit* edit, if (base != nullptr) { level = base->PickLevelForMemTableOutput(min_user_key, max_user_key); } - edit->AddFile(level, meta.number, meta.file_size, - meta.smallest, meta.largest); + edit->AddFile(level, meta.number, meta.file_size, meta.smallest, + meta.largest); } CompactionStats stats; @@ -542,7 +529,7 @@ void DBImpl::CompactMemTable() { // Save the contents of the memtable as a new Table VersionEdit edit; - Version* base = versions_->current(); + Version *base = versions_->current(); base->Ref(); Status s = WriteLevel0Table(imm_, &edit, base); base->Unref(); @@ -554,7 +541,7 @@ void DBImpl::CompactMemTable() { // Replace immutable memtable with the generated Table if (s.ok()) { edit.SetPrevLogNumber(0); - edit.SetLogNumber(logfile_number_); // Earlier logs no longer needed + edit.SetLogNumber(logfile_number_); // Earlier logs no longer needed s = versions_->LogAndApply(&edit, &mutex_); } @@ -569,25 +556,25 @@ void DBImpl::CompactMemTable() { } } -void DBImpl::CompactRange(const Slice* begin, const Slice* end) { +void DBImpl::CompactRange(const Slice *begin, const Slice *end) { int max_level_with_files = 1; { MutexLock l(&mutex_); - Version* base = versions_->current(); + Version *base = versions_->current(); for (int level = 1; level < config::kNumLevels; level++) { if (base->OverlapInLevel(level, begin, end)) { max_level_with_files = level; } } } - TEST_CompactMemTable(); // TODO(sanjay): Skip if memtable does not overlap + TEST_CompactMemTable(); // TODO(sanjay): Skip if memtable does not overlap for (int level = 0; level < max_level_with_files; level++) { TEST_CompactRange(level, begin, end); } } -void DBImpl::TEST_CompactRange(int level, const Slice* begin, - const Slice* end) { +void DBImpl::TEST_CompactRange(int level, const Slice *begin, + const Slice *end) { assert(level >= 0); assert(level + 1 < config::kNumLevels); @@ -612,10 +599,10 @@ void DBImpl::TEST_CompactRange(int level, const Slice* begin, MutexLock l(&mutex_); while (!manual.done && !shutting_down_.load(std::memory_order_acquire) && bg_error_.ok()) { - if (manual_compaction_ == nullptr) { // Idle + if (manual_compaction_ == nullptr) { // Idle manual_compaction_ = &manual; MaybeScheduleCompaction(); - } else { // Running either my compaction or another compaction. + } else { // Running either my compaction or another compaction. background_work_finished_signal_.Wait(); } } @@ -641,7 +628,7 @@ Status DBImpl::TEST_CompactMemTable() { return s; } -void DBImpl::RecordBackgroundError(const Status& s) { +void DBImpl::RecordBackgroundError(const Status &s) { mutex_.AssertHeld(); if (bg_error_.ok()) { bg_error_ = s; @@ -657,8 +644,7 @@ void DBImpl::MaybeScheduleCompaction() { // DB is being deleted; no more background compactions } else if (!bg_error_.ok()) { // Already got an error; no more changes - } else if (imm_ == nullptr && - manual_compaction_ == nullptr && + } else if (imm_ == nullptr && manual_compaction_ == nullptr && !versions_->NeedsCompaction()) { // No work to be done } else { @@ -667,8 +653,8 @@ void DBImpl::MaybeScheduleCompaction() { } } -void DBImpl::BGWork(void* db) { - reinterpret_cast(db)->BackgroundCall(); +void DBImpl::BGWork(void *db) { + reinterpret_cast(db)->BackgroundCall(); } void DBImpl::BackgroundCall() { @@ -698,11 +684,11 @@ void DBImpl::BackgroundCompaction() { return; } - Compaction* c; + Compaction *c; bool is_manual = (manual_compaction_ != nullptr); InternalKey manual_end; if (is_manual) { - ManualCompaction* m = manual_compaction_; + ManualCompaction *m = manual_compaction_; c = versions_->CompactRange(m->level, m->begin, m->end); m->done = (c == nullptr); if (c != nullptr) { @@ -710,8 +696,7 @@ void DBImpl::BackgroundCompaction() { } Log(options_.info_log, "Manual compaction at level-%d from %s .. %s; will stop at %s\n", - m->level, - (m->begin ? m->begin->DebugString().c_str() : "(begin)"), + m->level, (m->begin ? m->begin->DebugString().c_str() : "(begin)"), (m->end ? m->end->DebugString().c_str() : "(end)"), (m->done ? "(end)" : manual_end.DebugString().c_str())); } else { @@ -724,23 +709,21 @@ void DBImpl::BackgroundCompaction() { } else if (!is_manual && c->IsTrivialMove()) { // Move file to next level assert(c->num_input_files(0) == 1); - FileMetaData* f = c->input(0, 0); + FileMetaData *f = c->input(0, 0); c->edit()->DeleteFile(c->level(), f->number); - c->edit()->AddFile(c->level() + 1, f->number, f->file_size, - f->smallest, f->largest); + c->edit()->AddFile(c->level() + 1, f->number, f->file_size, f->smallest, + f->largest); status = versions_->LogAndApply(c->edit(), &mutex_); if (!status.ok()) { RecordBackgroundError(status); } VersionSet::LevelSummaryStorage tmp; Log(options_.info_log, "Moved #%lld to level-%d %lld bytes %s: %s\n", - static_cast(f->number), - c->level() + 1, + static_cast(f->number), c->level() + 1, static_cast(f->file_size), - status.ToString().c_str(), - versions_->LevelSummary(&tmp)); + status.ToString().c_str(), versions_->LevelSummary(&tmp)); } else { - CompactionState* compact = new CompactionState(c); + CompactionState *compact = new CompactionState(c); status = DoCompactionWork(compact); if (!status.ok()) { RecordBackgroundError(status); @@ -756,12 +739,11 @@ void DBImpl::BackgroundCompaction() { } else if (shutting_down_.load(std::memory_order_acquire)) { // Ignore compaction errors found during shutting down } else { - Log(options_.info_log, - "Compaction error: %s", status.ToString().c_str()); + Log(options_.info_log, "Compaction error: %s", status.ToString().c_str()); } if (is_manual) { - ManualCompaction* m = manual_compaction_; + ManualCompaction *m = manual_compaction_; if (!status.ok()) { m->done = true; } @@ -775,7 +757,7 @@ void DBImpl::BackgroundCompaction() { } } -void DBImpl::CleanupCompaction(CompactionState* compact) { +void DBImpl::CleanupCompaction(CompactionState *compact) { mutex_.AssertHeld(); if (compact->builder != nullptr) { // May happen if we get a shutdown call in the middle of compaction @@ -786,13 +768,13 @@ void DBImpl::CleanupCompaction(CompactionState* compact) { } delete compact->outfile; for (size_t i = 0; i < compact->outputs.size(); i++) { - const CompactionState::Output& out = compact->outputs[i]; + const CompactionState::Output &out = compact->outputs[i]; pending_outputs_.erase(out.number); } delete compact; } -Status DBImpl::OpenCompactionOutputFile(CompactionState* compact) { +Status DBImpl::OpenCompactionOutputFile(CompactionState *compact) { assert(compact != nullptr); assert(compact->builder == nullptr); uint64_t file_number; @@ -817,8 +799,8 @@ Status DBImpl::OpenCompactionOutputFile(CompactionState* compact) { return s; } -Status DBImpl::FinishCompactionOutputFile(CompactionState* compact, - Iterator* input) { +Status DBImpl::FinishCompactionOutputFile(CompactionState *compact, + Iterator *input) { assert(compact != nullptr); assert(compact->outfile != nullptr); assert(compact->builder != nullptr); @@ -852,52 +834,44 @@ Status DBImpl::FinishCompactionOutputFile(CompactionState* compact, if (s.ok() && current_entries > 0) { // Verify that the table is usable - Iterator* iter = table_cache_->NewIterator(ReadOptions(), - output_number, - current_bytes); + Iterator *iter = + table_cache_->NewIterator(ReadOptions(), output_number, current_bytes); s = iter->status(); delete iter; if (s.ok()) { - Log(options_.info_log, - "Generated table #%llu@%d: %lld keys, %lld bytes", - (unsigned long long) output_number, - compact->compaction->level(), - (unsigned long long) current_entries, - (unsigned long long) current_bytes); + Log(options_.info_log, "Generated table #%llu@%d: %lld keys, %lld bytes", + (unsigned long long)output_number, compact->compaction->level(), + (unsigned long long)current_entries, + (unsigned long long)current_bytes); } } return s; } - -Status DBImpl::InstallCompactionResults(CompactionState* compact) { +Status DBImpl::InstallCompactionResults(CompactionState *compact) { mutex_.AssertHeld(); - Log(options_.info_log, "Compacted %d@%d + %d@%d files => %lld bytes", - compact->compaction->num_input_files(0), - compact->compaction->level(), - compact->compaction->num_input_files(1), - compact->compaction->level() + 1, + Log(options_.info_log, "Compacted %d@%d + %d@%d files => %lld bytes", + compact->compaction->num_input_files(0), compact->compaction->level(), + compact->compaction->num_input_files(1), compact->compaction->level() + 1, static_cast(compact->total_bytes)); // Add compaction outputs compact->compaction->AddInputDeletions(compact->compaction->edit()); const int level = compact->compaction->level(); for (size_t i = 0; i < compact->outputs.size(); i++) { - const CompactionState::Output& out = compact->outputs[i]; - compact->compaction->edit()->AddFile( - level + 1, - out.number, out.file_size, out.smallest, out.largest); + const CompactionState::Output &out = compact->outputs[i]; + compact->compaction->edit()->AddFile(level + 1, out.number, out.file_size, + out.smallest, out.largest); } return versions_->LogAndApply(compact->compaction->edit(), &mutex_); } -Status DBImpl::DoCompactionWork(CompactionState* compact) { +Status DBImpl::DoCompactionWork(CompactionState *compact) { const uint64_t start_micros = env_->NowMicros(); - int64_t imm_micros = 0; // Micros spent doing imm_ compactions + int64_t imm_micros = 0; // Micros spent doing imm_ compactions - Log(options_.info_log, "Compacting %d@%d + %d@%d files", - compact->compaction->num_input_files(0), - compact->compaction->level(), + Log(options_.info_log, "Compacting %d@%d + %d@%d files", + compact->compaction->num_input_files(0), compact->compaction->level(), compact->compaction->num_input_files(1), compact->compaction->level() + 1); @@ -913,14 +887,14 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) { // Release mutex while we're actually doing the compaction work mutex_.Unlock(); - Iterator* input = versions_->MakeInputIterator(compact->compaction); + Iterator *input = versions_->MakeInputIterator(compact->compaction); input->SeekToFirst(); Status status; ParsedInternalKey ikey; std::string current_user_key; bool has_current_user_key = false; SequenceNumber last_sequence_for_key = kMaxSequenceNumber; - for (; input->Valid() && !shutting_down_.load(std::memory_order_acquire); ) { + for (; input->Valid() && !shutting_down_.load(std::memory_order_acquire);) { // Prioritize immutable compaction work if (has_imm_.load(std::memory_order_relaxed)) { const uint64_t imm_start = env_->NowMicros(); @@ -952,8 +926,8 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) { last_sequence_for_key = kMaxSequenceNumber; } else { if (!has_current_user_key || - user_comparator()->Compare(ikey.user_key, - Slice(current_user_key)) != 0) { + user_comparator()->Compare(ikey.user_key, Slice(current_user_key)) != + 0) { // First occurrence of this user key current_user_key.assign(ikey.user_key.data(), ikey.user_key.size()); has_current_user_key = true; @@ -962,7 +936,7 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) { if (last_sequence_for_key <= compact->smallest_snapshot) { // Hidden by an newer entry for same user key - drop = true; // (A) + drop = true; // (A) } else if (ikey.type == kTypeDeletion && ikey.sequence <= compact->smallest_snapshot && compact->compaction->IsBaseLevelForKey(ikey.user_key)) { @@ -1048,43 +1022,43 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) { RecordBackgroundError(status); } VersionSet::LevelSummaryStorage tmp; - Log(options_.info_log, - "compacted to: %s", versions_->LevelSummary(&tmp)); + Log(options_.info_log, "compacted to: %s", versions_->LevelSummary(&tmp)); return status; } namespace { struct IterState { - port::Mutex* const mu; - Version* const version GUARDED_BY(mu); - MemTable* const mem GUARDED_BY(mu); - MemTable* const imm GUARDED_BY(mu); + port::Mutex *const mu; + Version *const version GUARDED_BY(mu); + MemTable *const mem GUARDED_BY(mu); + MemTable *const imm GUARDED_BY(mu); - IterState(port::Mutex* mutex, MemTable* mem, MemTable* imm, Version* version) - : mu(mutex), version(version), mem(mem), imm(imm) { } + IterState(port::Mutex *mutex, MemTable *mem, MemTable *imm, Version *version) + : mu(mutex), version(version), mem(mem), imm(imm) {} }; -static void CleanupIteratorState(void* arg1, void* arg2) { - IterState* state = reinterpret_cast(arg1); +static void CleanupIteratorState(void *arg1, void *arg2) { + IterState *state = reinterpret_cast(arg1); state->mu->Lock(); state->mem->Unref(); - if (state->imm != nullptr) state->imm->Unref(); + if (state->imm != nullptr) + state->imm->Unref(); state->version->Unref(); state->mu->Unlock(); delete state; } -} // anonymous namespace +} // anonymous namespace -Iterator* DBImpl::NewInternalIterator(const ReadOptions& options, - SequenceNumber* latest_snapshot, - uint32_t* seed) { +Iterator *DBImpl::NewInternalIterator(const ReadOptions &options, + SequenceNumber *latest_snapshot, + uint32_t *seed) { mutex_.Lock(); *latest_snapshot = versions_->LastSequence(); // Collect together all needed child iterators - std::vector list; + std::vector list; list.push_back(mem_->NewIterator()); mem_->Ref(); if (imm_ != nullptr) { @@ -1092,11 +1066,11 @@ Iterator* DBImpl::NewInternalIterator(const ReadOptions& options, imm_->Ref(); } versions_->current()->AddIterators(options, &list); - Iterator* internal_iter = + Iterator *internal_iter = NewMergingIterator(&internal_comparator_, &list[0], list.size()); versions_->current()->Ref(); - IterState* cleanup = new IterState(&mutex_, mem_, imm_, versions_->current()); + IterState *cleanup = new IterState(&mutex_, mem_, imm_, versions_->current()); internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, nullptr); *seed = ++seed_; @@ -1104,7 +1078,7 @@ Iterator* DBImpl::NewInternalIterator(const ReadOptions& options, return internal_iter; } -Iterator* DBImpl::TEST_NewInternalIterator() { +Iterator *DBImpl::TEST_NewInternalIterator() { SequenceNumber ignored; uint32_t ignored_seed; return NewInternalIterator(ReadOptions(), &ignored, &ignored_seed); @@ -1115,24 +1089,24 @@ int64_t DBImpl::TEST_MaxNextLevelOverlappingBytes() { return versions_->MaxNextLevelOverlappingBytes(); } -Status DBImpl::Get(const ReadOptions& options, - const Slice& key, - std::string* value) { +Status DBImpl::Get(const ReadOptions &options, const Slice &key, + std::string *value) { Status s; MutexLock l(&mutex_); SequenceNumber snapshot; if (options.snapshot != nullptr) { snapshot = - static_cast(options.snapshot)->sequence_number(); + static_cast(options.snapshot)->sequence_number(); } else { snapshot = versions_->LastSequence(); } - MemTable* mem = mem_; - MemTable* imm = imm_; - Version* current = versions_->current(); + MemTable *mem = mem_; + MemTable *imm = imm_; + Version *current = versions_->current(); mem->Ref(); - if (imm != nullptr) imm->Ref(); + if (imm != nullptr) + imm->Ref(); current->Ref(); bool have_stat_update = false; @@ -1158,20 +1132,22 @@ Status DBImpl::Get(const ReadOptions& options, MaybeScheduleCompaction(); } mem->Unref(); - if (imm != nullptr) imm->Unref(); + if (imm != nullptr) + imm->Unref(); current->Unref(); return s; } -Iterator* DBImpl::NewIterator(const ReadOptions& options) { +Iterator *DBImpl::NewIterator(const ReadOptions &options) { SequenceNumber latest_snapshot; uint32_t seed; - Iterator* iter = NewInternalIterator(options, &latest_snapshot, &seed); + Iterator *iter = NewInternalIterator(options, &latest_snapshot, &seed); return NewDBIterator( this, user_comparator(), iter, (options.snapshot != nullptr - ? static_cast(options.snapshot)->sequence_number() - : latest_snapshot), + ? static_cast(options.snapshot) + ->sequence_number() + : latest_snapshot), seed); } @@ -1182,26 +1158,26 @@ void DBImpl::RecordReadSample(Slice key) { } } -const Snapshot* DBImpl::GetSnapshot() { +const Snapshot *DBImpl::GetSnapshot() { MutexLock l(&mutex_); return snapshots_.New(versions_->LastSequence()); } -void DBImpl::ReleaseSnapshot(const Snapshot* snapshot) { +void DBImpl::ReleaseSnapshot(const Snapshot *snapshot) { MutexLock l(&mutex_); - snapshots_.Delete(static_cast(snapshot)); + snapshots_.Delete(static_cast(snapshot)); } // Convenience methods -Status DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val) { +Status DBImpl::Put(const WriteOptions &o, const Slice &key, const Slice &val) { return DB::Put(o, key, val); } -Status DBImpl::Delete(const WriteOptions& options, const Slice& key) { +Status DBImpl::Delete(const WriteOptions &options, const Slice &key) { return DB::Delete(options, key); } -Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) { +Status DBImpl::Write(const WriteOptions &options, WriteBatch *my_batch) { Writer w(&mutex_); w.batch = my_batch; w.sync = options.sync; @@ -1219,9 +1195,9 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) { // May temporarily unlock and wait. Status status = MakeRoomForWrite(my_batch == nullptr); uint64_t last_sequence = versions_->LastSequence(); - Writer* last_writer = &w; - if (status.ok() && my_batch != nullptr) { // nullptr batch is for compactions - WriteBatch* updates = BuildBatchGroup(&last_writer); + Writer *last_writer = &w; + if (status.ok() && my_batch != nullptr) { // nullptr batch is for compactions + WriteBatch *updates = BuildBatchGroup(&last_writer); WriteBatchInternal::SetSequence(updates, last_sequence + 1); last_sequence += WriteBatchInternal::Count(updates); @@ -1250,20 +1226,22 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) { RecordBackgroundError(status); } } - if (updates == tmp_batch_) tmp_batch_->Clear(); + if (updates == tmp_batch_) + tmp_batch_->Clear(); versions_->SetLastSequence(last_sequence); } while (true) { - Writer* ready = writers_.front(); + Writer *ready = writers_.front(); writers_.pop_front(); if (ready != &w) { ready->status = status; ready->done = true; ready->cv.Signal(); } - if (ready == last_writer) break; + if (ready == last_writer) + break; } // Notify new head of write queue @@ -1276,11 +1254,11 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) { // REQUIRES: Writer list must be non-empty // REQUIRES: First writer must have a non-null batch -WriteBatch* DBImpl::BuildBatchGroup(Writer** last_writer) { +WriteBatch *DBImpl::BuildBatchGroup(Writer **last_writer) { mutex_.AssertHeld(); assert(!writers_.empty()); - Writer* first = writers_.front(); - WriteBatch* result = first->batch; + Writer *first = writers_.front(); + WriteBatch *result = first->batch; assert(result != nullptr); size_t size = WriteBatchInternal::ByteSize(first->batch); @@ -1288,16 +1266,17 @@ WriteBatch* DBImpl::BuildBatchGroup(Writer** last_writer) { // Allow the group to grow up to a maximum size, but if the // original write is small, limit the growth so we do not slow // down the small write too much. + // 允许组增长到最大大小,但如果原始写入很小,限制增长,这样我们就不会过多地减慢小写入。 size_t max_size = 1 << 20; - if (size <= (128<<10)) { - max_size = size + (128<<10); + if (size <= (128 << 10)) { + max_size = size + (128 << 10); } *last_writer = first; - std::deque::iterator iter = writers_.begin(); - ++iter; // Advance past "first" + std::deque::iterator iter = writers_.begin(); + ++iter; // Advance past "first" // 跳过first for (; iter != writers_.end(); ++iter) { - Writer* w = *iter; + Writer *w = *iter; if (w->sync && !first->sync) { // Do not include a sync write into a batch handled by a non-sync write. break; @@ -1313,13 +1292,14 @@ WriteBatch* DBImpl::BuildBatchGroup(Writer** last_writer) { // Append to *result if (result == first->batch) { // Switch to temporary batch instead of disturbing caller's batch + // 切换到临时批处理,而不是干扰调用者的批处理 result = tmp_batch_; assert(WriteBatchInternal::Count(result) == 0); WriteBatchInternal::Append(result, first->batch); } WriteBatchInternal::Append(result, w->batch); } - *last_writer = w; + *last_writer = w; // todo不清楚这里的last_writer的作用 } return result; } @@ -1336,26 +1316,32 @@ Status DBImpl::MakeRoomForWrite(bool force) { // Yield previous error s = bg_error_; break; - } else if ( - allow_delay && - versions_->NumLevelFiles(0) >= config::kL0_SlowdownWritesTrigger) { + } else if (allow_delay && versions_->NumLevelFiles(0) >= + config::kL0_SlowdownWritesTrigger) { // We are getting close to hitting a hard limit on the number of // L0 files. Rather than delaying a single write by several // seconds when we hit the hard limit, start delaying each // individual write by 1ms to reduce latency variance. Also, // this delay hands over some CPU to the compaction thread in // case it is sharing the same core as the writer. + /* + 我们正在接近达到L0文件数量的硬限制。 + 当我们达到硬限制时,不要将单个写入延迟几秒钟,而是开始将每个写入延迟1ms,以减少延迟差异。 + 此外,如果压缩线程与写入线程共享同一个内核,则此延迟将一些CPU交给压缩线程。 + */ mutex_.Unlock(); env_->SleepForMicroseconds(1000); - allow_delay = false; // Do not delay a single write more than once + allow_delay = false; // Do not delay a single write more than once mutex_.Lock(); } else if (!force && (mem_->ApproximateMemoryUsage() <= options_.write_buffer_size)) { // There is room in current memtable - break; + // 当前的memtable中有空间 + break; // 一般情况下,会在此处退出循环 } else if (imm_ != nullptr) { // We have filled up the current memtable, but the previous // one is still being compacted, so we wait. + // 我们已经填满了当前的memtable,但是之前的memtable还在被压缩,所以我们等待。 Log(options_.info_log, "Current memtable full; waiting...\n"); background_work_finished_signal_.Wait(); } else if (versions_->NumLevelFiles(0) >= config::kL0_StopWritesTrigger) { @@ -1363,13 +1349,20 @@ Status DBImpl::MakeRoomForWrite(bool force) { Log(options_.info_log, "Too many L0 files; waiting...\n"); background_work_finished_signal_.Wait(); } else { + // Attempt to switch to a new memtable and trigger compaction of old + /* + 1. 创建新的log文件 + 2. 尝试切换到新的memtable并触发旧memtable的压缩 + 3. 尝试开启后台线程去执行压缩 + */ assert(versions_->PrevLogNumber() == 0); uint64_t new_log_number = versions_->NewFileNumber(); - WritableFile* lfile = nullptr; + WritableFile *lfile = nullptr; s = env_->NewWritableFile(LogFileName(dbname_, new_log_number), &lfile); if (!s.ok()) { // Avoid chewing through file number space in a tight loop. + // 由于创建文件失败,故返回log_number,避免在紧密的循环中chewing文件编号空间。 versions_->ReuseFileNumber(new_log_number); break; } @@ -1382,20 +1375,21 @@ Status DBImpl::MakeRoomForWrite(bool force) { has_imm_.store(true, std::memory_order_release); mem_ = new MemTable(internal_comparator_); mem_->Ref(); - force = false; // Do not force another compaction if have room + force = false; // Do not force another compaction if have room MaybeScheduleCompaction(); } } return s; } -bool DBImpl::GetProperty(const Slice& property, std::string* value) { +bool DBImpl::GetProperty(const Slice &property, std::string *value) { value->clear(); MutexLock l(&mutex_); Slice in = property; Slice prefix("leveldb."); - if (!in.starts_with(prefix)) return false; + if (!in.starts_with(prefix)) + return false; in.remove_prefix(prefix.size()); if (in.starts_with("num-files-at-level")) { @@ -1416,21 +1410,16 @@ bool DBImpl::GetProperty(const Slice& property, std::string* value) { snprintf(buf, sizeof(buf), " Compactions\n" "Level Files Size(MB) Time(sec) Read(MB) Write(MB)\n" - "--------------------------------------------------\n" - ); + "--------------------------------------------------\n"); value->append(buf); for (int level = 0; level < config::kNumLevels; level++) { int files = versions_->NumLevelFiles(level); if (stats_[level].micros > 0 || files > 0) { - snprintf( - buf, sizeof(buf), - "%3d %8d %8.0f %9.0f %8.0f %9.0f\n", - level, - files, - versions_->NumLevelBytes(level) / 1048576.0, - stats_[level].micros / 1e6, - stats_[level].bytes_read / 1048576.0, - stats_[level].bytes_written / 1048576.0); + snprintf(buf, sizeof(buf), "%3d %8d %8.0f %9.0f %8.0f %9.0f\n", level, + files, versions_->NumLevelBytes(level) / 1048576.0, + stats_[level].micros / 1e6, + stats_[level].bytes_read / 1048576.0, + stats_[level].bytes_written / 1048576.0); value->append(buf); } } @@ -1456,11 +1445,9 @@ bool DBImpl::GetProperty(const Slice& property, std::string* value) { return false; } -void DBImpl::GetApproximateSizes( - const Range* range, int n, - uint64_t* sizes) { +void DBImpl::GetApproximateSizes(const Range *range, int n, uint64_t *sizes) { // TODO(opt): better implementation - Version* v; + Version *v; { MutexLock l(&mutex_); versions_->current()->Ref(); @@ -1484,34 +1471,34 @@ void DBImpl::GetApproximateSizes( // Default implementations of convenience methods that subclasses of DB // can call if they wish -Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) { +Status DB::Put(const WriteOptions &opt, const Slice &key, const Slice &value) { WriteBatch batch; batch.Put(key, value); return Write(opt, &batch); } -Status DB::Delete(const WriteOptions& opt, const Slice& key) { +Status DB::Delete(const WriteOptions &opt, const Slice &key) { WriteBatch batch; batch.Delete(key); return Write(opt, &batch); } -DB::~DB() { } +DB::~DB() {} -Status DB::Open(const Options& options, const std::string& dbname, - DB** dbptr) { +Status DB::Open(const Options &options, const std::string &dbname, DB **dbptr) { *dbptr = nullptr; - DBImpl* impl = new DBImpl(options, dbname); + DBImpl *impl = new DBImpl(options, dbname); impl->mutex_.Lock(); VersionEdit edit; // Recover handles create_if_missing, error_if_exists bool save_manifest = false; Status s = impl->Recover(&edit, &save_manifest); + // 表明第一次创建数据库,需要为数据库做一些初始化操作 if (s.ok() && impl->mem_ == nullptr) { // Create new log and a corresponding memtable. uint64_t new_log_number = impl->versions_->NewFileNumber(); - WritableFile* lfile; + WritableFile *lfile; s = options.env->NewWritableFile(LogFileName(dbname, new_log_number), &lfile); if (s.ok()) { @@ -1523,8 +1510,9 @@ Status DB::Open(const Options& options, const std::string& dbname, impl->mem_->Ref(); } } + // 此处的save_manifest应该是指 需要保存manifest if (s.ok() && save_manifest) { - edit.SetPrevLogNumber(0); // No older logs needed after recovery. + edit.SetPrevLogNumber(0); // No older logs needed after recovery. edit.SetLogNumber(impl->logfile_number_); s = impl->versions_->LogAndApply(&edit, &impl->mutex_); } @@ -1542,11 +1530,10 @@ Status DB::Open(const Options& options, const std::string& dbname, return s; } -Snapshot::~Snapshot() { -} +Snapshot::~Snapshot() {} -Status DestroyDB(const std::string& dbname, const Options& options) { - Env* env = options.env; +Status DestroyDB(const std::string &dbname, const Options &options) { + Env *env = options.env; std::vector filenames; Status result = env->GetChildren(dbname, &filenames); if (!result.ok()) { @@ -1554,7 +1541,7 @@ Status DestroyDB(const std::string& dbname, const Options& options) { return Status::OK(); } - FileLock* lock; + FileLock *lock; const std::string lockname = LockFileName(dbname); result = env->LockFile(lockname, &lock); if (result.ok()) { @@ -1562,18 +1549,18 @@ Status DestroyDB(const std::string& dbname, const Options& options) { FileType type; for (size_t i = 0; i < filenames.size(); i++) { if (ParseFileName(filenames[i], &number, &type) && - type != kDBLockFile) { // Lock file will be deleted at end + type != kDBLockFile) { // Lock file will be deleted at end Status del = env->DeleteFile(dbname + "/" + filenames[i]); if (result.ok() && !del.ok()) { result = del; } } } - env->UnlockFile(lock); // Ignore error since state is already gone + env->UnlockFile(lock); // Ignore error since state is already gone env->DeleteFile(lockname); - env->DeleteDir(dbname); // Ignore error in case dir contains other files + env->DeleteDir(dbname); // Ignore error in case dir contains other files } return result; } -} // namespace leveldb +} // namespace leveldb diff --git a/db/db_impl.h b/db/db_impl.h index ca00d42..78ec079 100644 --- a/db/db_impl.h +++ b/db/db_impl.h @@ -27,28 +27,28 @@ class VersionEdit; class VersionSet; class DBImpl : public DB { - public: - DBImpl(const Options& options, const std::string& dbname); +public: + DBImpl(const Options &options, const std::string &dbname); virtual ~DBImpl(); // Implementations of the DB interface - virtual Status Put(const WriteOptions&, const Slice& key, const Slice& value); - virtual Status Delete(const WriteOptions&, const Slice& key); - virtual Status Write(const WriteOptions& options, WriteBatch* updates); - virtual Status Get(const ReadOptions& options, - const Slice& key, - std::string* value); - virtual Iterator* NewIterator(const ReadOptions&); - virtual const Snapshot* GetSnapshot(); - virtual void ReleaseSnapshot(const Snapshot* snapshot); - virtual bool GetProperty(const Slice& property, std::string* value); - virtual void GetApproximateSizes(const Range* range, int n, uint64_t* sizes); - virtual void CompactRange(const Slice* begin, const Slice* end); + virtual Status Put(const WriteOptions &, const Slice &key, + const Slice &value); + virtual Status Delete(const WriteOptions &, const Slice &key); + virtual Status Write(const WriteOptions &options, WriteBatch *updates); + virtual Status Get(const ReadOptions &options, const Slice &key, + std::string *value); + virtual Iterator *NewIterator(const ReadOptions &); + virtual const Snapshot *GetSnapshot(); + virtual void ReleaseSnapshot(const Snapshot *snapshot); + virtual bool GetProperty(const Slice &property, std::string *value); + virtual void GetApproximateSizes(const Range *range, int n, uint64_t *sizes); + virtual void CompactRange(const Slice *begin, const Slice *end); // Extra methods (for testing) that are not in the public DB interface // Compact any files in the named level that overlap [*begin,*end] - void TEST_CompactRange(int level, const Slice* begin, const Slice* end); + void TEST_CompactRange(int level, const Slice *begin, const Slice *end); // Force current memtable contents to be compacted. Status TEST_CompactMemTable(); @@ -56,7 +56,7 @@ class DBImpl : public DB { // Return an internal iterator over the current state of the database. // The keys of this iterator are internal keys (see format.h). // The returned iterator should be deleted when no longer needed. - Iterator* TEST_NewInternalIterator(); + Iterator *TEST_NewInternalIterator(); // Return the maximum overlapping data (in bytes) at next level for any // file at a level >= 1. @@ -67,24 +67,28 @@ class DBImpl : public DB { // bytes. void RecordReadSample(Slice key); - private: +private: friend class DB; struct CompactionState; struct Writer; - Iterator* NewInternalIterator(const ReadOptions&, - SequenceNumber* latest_snapshot, - uint32_t* seed); + Iterator *NewInternalIterator(const ReadOptions &, + SequenceNumber *latest_snapshot, + uint32_t *seed); Status NewDB(); // Recover the descriptor from persistent storage. May do a significant // amount of work to recover recently logged updates. Any changes to // be made to the descriptor are added to *edit. - Status Recover(VersionEdit* edit, bool* save_manifest) + /** + * @brief 从持久存储恢复描述符。 + * 可能需要做大量的工作来恢复最近记录的更新。对描述符所做的任何更改都会添加到*edit中。 + */ + Status Recover(VersionEdit *edit, bool *save_manifest) EXCLUSIVE_LOCKS_REQUIRED(mutex_); - void MaybeIgnoreError(Status* s) const; + void MaybeIgnoreError(Status *s) const; // Delete any unneeded files and stale in-memory entries. void DeleteObsoleteFiles() EXCLUSIVE_LOCKS_REQUIRED(mutex_); @@ -94,99 +98,118 @@ class DBImpl : public DB { // Errors are recorded in bg_error_. void CompactMemTable() EXCLUSIVE_LOCKS_REQUIRED(mutex_); - Status RecoverLogFile(uint64_t log_number, bool last_log, bool* save_manifest, - VersionEdit* edit, SequenceNumber* max_sequence) + Status RecoverLogFile(uint64_t log_number, bool last_log, bool *save_manifest, + VersionEdit *edit, SequenceNumber *max_sequence) EXCLUSIVE_LOCKS_REQUIRED(mutex_); - Status WriteLevel0Table(MemTable* mem, VersionEdit* edit, Version* base) + /** + * @brief 将memtable内容写入到文件中 + * + * 将信息写到VersionEdit中 + */ + Status WriteLevel0Table(MemTable *mem, VersionEdit *edit, Version *base) EXCLUSIVE_LOCKS_REQUIRED(mutex_); Status MakeRoomForWrite(bool force /* compact even if there is room? */) EXCLUSIVE_LOCKS_REQUIRED(mutex_); - WriteBatch* BuildBatchGroup(Writer** last_writer) + WriteBatch *BuildBatchGroup(Writer **last_writer) EXCLUSIVE_LOCKS_REQUIRED(mutex_); - void RecordBackgroundError(const Status& s); + void RecordBackgroundError(const Status &s); void MaybeScheduleCompaction() EXCLUSIVE_LOCKS_REQUIRED(mutex_); - static void BGWork(void* db); + static void BGWork(void *db); void BackgroundCall(); void BackgroundCompaction() EXCLUSIVE_LOCKS_REQUIRED(mutex_); - void CleanupCompaction(CompactionState* compact) + void CleanupCompaction(CompactionState *compact) EXCLUSIVE_LOCKS_REQUIRED(mutex_); - Status DoCompactionWork(CompactionState* compact) + Status DoCompactionWork(CompactionState *compact) EXCLUSIVE_LOCKS_REQUIRED(mutex_); - Status OpenCompactionOutputFile(CompactionState* compact); - Status FinishCompactionOutputFile(CompactionState* compact, Iterator* input); - Status InstallCompactionResults(CompactionState* compact) + Status OpenCompactionOutputFile(CompactionState *compact); + Status FinishCompactionOutputFile(CompactionState *compact, Iterator *input); + Status InstallCompactionResults(CompactionState *compact) EXCLUSIVE_LOCKS_REQUIRED(mutex_); // Constant after construction - Env* const env_; + Env *const env_; const InternalKeyComparator internal_comparator_; const InternalFilterPolicy internal_filter_policy_; - const Options options_; // options_.comparator == &internal_comparator_ + const Options options_; // options_.comparator == &internal_comparator_ const bool owns_info_log_; const bool owns_cache_; const std::string dbname_; // table_cache_ provides its own synchronization - TableCache* const table_cache_; + TableCache *const table_cache_; // Lock over the persistent DB state. Non-null iff successfully acquired. - FileLock* db_lock_; + FileLock *db_lock_; // State below is protected by mutex_ port::Mutex mutex_; std::atomic shutting_down_; port::CondVar background_work_finished_signal_ GUARDED_BY(mutex_); - MemTable* mem_; - MemTable* imm_ GUARDED_BY(mutex_); // Memtable being compacted - std::atomic has_imm_; // So bg thread can detect non-null imm_ - WritableFile* logfile_; + MemTable *mem_; + MemTable *imm_ GUARDED_BY(mutex_); // Memtable being compacted + std::atomic has_imm_; // So bg thread can detect non-null imm_ + WritableFile *logfile_; uint64_t logfile_number_ GUARDED_BY(mutex_); - log::Writer* log_; - uint32_t seed_ GUARDED_BY(mutex_); // For sampling. + log::Writer *log_; + uint32_t seed_ GUARDED_BY(mutex_); // For sampling. // Queue of writers. - std::deque writers_ GUARDED_BY(mutex_); - WriteBatch* tmp_batch_ GUARDED_BY(mutex_); + std::deque writers_ GUARDED_BY(mutex_); + WriteBatch *tmp_batch_ GUARDED_BY(mutex_); SnapshotList snapshots_ GUARDED_BY(mutex_); // Set of table files to protect from deletion because they are // part of ongoing compactions. + + /** + * @brief 正在进行压缩的一组table文件 + * + * 防止删除 + */ std::set pending_outputs_ GUARDED_BY(mutex_); // Has a background compaction been scheduled or is running? + + /** + * @brief 是否已计划或正在运行后台压缩? + */ bool background_compaction_scheduled_ GUARDED_BY(mutex_); // Information for a manual compaction struct ManualCompaction { int level; bool done; - const InternalKey* begin; // null means beginning of key range - const InternalKey* end; // null means end of key range - InternalKey tmp_storage; // Used to keep track of compaction progress + const InternalKey *begin; // null means beginning of key range + const InternalKey *end; // null means end of key range + InternalKey tmp_storage; // Used to keep track of compaction progress }; - ManualCompaction* manual_compaction_ GUARDED_BY(mutex_); + ManualCompaction *manual_compaction_ GUARDED_BY(mutex_); - VersionSet* const versions_; + VersionSet *const versions_; // Have we encountered a background error in paranoid mode? Status bg_error_ GUARDED_BY(mutex_); // Per level compaction stats. stats_[level] stores the stats for // compactions that produced data for the specified "level". + /** + * @breif 每level compaction stats。 + * stats_[level]存储为指定“level”生成数据的压缩的统计信息。 + */ struct CompactionStats { int64_t micros; int64_t bytes_read; int64_t bytes_written; - CompactionStats() : micros(0), bytes_read(0), bytes_written(0) { } + CompactionStats() : micros(0), bytes_read(0), bytes_written(0) {} - void Add(const CompactionStats& c) { + void Add(const CompactionStats &c) { this->micros += c.micros; this->bytes_read += c.bytes_read; this->bytes_written += c.bytes_written; @@ -195,21 +218,21 @@ class DBImpl : public DB { CompactionStats stats_[config::kNumLevels] GUARDED_BY(mutex_); // No copying allowed - DBImpl(const DBImpl&); - void operator=(const DBImpl&); + DBImpl(const DBImpl &); + void operator=(const DBImpl &); - const Comparator* user_comparator() const { + const Comparator *user_comparator() const { return internal_comparator_.user_comparator(); } }; // Sanitize db options. The caller should delete result.info_log if // it is not equal to src.info_log. -Options SanitizeOptions(const std::string& db, - const InternalKeyComparator* icmp, - const InternalFilterPolicy* ipolicy, - const Options& src); +Options SanitizeOptions(const std::string &db, + const InternalKeyComparator *icmp, + const InternalFilterPolicy *ipolicy, + const Options &src); -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_DB_DB_IMPL_H_ +#endif // STORAGE_LEVELDB_DB_DB_IMPL_H_ diff --git a/db/db_iter.h b/db/db_iter.h index 262840e..c7c1417 100644 --- a/db/db_iter.h +++ b/db/db_iter.h @@ -16,6 +16,11 @@ class DBImpl; // Return a new iterator that converts internal keys (yielded by // "*internal_iter") that were live at the specified "sequence" number // into appropriate user keys. + +/** + * @brief + * 返回一个新的迭代器,该迭代器将在指定“sequence”编号处存在的内部键(由"*internal_iter"产生)转换为适当的用户键。 + */ Iterator* NewDBIterator(DBImpl* db, const Comparator* user_key_comparator, Iterator* internal_iter, diff --git a/db/dbformat.h b/db/dbformat.h index c4d9575..a8bf9fa 100644 --- a/db/dbformat.h +++ b/db/dbformat.h @@ -5,7 +5,6 @@ #ifndef STORAGE_LEVELDB_DB_DBFORMAT_H_ #define STORAGE_LEVELDB_DB_DBFORMAT_H_ -#include #include "leveldb/comparator.h" #include "leveldb/db.h" #include "leveldb/filter_policy.h" @@ -13,6 +12,7 @@ #include "leveldb/table_builder.h" #include "util/coding.h" #include "util/logging.h" +#include namespace leveldb { @@ -25,9 +25,16 @@ static const int kNumLevels = 7; static const int kL0_CompactionTrigger = 4; // Soft limit on number of level-0 files. We slow down writes at this point. + +/** + * @brief 0级文件数量软限制。此时我们减慢了写入速度。 + */ static const int kL0_SlowdownWritesTrigger = 8; // Maximum number of level-0 files. We stop writes at this point. +/** + * @brief 0级文件的最大数目。我们在这里停止写入。 + */ static const int kL0_StopWritesTrigger = 12; // Maximum level to which a new compacted memtable is pushed if it @@ -36,22 +43,30 @@ static const int kL0_StopWritesTrigger = 12; // expensive manifest file operations. We do not push all the way to // the largest level since that can generate a lot of wasted disk // space if the same key space is being repeatedly overwritten. + +/* +如果新的压缩后的memtable不会产生重叠,则将其推至的最高级别。 +我们尝试推到级别2,以避免相对昂贵的级别0=>1压缩,并避免一些昂贵的manifest文件操作。 +我们不会一直推到最大级别,因为如果重复覆盖相同的key space,可能会产生大量浪费的磁盘空间。 +*/ static const int kMaxMemCompactLevel = 2; // Approximate gap in bytes between samples of data read during iteration. static const int kReadBytesPeriod = 1048576; -} // namespace config +} // namespace config class InternalKey; // Value types encoded as the last component of internal keys. // DO NOT CHANGE THESE ENUM VALUES: they are embedded in the on-disk // data structures. -enum ValueType { - kTypeDeletion = 0x0, - kTypeValue = 0x1 -}; + +/** + * @brief 编码为内部键的最后一个组件的值类型。 + * @note 不要改变这些枚举值:它们嵌入在磁盘上的数据结构中。 + */ +enum ValueType { kTypeDeletion = 0x0, kTypeValue = 0x1 }; // kValueTypeForSeek defines the ValueType that should be passed when // constructing a ParsedInternalKey object for seeking to a particular // sequence number (since we sort sequence numbers in decreasing order @@ -64,36 +79,35 @@ typedef uint64_t SequenceNumber; // We leave eight bits empty at the bottom so a type and sequence# // can be packed together into 64-bits. -static const SequenceNumber kMaxSequenceNumber = - ((0x1ull << 56) - 1); +static const SequenceNumber kMaxSequenceNumber = ((0x1ull << 56) - 1); struct ParsedInternalKey { Slice user_key; SequenceNumber sequence; ValueType type; - ParsedInternalKey() { } // Intentionally left uninitialized (for speed) - ParsedInternalKey(const Slice& u, const SequenceNumber& seq, ValueType t) - : user_key(u), sequence(seq), type(t) { } + ParsedInternalKey() {} // Intentionally left uninitialized (for speed) + ParsedInternalKey(const Slice &u, const SequenceNumber &seq, ValueType t) + : user_key(u), sequence(seq), type(t) {} std::string DebugString() const; }; // Return the length of the encoding of "key". -inline size_t InternalKeyEncodingLength(const ParsedInternalKey& key) { +inline size_t InternalKeyEncodingLength(const ParsedInternalKey &key) { return key.user_key.size() + 8; } // Append the serialization of "key" to *result. -void AppendInternalKey(std::string* result, const ParsedInternalKey& key); +void AppendInternalKey(std::string *result, const ParsedInternalKey &key); // Attempt to parse an internal key from "internal_key". On success, // stores the parsed data in "*result", and returns true. // // On error, returns false, leaves "*result" in an undefined state. -bool ParseInternalKey(const Slice& internal_key, ParsedInternalKey* result); +bool ParseInternalKey(const Slice &internal_key, ParsedInternalKey *result); // Returns the user key portion of an internal key. -inline Slice ExtractUserKey(const Slice& internal_key) { +inline Slice ExtractUserKey(const Slice &internal_key) { assert(internal_key.size() >= 8); return Slice(internal_key.data(), internal_key.size() - 8); } @@ -101,46 +115,48 @@ inline Slice ExtractUserKey(const Slice& internal_key) { // A comparator for internal keys that uses a specified comparator for // the user key portion and breaks ties by decreasing sequence number. class InternalKeyComparator : public Comparator { - private: - const Comparator* user_comparator_; - public: - explicit InternalKeyComparator(const Comparator* c) : user_comparator_(c) { } - virtual const char* Name() const; - virtual int Compare(const Slice& a, const Slice& b) const; - virtual void FindShortestSeparator( - std::string* start, - const Slice& limit) const; - virtual void FindShortSuccessor(std::string* key) const; - - const Comparator* user_comparator() const { return user_comparator_; } - - int Compare(const InternalKey& a, const InternalKey& b) const; +private: + const Comparator *user_comparator_; + +public: + explicit InternalKeyComparator(const Comparator *c) : user_comparator_(c) {} + virtual const char *Name() const; + virtual int Compare(const Slice &a, const Slice &b) const; + virtual void FindShortestSeparator(std::string *start, + const Slice &limit) const; + virtual void FindShortSuccessor(std::string *key) const; + + const Comparator *user_comparator() const { return user_comparator_; } + + int Compare(const InternalKey &a, const InternalKey &b) const; }; // Filter policy wrapper that converts from internal keys to user keys class InternalFilterPolicy : public FilterPolicy { - private: - const FilterPolicy* const user_policy_; - public: - explicit InternalFilterPolicy(const FilterPolicy* p) : user_policy_(p) { } - virtual const char* Name() const; - virtual void CreateFilter(const Slice* keys, int n, std::string* dst) const; - virtual bool KeyMayMatch(const Slice& key, const Slice& filter) const; +private: + const FilterPolicy *const user_policy_; + +public: + explicit InternalFilterPolicy(const FilterPolicy *p) : user_policy_(p) {} + virtual const char *Name() const; + virtual void CreateFilter(const Slice *keys, int n, std::string *dst) const; + virtual bool KeyMayMatch(const Slice &key, const Slice &filter) const; }; // Modules in this directory should keep internal keys wrapped inside // the following class instead of plain strings so that we do not // incorrectly use string comparisons instead of an InternalKeyComparator. class InternalKey { - private: +private: std::string rep_; - public: - InternalKey() { } // Leave rep_ as empty to indicate it is invalid - InternalKey(const Slice& user_key, SequenceNumber s, ValueType t) { + +public: + InternalKey() {} // Leave rep_ as empty to indicate it is invalid + InternalKey(const Slice &user_key, SequenceNumber s, ValueType t) { AppendInternalKey(&rep_, ParsedInternalKey(user_key, s, t)); } - void DecodeFrom(const Slice& s) { rep_.assign(s.data(), s.size()); } + void DecodeFrom(const Slice &s) { rep_.assign(s.data(), s.size()); } Slice Encode() const { assert(!rep_.empty()); return rep_; @@ -148,7 +164,7 @@ class InternalKey { Slice user_key() const { return ExtractUserKey(rep_); } - void SetFrom(const ParsedInternalKey& p) { + void SetFrom(const ParsedInternalKey &p) { rep_.clear(); AppendInternalKey(&rep_, p); } @@ -158,15 +174,16 @@ class InternalKey { std::string DebugString() const; }; -inline int InternalKeyComparator::Compare( - const InternalKey& a, const InternalKey& b) const { +inline int InternalKeyComparator::Compare(const InternalKey &a, + const InternalKey &b) const { return Compare(a.Encode(), b.Encode()); } -inline bool ParseInternalKey(const Slice& internal_key, - ParsedInternalKey* result) { +inline bool ParseInternalKey(const Slice &internal_key, + ParsedInternalKey *result) { const size_t n = internal_key.size(); - if (n < 8) return false; + if (n < 8) + return false; uint64_t num = DecodeFixed64(internal_key.data() + n - 8); unsigned char c = num & 0xff; result->sequence = num >> 8; @@ -177,10 +194,10 @@ inline bool ParseInternalKey(const Slice& internal_key, // A helper class useful for DBImpl::Get() class LookupKey { - public: +public: // Initialize *this for looking up user_key at a snapshot with // the specified sequence number. - LookupKey(const Slice& user_key, SequenceNumber sequence); + LookupKey(const Slice &user_key, SequenceNumber sequence); ~LookupKey(); @@ -193,7 +210,7 @@ class LookupKey { // Return the user key Slice user_key() const { return Slice(kstart_, end_ - kstart_ - 8); } - private: +private: // We construct a char array of the form: // klength varint32 <-- start_ // userkey char[klength] <-- kstart_ @@ -201,20 +218,21 @@ class LookupKey { // <-- end_ // The array is a suitable MemTable key. // The suffix starting with "userkey" can be used as an InternalKey. - const char* start_; - const char* kstart_; - const char* end_; - char space_[200]; // Avoid allocation for short keys + const char *start_; + const char *kstart_; + const char *end_; + char space_[200]; // Avoid allocation for short keys // No copying allowed - LookupKey(const LookupKey&); - void operator=(const LookupKey&); + LookupKey(const LookupKey &); + void operator=(const LookupKey &); }; inline LookupKey::~LookupKey() { - if (start_ != space_) delete[] start_; + if (start_ != space_) + delete[] start_; } -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_DB_DBFORMAT_H_ +#endif // STORAGE_LEVELDB_DB_DBFORMAT_H_ diff --git a/db/log_writer.h b/db/log_writer.h index 9e7cc47..40fdfd9 100644 --- a/db/log_writer.h +++ b/db/log_writer.h @@ -21,11 +21,25 @@ class Writer { // Create a writer that will append data to "*dest". // "*dest" must be initially empty. // "*dest" must remain live while this Writer is in use. + + /** + * @brief 创建一个Writer,将数据追加到"dest"。 + * + * "dest"一开始必须为空。 + * 当这个Writer被使用时,“dest”必须保持活动状态。 + */ explicit Writer(WritableFile* dest); // Create a writer that will append data to "*dest". // "*dest" must have initial length "dest_length". // "*dest" must remain live while this Writer is in use. + + /** + * @brief 创建一个Writer,将数据追加到"dest"。 + + * “dest”必须具有初始长度“dest_length”。 + * 当这个Writer被使用时,“dest”必须保持活动状态。 + */ Writer(WritableFile* dest, uint64_t dest_length); ~Writer(); @@ -39,6 +53,10 @@ class Writer { // crc32c values for all supported record types. These are // pre-computed to reduce the overhead of computing the crc of the // record type stored in the header. + + /* + crc32c 值。 这些是预先计算的,以减少计算标头中存储的记录类型的 crc 的开销。 + */ uint32_t type_crc_[kMaxRecordType + 1]; Status EmitPhysicalRecord(RecordType type, const char* ptr, size_t length); diff --git a/db/memtable.h b/db/memtable.h index f2a6736..f625079 100644 --- a/db/memtable.h +++ b/db/memtable.h @@ -20,6 +20,10 @@ class MemTable { public: // MemTables are reference counted. The initial reference count // is zero and the caller must call Ref() at least once. + + /** + * @brief memtable是引用计数的。初始引用计数为零,调用者必须至少调用Ref()一次。 + */ explicit MemTable(const InternalKeyComparator& comparator); // Increase reference count. @@ -36,6 +40,11 @@ class MemTable { // Returns an estimate of the number of bytes of data in use by this // data structure. It is safe to call when MemTable is being modified. + + /** + * @brief + * 返回此数据结构正在使用的数据字节数的估计值。当MemTable被修改时调用它是安全的。 + */ size_t ApproximateMemoryUsage(); // Return an iterator that yields the contents of the memtable. @@ -44,11 +53,25 @@ class MemTable { // while the returned iterator is live. The keys returned by this // iterator are internal keys encoded by AppendInternalKey in the // db/format.{h,cc} module. + + /** + * @brief 返回一个迭代器,生成memtable的内容。 + * + * 调用者必须确保底层的MemTable在返回的迭代器处于活动状态时保持活动状态。 + * + * 这个迭代器返回的键是由db/format.h模块中的AppendInternalKey编码的内部键。 + */ Iterator* NewIterator(); // Add an entry into memtable that maps key to value at the // specified sequence number and with the specified type. // Typically value will be empty if type==kTypeDeletion. + + /** + * @brief 在memtable中添加一个表项,该表项将key映射为指定序列号和指定类型的值。 + * + * 如果type==kTypeDeletion, value通常为空。 + */ void Add(SequenceNumber seq, ValueType type, const Slice& key, const Slice& value); @@ -57,6 +80,13 @@ class MemTable { // If memtable contains a deletion for key, store a NotFound() error // in *status and return true. // Else, return false. + + /** + * @brief 如果memtable中包含key的值,将其存储在*value中并返回true。 + * + * 如果memtable包含delete for key,则在*status中存储NotFound()错误并返回true。 + * 否则,返回false。 + */ bool Get(const LookupKey& key, std::string* value, Status* s); private: diff --git a/db/skiplist.h b/db/skiplist.h index 7ac914b..95e850f 100644 --- a/db/skiplist.h +++ b/db/skiplist.h @@ -38,75 +38,114 @@ namespace leveldb { class Arena; -template -class SkipList { - private: +template class SkipList { +private: struct Node; - public: +public: // Create a new SkipList object that will use "cmp" for comparing keys, // and will allocate memory using "*arena". Objects allocated in the arena // must remain allocated for the lifetime of the skiplist object. - explicit SkipList(Comparator cmp, Arena* arena); + explicit SkipList(Comparator cmp, Arena *arena); // Insert key into the list. // REQUIRES: nothing that compares equal to key is currently in the list. - void Insert(const Key& key); + void Insert(const Key &key); // Returns true iff an entry that compares equal to key is in the list. - bool Contains(const Key& key) const; + + /** + * 如果列表中存在与key相等的项,则返回true。 + */ + bool Contains(const Key &key) const; // Iteration over the contents of a skip list + + /** + * 对skip list的内容进行迭代 + */ class Iterator { - public: + public: // Initialize an iterator over the specified list. // The returned iterator is not valid. - explicit Iterator(const SkipList* list); + + /** + * 初始化指定列表上的迭代器。 返回的迭代器无效。 + */ + explicit Iterator(const SkipList *list); // Returns true iff the iterator is positioned at a valid node. + + /** + * 如果迭代器位于有效节点,则返回true。 + */ bool Valid() const; // Returns the key at the current position. // REQUIRES: Valid() - const Key& key() const; + + /** + * 返回当前位置的键。 + */ + const Key &key() const; // Advances to the next position. // REQUIRES: Valid() + + /** + * 前进到下一个位置。 + */ void Next(); // Advances to the previous position. // REQUIRES: Valid() + + /** + * 前进到之前的位置。 + */ void Prev(); // Advance to the first entry with a key >= target - void Seek(const Key& target); + + /** + * 使用key前进到第一个条目(>= target) + */ + void Seek(const Key &target); // Position at the first entry in list. // Final state of iterator is Valid() iff list is not empty. + + /** + * 在列表的第一个条目的位置。 + */ void SeekToFirst(); // Position at the last entry in list. // Final state of iterator is Valid() iff list is not empty. + + /** + * 在列表的最后一个条目的位置。 + */ void SeekToLast(); - private: - const SkipList* list_; - Node* node_; + private: + const SkipList *list_; + Node *node_; // Intentionally copyable }; - private: +private: enum { kMaxHeight = 12 }; // Immutable after construction Comparator const compare_; - Arena* const arena_; // Arena used for allocations of nodes + Arena *const arena_; // Arena used for allocations of nodes - Node* const head_; + Node *const head_; // Modified only by Insert(). Read racily by readers, but stale // values are ok. - std::atomic max_height_; // Height of the entire list + std::atomic max_height_; // Height of the entire list inline int GetMaxHeight() const { return max_height_.load(std::memory_order_relaxed); @@ -115,105 +154,137 @@ class SkipList { // Read/written only by Insert(). Random rnd_; - Node* NewNode(const Key& key, int height); + Node *NewNode(const Key &key, int height); int RandomHeight(); - bool Equal(const Key& a, const Key& b) const { return (compare_(a, b) == 0); } + bool Equal(const Key &a, const Key &b) const { return (compare_(a, b) == 0); } // Return true if key is greater than the data stored in "n" - bool KeyIsAfterNode(const Key& key, Node* n) const; + + /** + * 如果key大于存储在“n”中的数据,则返回true + */ + bool KeyIsAfterNode(const Key &key, Node *n) const; // Return the earliest node that comes at or after key. // Return nullptr if there is no such node. // // If prev is non-null, fills prev[level] with pointer to previous // node at "level" for every level in [0..max_height_-1]. - Node* FindGreaterOrEqual(const Key& key, Node** prev) const; + + /** + * 返回键处或键后最早的节点。 + * 如果没有这样的节点,则返回nullptr。 + * + * 如果prev为非空,则在prev[level]中为[0.. + * max_height_1]中的每个级别填充指向“level”上一个节点的指针。 + */ + Node *FindGreaterOrEqual(const Key &key, Node **prev) const; // Return the latest node with a key < key. // Return head_ if there is no such node. - Node* FindLessThan(const Key& key) const; + + /** + * 返回小于给定key的最新节点。 + * 如果没有这样的节点,返回head_。 + */ + Node *FindLessThan(const Key &key) const; // Return the last node in the list. // Return head_ if list is empty. - Node* FindLast() const; + + /** + * 返回列表中的最后一个节点。 + * 如果列表为空,返回head_。 + */ + Node *FindLast() const; // No copying allowed - SkipList(const SkipList&); - void operator=(const SkipList&); + SkipList(const SkipList &); + void operator=(const SkipList &); }; // Implementation details follow -template +template struct SkipList::Node { - explicit Node(const Key& k) : key(k) { } + explicit Node(const Key &k) : key(k) {} Key const key; // Accessors/mutators for links. Wrapped in methods so we can // add the appropriate barriers as necessary. - Node* Next(int n) { + + /** + * links的访问器/变量。封装在方法中,以便我们可以根据需要添加适当的屏障。 + */ + Node *Next(int n) { assert(n >= 0); // Use an 'acquire load' so that we observe a fully initialized // version of the returned Node. return next_[n].load(std::memory_order_acquire); } - void SetNext(int n, Node* x) { + void SetNext(int n, Node *x) { assert(n >= 0); // Use a 'release store' so that anybody who reads through this // pointer observes a fully initialized version of the inserted node. + + // 使用'release + // store',这样任何读取该指针的人都会看到插入节点的完全初始化版本。 + next_[n].store(x, std::memory_order_release); } // No-barrier variants that can be safely used in a few locations. - Node* NoBarrier_Next(int n) { + Node *NoBarrier_Next(int n) { assert(n >= 0); return next_[n].load(std::memory_order_relaxed); } - void NoBarrier_SetNext(int n, Node* x) { + void NoBarrier_SetNext(int n, Node *x) { assert(n >= 0); next_[n].store(x, std::memory_order_relaxed); } - private: +private: // Array of length equal to the node height. next_[0] is lowest level link. - std::atomic next_[1]; + // 长度等于节点高度的数组。Next_[0]为最低级别链路。 + std::atomic next_[1]; }; -template -typename SkipList::Node* -SkipList::NewNode(const Key& key, int height) { - char* const node_memory = arena_->AllocateAligned( - sizeof(Node) + sizeof(std::atomic) * (height - 1)); +template +typename SkipList::Node * +SkipList::NewNode(const Key &key, int height) { + char *const node_memory = arena_->AllocateAligned( + sizeof(Node) + sizeof(std::atomic) * (height - 1)); return new (node_memory) Node(key); } -template -inline SkipList::Iterator::Iterator(const SkipList* list) { +template +inline SkipList::Iterator::Iterator(const SkipList *list) { list_ = list; node_ = nullptr; } -template +template inline bool SkipList::Iterator::Valid() const { return node_ != nullptr; } -template -inline const Key& SkipList::Iterator::key() const { +template +inline const Key &SkipList::Iterator::key() const { assert(Valid()); return node_->key; } -template +template inline void SkipList::Iterator::Next() { assert(Valid()); node_ = node_->Next(0); } -template +template inline void SkipList::Iterator::Prev() { // Instead of using explicit "prev" links, we just search for the // last node that falls before key. + // 不使用明确的“prev” links,我们只是搜索在关键字之前的最后一个节点。 assert(Valid()); node_ = list_->FindLessThan(node_->key); if (node_ == list_->head_) { @@ -221,17 +292,17 @@ inline void SkipList::Iterator::Prev() { } } -template -inline void SkipList::Iterator::Seek(const Key& target) { +template +inline void SkipList::Iterator::Seek(const Key &target) { node_ = list_->FindGreaterOrEqual(target, nullptr); } -template +template inline void SkipList::Iterator::SeekToFirst() { node_ = list_->head_->Next(0); } -template +template inline void SkipList::Iterator::SeekToLast() { node_ = list_->FindLast(); if (node_ == list_->head_) { @@ -239,7 +310,7 @@ inline void SkipList::Iterator::SeekToLast() { } } -template +template int SkipList::RandomHeight() { // Increase height with probability 1 in kBranching static const unsigned int kBranching = 4; @@ -252,25 +323,27 @@ int SkipList::RandomHeight() { return height; } -template -bool SkipList::KeyIsAfterNode(const Key& key, Node* n) const { +template +bool SkipList::KeyIsAfterNode(const Key &key, Node *n) const { // null n is considered infinite + // null被认为是无限的 return (n != nullptr) && (compare_(n->key, key) < 0); } -template -typename SkipList::Node* -SkipList::FindGreaterOrEqual(const Key& key, - Node** prev) const { - Node* x = head_; +template +typename SkipList::Node * +SkipList::FindGreaterOrEqual(const Key &key, + Node **prev) const { + Node *x = head_; int level = GetMaxHeight() - 1; while (true) { - Node* next = x->Next(level); + Node *next = x->Next(level); if (KeyIsAfterNode(key, next)) { // Keep searching in this list x = next; } else { - if (prev != nullptr) prev[level] = x; + if (prev != nullptr) + prev[level] = x; if (level == 0) { return next; } else { @@ -281,14 +354,14 @@ SkipList::FindGreaterOrEqual(const Key& key, } } -template -typename SkipList::Node* -SkipList::FindLessThan(const Key& key) const { - Node* x = head_; +template +typename SkipList::Node * +SkipList::FindLessThan(const Key &key) const { + Node *x = head_; int level = GetMaxHeight() - 1; while (true) { assert(x == head_ || compare_(x->key, key) < 0); - Node* next = x->Next(level); + Node *next = x->Next(level); if (next == nullptr || compare_(next->key, key) >= 0) { if (level == 0) { return x; @@ -302,13 +375,13 @@ SkipList::FindLessThan(const Key& key) const { } } -template -typename SkipList::Node* SkipList::FindLast() - const { - Node* x = head_; +template +typename SkipList::Node * +SkipList::FindLast() const { + Node *x = head_; int level = GetMaxHeight() - 1; while (true) { - Node* next = x->Next(level); + Node *next = x->Next(level); if (next == nullptr) { if (level == 0) { return x; @@ -322,24 +395,22 @@ typename SkipList::Node* SkipList::FindLast() } } -template -SkipList::SkipList(Comparator cmp, Arena* arena) - : compare_(cmp), - arena_(arena), - head_(NewNode(0 /* any key will do */, kMaxHeight)), - max_height_(1), +template +SkipList::SkipList(Comparator cmp, Arena *arena) + : compare_(cmp), arena_(arena), + head_(NewNode(0 /* any key will do */, kMaxHeight)), max_height_(1), rnd_(0xdeadbeef) { for (int i = 0; i < kMaxHeight; i++) { head_->SetNext(i, nullptr); } } -template -void SkipList::Insert(const Key& key) { +template +void SkipList::Insert(const Key &key) { // TODO(opt): We can use a barrier-free variant of FindGreaterOrEqual() // here since Insert() is externally synchronized. - Node* prev[kMaxHeight]; - Node* x = FindGreaterOrEqual(key, prev); + Node *prev[kMaxHeight]; + Node *x = FindGreaterOrEqual(key, prev); // Our data structure does not allow duplicate insertion assert(x == nullptr || !Equal(key, x->key)); @@ -356,6 +427,12 @@ void SkipList::Insert(const Key& key) { // the loop below. In the former case the reader will // immediately drop to the next level since nullptr sorts after all // keys. In the latter case the reader will use the new node. + + // 可以在不与并发读取器进行任何同步的情况下更改max_height_。 + // 观察到 max_height_ 的新值的并发读取器将看到来自head_ (nullptr) + // 的新级别指针的旧值, 或者在下面的循环中设置的新值。 + // 在前一种情况下,读取器将立即下降到下一个级别, 因为 nullptr + // 在所有键之后排序。 在后一种情况下,读取器将使用新节点。 max_height_.store(height, std::memory_order_relaxed); } @@ -363,14 +440,17 @@ void SkipList::Insert(const Key& key) { for (int i = 0; i < height; i++) { // NoBarrier_SetNext() suffices since we will add a barrier when // we publish a pointer to "x" in prev[i]. + + // NoBarrier_SetNext() 就足够了,因为我们在 prev[i] + // 中发布指向“x”的指针时会添加一个障碍。 x->NoBarrier_SetNext(i, prev[i]->NoBarrier_Next(i)); prev[i]->SetNext(i, x); } } -template -bool SkipList::Contains(const Key& key) const { - Node* x = FindGreaterOrEqual(key, nullptr); +template +bool SkipList::Contains(const Key &key) const { + Node *x = FindGreaterOrEqual(key, nullptr); if (x != nullptr && Equal(key, x->key)) { return true; } else { @@ -378,6 +458,6 @@ bool SkipList::Contains(const Key& key) const { } } -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_DB_SKIPLIST_H_ +#endif // STORAGE_LEVELDB_DB_SKIPLIST_H_ diff --git a/db/snapshot.h b/db/snapshot.h index c43d9f9..7fee8a7 100644 --- a/db/snapshot.h +++ b/db/snapshot.h @@ -70,6 +70,14 @@ class SnapshotList { // The snapshot pointer should not be const, because its memory is // deallocated. However, that would force us to change DB::ReleaseSnapshot(), // which is in the API, and currently takes a const Snapshot. + + /** + * 从列表中删除SnapshotImpl。 + * + * 该快照必须是通过调用该列表中的New()创建的。 + * + * 快照指针不应该是const,因为它的内存被释放了。然而,这将迫使我们改变DB::ReleaseSnapshot(),它在API中,目前使用const快照。 + */ void Delete(const SnapshotImpl* snapshot) { #if !defined(NDEBUG) assert(snapshot->list_ == this); diff --git a/db/table_cache.h b/db/table_cache.h index ae8bee5..316ccdc 100644 --- a/db/table_cache.h +++ b/db/table_cache.h @@ -7,20 +7,20 @@ #ifndef STORAGE_LEVELDB_DB_TABLE_CACHE_H_ #define STORAGE_LEVELDB_DB_TABLE_CACHE_H_ -#include -#include #include "db/dbformat.h" #include "leveldb/cache.h" #include "leveldb/table.h" #include "port/port.h" +#include +#include namespace leveldb { class Env; class TableCache { - public: - TableCache(const std::string& dbname, const Options& options, int entries); +public: + TableCache(const std::string &dbname, const Options &options, int entries); ~TableCache(); // Return an iterator for the specified file number (the corresponding @@ -30,32 +30,41 @@ class TableCache { // underlies the returned iterator. The returned "*tableptr" object is owned // by the cache and should not be deleted, and is valid for as long as the // returned iterator is live. - Iterator* NewIterator(const ReadOptions& options, - uint64_t file_number, - uint64_t file_size, - Table** tableptr = nullptr); + + /** + * @brief 返回指定文件号的迭代器(对应的文件长度必须正好是"file_size"字节)。 + * + * 如果"tableptr"非空,也设置"*tableptr"指向返回的迭代器底层的Table对象, + * 如果没有返回的迭代器底层的Table对象,则指向nullptr。 + * + * 返回的“*tableptr”对象归缓存所有,不应该被删除,只要返回的迭代器是活跃的,它就有效。 + */ + Iterator *NewIterator(const ReadOptions &options, uint64_t file_number, + uint64_t file_size, Table **tableptr = nullptr); // If a seek to internal key "k" in specified file finds an entry, // call (*handle_result)(arg, found_key, found_value). - Status Get(const ReadOptions& options, - uint64_t file_number, - uint64_t file_size, - const Slice& k, - void* arg, - void (*handle_result)(void*, const Slice&, const Slice&)); + + /** + * @brief 如果查找指定文件中的 internal key + * "k"找到了一个条目,则调用(*handle_result)(arg, found_key, found_value)。 + */ + Status Get(const ReadOptions &options, uint64_t file_number, + uint64_t file_size, const Slice &k, void *arg, + void (*handle_result)(void *, const Slice &, const Slice &)); // Evict any entry for the specified file number void Evict(uint64_t file_number); - private: - Env* const env_; +private: + Env *const env_; const std::string dbname_; - const Options& options_; - Cache* cache_; + const Options &options_; + Cache *cache_; - Status FindTable(uint64_t file_number, uint64_t file_size, Cache::Handle**); + Status FindTable(uint64_t file_number, uint64_t file_size, Cache::Handle **); }; -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_DB_TABLE_CACHE_H_ +#endif // STORAGE_LEVELDB_DB_TABLE_CACHE_H_ diff --git a/db/version_edit.h b/db/version_edit.h index eaef77b..0262193 100644 --- a/db/version_edit.h +++ b/db/version_edit.h @@ -59,6 +59,14 @@ class VersionEdit { // Add the specified file at the specified number. // REQUIRES: This version has not been saved (see VersionSet::SaveTo) // REQUIRES: "smallest" and "largest" are smallest and largest keys in file + + /** + * 添加 信息 + * + * 要求: 此版本未被保存(参见VersionSet::SaveTo) + * + * 要求: "smallest"和 "largest"是文件中最小和最大的key + */ void AddFile(int level, uint64_t file, uint64_t file_size, const InternalKey& smallest, diff --git a/db/version_set.h b/db/version_set.h index 0beae4d..cb4d814 100644 --- a/db/version_set.h +++ b/db/version_set.h @@ -15,17 +15,19 @@ #ifndef STORAGE_LEVELDB_DB_VERSION_SET_H_ #define STORAGE_LEVELDB_DB_VERSION_SET_H_ -#include -#include -#include #include "db/dbformat.h" #include "db/version_edit.h" #include "port/port.h" #include "port/thread_annotations.h" +#include +#include +#include namespace leveldb { -namespace log { class Writer; } +namespace log { +class Writer; +} class Compaction; class Iterator; @@ -39,9 +41,8 @@ class WritableFile; // Return the smallest index i such that files[i]->largest >= key. // Return files.size() if there is no such file. // REQUIRES: "files" contains a sorted list of non-overlapping files. -int FindFile(const InternalKeyComparator& icmp, - const std::vector& files, - const Slice& key); +int FindFile(const InternalKeyComparator &icmp, + const std::vector &files, const Slice &key); // Returns true iff some file in "files" overlaps the user key range // [*smallest,*largest]. @@ -49,33 +50,33 @@ int FindFile(const InternalKeyComparator& icmp, // largest==nullptr represents a key largest than all keys in the DB. // REQUIRES: If disjoint_sorted_files, files[] contains disjoint ranges // in sorted order. -bool SomeFileOverlapsRange(const InternalKeyComparator& icmp, +bool SomeFileOverlapsRange(const InternalKeyComparator &icmp, bool disjoint_sorted_files, - const std::vector& files, - const Slice* smallest_user_key, - const Slice* largest_user_key); + const std::vector &files, + const Slice *smallest_user_key, + const Slice *largest_user_key); class Version { - public: +public: // Append to *iters a sequence of iterators that will // yield the contents of this Version when merged together. // REQUIRES: This version has been saved (see VersionSet::SaveTo) - void AddIterators(const ReadOptions&, std::vector* iters); + void AddIterators(const ReadOptions &, std::vector *iters); // Lookup the value for key. If found, store it in *val and // return OK. Else return a non-OK status. Fills *stats. // REQUIRES: lock is not held struct GetStats { - FileMetaData* seek_file; + FileMetaData *seek_file; int seek_file_level; }; - Status Get(const ReadOptions&, const LookupKey& key, std::string* val, - GetStats* stats); + Status Get(const ReadOptions &, const LookupKey &key, std::string *val, + GetStats *stats); // Adds "stats" into the current state. Returns true if a new // compaction may need to be triggered, false otherwise. // REQUIRES: lock is held - bool UpdateStats(const GetStats& stats); + bool UpdateStats(const GetStats &stats); // Record a sample of bytes read at the specified internal key. // Samples are taken approximately once every config::kReadBytesPeriod @@ -90,54 +91,52 @@ class Version { void GetOverlappingInputs( int level, - const InternalKey* begin, // nullptr means before all keys - const InternalKey* end, // nullptr means after all keys - std::vector* inputs); + const InternalKey *begin, // nullptr means before all keys + const InternalKey *end, // nullptr means after all keys + std::vector *inputs); // Returns true iff some file in the specified level overlaps // some part of [*smallest_user_key,*largest_user_key]. // smallest_user_key==nullptr represents a key smaller than all the DB's keys. // largest_user_key==nullptr represents a key largest than all the DB's keys. - bool OverlapInLevel(int level, - const Slice* smallest_user_key, - const Slice* largest_user_key); + bool OverlapInLevel(int level, const Slice *smallest_user_key, + const Slice *largest_user_key); // Return the level at which we should place a new memtable compaction // result that covers the range [smallest_user_key,largest_user_key]. - int PickLevelForMemTableOutput(const Slice& smallest_user_key, - const Slice& largest_user_key); + int PickLevelForMemTableOutput(const Slice &smallest_user_key, + const Slice &largest_user_key); int NumFiles(int level) const { return files_[level].size(); } // Return a human readable string that describes this version's contents. std::string DebugString() const; - private: +private: friend class Compaction; friend class VersionSet; class LevelFileNumIterator; - Iterator* NewConcatenatingIterator(const ReadOptions&, int level) const; + Iterator *NewConcatenatingIterator(const ReadOptions &, int level) const; // Call func(arg, level, f) for every file that overlaps user_key in // order from newest to oldest. If an invocation of func returns // false, makes no more calls. // // REQUIRES: user portion of internal_key == user_key. - void ForEachOverlapping(Slice user_key, Slice internal_key, - void* arg, - bool (*func)(void*, int, FileMetaData*)); + void ForEachOverlapping(Slice user_key, Slice internal_key, void *arg, + bool (*func)(void *, int, FileMetaData *)); - VersionSet* vset_; // VersionSet to which this Version belongs - Version* next_; // Next version in linked list - Version* prev_; // Previous version in linked list - int refs_; // Number of live refs to this version + VersionSet *vset_; // VersionSet to which this Version belongs + Version *next_; // Next version in linked list + Version *prev_; // Previous version in linked list + int refs_; // Number of live refs to this version // List of files per level - std::vector files_[config::kNumLevels]; + std::vector files_[config::kNumLevels]; // Next file to compact based on seek stats. - FileMetaData* file_to_compact_; + FileMetaData *file_to_compact_; int file_to_compact_level_; // Level that should be compacted next and its compaction score. @@ -146,27 +145,22 @@ class Version { double compaction_score_; int compaction_level_; - explicit Version(VersionSet* vset) + explicit Version(VersionSet *vset) : vset_(vset), next_(this), prev_(this), refs_(0), - file_to_compact_(nullptr), - file_to_compact_level_(-1), - compaction_score_(-1), - compaction_level_(-1) { - } + file_to_compact_(nullptr), file_to_compact_level_(-1), + compaction_score_(-1), compaction_level_(-1) {} ~Version(); // No copying allowed - Version(const Version&); - void operator=(const Version&); + Version(const Version &); + void operator=(const Version &); }; class VersionSet { - public: - VersionSet(const std::string& dbname, - const Options* options, - TableCache* table_cache, - const InternalKeyComparator*); +public: + VersionSet(const std::string &dbname, const Options *options, + TableCache *table_cache, const InternalKeyComparator *); ~VersionSet(); // Apply *edit to the current version to form a new descriptor that @@ -174,24 +168,48 @@ class VersionSet { // current version. Will release *mu while actually writing to the file. // REQUIRES: *mu is held on entry. // REQUIRES: no other thread concurrently calls LogAndApply() - Status LogAndApply(VersionEdit* edit, port::Mutex* mu) + /** + * 对当前版本应用*edit以形成一个新的描述符,该描述符既保存为持久状态,又作为新的当前版本安装。 + * 将在实际写入文件时释放*mu。 + * + * 要求: *mu在输入时被保留。 + * 要求: 没有其他线程并发调用LogAndApply() + */ + Status LogAndApply(VersionEdit *edit, port::Mutex *mu) EXCLUSIVE_LOCKS_REQUIRED(mu); // Recover the last saved descriptor from persistent storage. + /** + * 从持久存储恢复最后保存的描述符。 + */ Status Recover(bool *save_manifest); // Return the current version. - Version* current() const { return current_; } + /** + * 返回当前版本。 + */ + Version *current() const { return current_; } // Return the current manifest file number + /** + * 返回当前manifest文件号 + */ uint64_t ManifestFileNumber() const { return manifest_file_number_; } // Allocate and return a new file number + /** + * 分配并返回一个新的文件号 + */ uint64_t NewFileNumber() { return next_file_number_++; } // Arrange to reuse "file_number" unless a newer file number has // already been allocated. // REQUIRES: "file_number" was returned by a call to NewFileNumber(). + /** + * 安排重用“file_number”,除非已经分配了新的文件号。 + * + * require: "file_number"是通过调用NewFileNumber()返回的。 + */ void ReuseFileNumber(uint64_t file_number) { if (next_file_number_ == file_number + 1) { next_file_number_ = file_number; @@ -199,129 +217,203 @@ class VersionSet { } // Return the number of Table files at the specified level. + /** + * 返回指定级别的Table文件数量。 + */ int NumLevelFiles(int level) const; // Return the combined file size of all files at the specified level. + /** + * 返回指定级别上所有文件的总文件大小。 + */ int64_t NumLevelBytes(int level) const; // Return the last sequence number. + /** + * return the last sequence number + */ uint64_t LastSequence() const { return last_sequence_; } // Set the last sequence number to s. + /** + * 设置s为the last sequence number to s + */ void SetLastSequence(uint64_t s) { assert(s >= last_sequence_); last_sequence_ = s; } // Mark the specified file number as used. + /** + * 将指定的文件编号标记为已使用。 + */ void MarkFileNumberUsed(uint64_t number); // Return the current log file number. + /** + * 返回当前日志文件号。 + */ uint64_t LogNumber() const { return log_number_; } // Return the log file number for the log file that is currently // being compacted, or zero if there is no such log file. + /** + * 返回当前正在压缩的日志文件的日志文件号,如果没有这样的日志文件,则返回零。 + */ uint64_t PrevLogNumber() const { return prev_log_number_; } // Pick level and inputs for a new compaction. // Returns nullptr if there is no compaction to be done. // Otherwise returns a pointer to a heap-allocated object that // describes the compaction. Caller should delete the result. - Compaction* PickCompaction(); + /** + * 为new compaction 选择level和inputs + * 如果不进行压缩,则返回nullptr。 + * 否则返回指向描述压缩的堆分配对象的指针。 + * + * @note 调用者负责删除返回结果。 + */ + Compaction *PickCompaction(); // Return a compaction object for compacting the range [begin,end] in // the specified level. Returns nullptr if there is nothing in that // level that overlaps the specified range. Caller should delete // the result. - Compaction* CompactRange( - int level, - const InternalKey* begin, - const InternalKey* end); + /** + * 返回一个压缩对象,用于在指定级别压缩范围[begin,end]。 + * + * 如果该级别中没有任何内容与指定范围重叠,则返回nullptr。 + * + * @note 调用者负责删除返回结果。 + */ + Compaction *CompactRange(int level, const InternalKey *begin, + const InternalKey *end); // Return the maximum overlapping data (in bytes) at next level for any // file at a level >= 1. + /** + * 对于level >= 1的任何文件,返回next level的最大重叠数据(以字节为单位)。 + */ int64_t MaxNextLevelOverlappingBytes(); // Create an iterator that reads over the compaction inputs for "*c". // The caller should delete the iterator when no longer needed. - Iterator* MakeInputIterator(Compaction* c); + /** + * 创建一个迭代器,读取“*c”的压缩输入。 + * + * @note 当不再需要迭代器时,调用者应该删除该迭代器。 + */ + Iterator *MakeInputIterator(Compaction *c); // Returns true iff some level needs a compaction. + /** + * 如果某个级别需要压缩,则返回true。 + */ bool NeedsCompaction() const { - Version* v = current_; + Version *v = current_; return (v->compaction_score_ >= 1) || (v->file_to_compact_ != nullptr); } // Add all files listed in any live version to *live. // May also mutate some internal state. - void AddLiveFiles(std::set* live); + /** + * @brief 将VersionSet中每个Version的每个level的每个文件(number)添加到live中 + */ + void AddLiveFiles(std::set *live); // Return the approximate offset in the database of the data for // "key" as of version "v". - uint64_t ApproximateOffsetOf(Version* v, const InternalKey& key); + /** + * 返回版本“v”的“key”数据在数据库中的近似偏移量。 + */ + uint64_t ApproximateOffsetOf(Version *v, const InternalKey &key); // Return a human-readable short (single-line) summary of the number // of files per level. Uses *scratch as backing store. + /** + * 返回一个人类可读的简短(单行)的每层(level)文件数量摘要。使用*scratch作为后备存储。 + */ struct LevelSummaryStorage { char buffer[100]; }; - const char* LevelSummary(LevelSummaryStorage* scratch) const; + const char *LevelSummary(LevelSummaryStorage *scratch) const; - private: +private: class Builder; friend class Compaction; friend class Version; - bool ReuseManifest(const std::string& dscname, const std::string& dscbase); + bool ReuseManifest(const std::string &dscname, const std::string &dscbase); - void Finalize(Version* v); + /** + * @brief 为version计算compaction_level_, compaction_score_ + * + * 对于level0:根据文件个数计算 + * + * 对于其他level:根据bytes大小计算 + * + */ + void Finalize(Version *v); - void GetRange(const std::vector& inputs, - InternalKey* smallest, - InternalKey* largest); + void GetRange(const std::vector &inputs, + InternalKey *smallest, InternalKey *largest); - void GetRange2(const std::vector& inputs1, - const std::vector& inputs2, - InternalKey* smallest, - InternalKey* largest); + void GetRange2(const std::vector &inputs1, + const std::vector &inputs2, + InternalKey *smallest, InternalKey *largest); - void SetupOtherInputs(Compaction* c); + void SetupOtherInputs(Compaction *c); // Save current contents to *log - Status WriteSnapshot(log::Writer* log); - - void AppendVersion(Version* v); - - Env* const env_; + /** + * @brief 将当前内容保存到log + */ + Status WriteSnapshot(log::Writer *log); + + /** + * @brief 将Version追加到VersionSet中(头部追加) + * + * 将current_ 设置为v + */ + void AppendVersion(Version *v); + + Env *const env_; const std::string dbname_; - const Options* const options_; - TableCache* const table_cache_; + const Options *const options_; + TableCache *const table_cache_; const InternalKeyComparator icmp_; uint64_t next_file_number_; uint64_t manifest_file_number_; uint64_t last_sequence_; uint64_t log_number_; - uint64_t prev_log_number_; // 0 or backing store for memtable being compacted + + // 0 或正在压缩的 memtable 的后备存储 + uint64_t prev_log_number_; // 0 or backing store for memtable being compacted // Opened lazily - WritableFile* descriptor_file_; - log::Writer* descriptor_log_; - Version dummy_versions_; // Head of circular doubly-linked list of versions. - Version* current_; // == dummy_versions_.prev_ + WritableFile *descriptor_file_; + log::Writer *descriptor_log_; + Version dummy_versions_; // Head of circular doubly-linked list of versions. + Version *current_; // == dummy_versions_.prev_ // Per-level key at which the next compaction at that level should start. // Either an empty string, or a valid InternalKey. + + /* + 每个级别的键,该级别的下一个压缩应该从该键开始。 + 一个空字符串或一个有效的InternalKey。 + */ std::string compact_pointer_[config::kNumLevels]; // No copying allowed - VersionSet(const VersionSet&); - void operator=(const VersionSet&); + VersionSet(const VersionSet &); + void operator=(const VersionSet &); }; // A Compaction encapsulates information about a compaction. class Compaction { - public: +public: ~Compaction(); // Return the level that is being compacted. Inputs from "level" @@ -330,13 +422,13 @@ class Compaction { // Return the object that holds the edits to the descriptor done // by this compaction. - VersionEdit* edit() { return &edit_; } + VersionEdit *edit() { return &edit_; } // "which" must be either 0 or 1 int num_input_files(int which) const { return inputs_[which].size(); } // Return the ith input file at "level()+which" ("which" must be 0 or 1). - FileMetaData* input(int which, int i) const { return inputs_[which][i]; } + FileMetaData *input(int which, int i) const { return inputs_[which][i]; } // Maximum size of files to build during this compaction. uint64_t MaxOutputFileSize() const { return max_output_file_size_; } @@ -346,42 +438,42 @@ class Compaction { bool IsTrivialMove() const; // Add all inputs to this compaction as delete operations to *edit. - void AddInputDeletions(VersionEdit* edit); + void AddInputDeletions(VersionEdit *edit); // Returns true if the information we have available guarantees that // the compaction is producing data in "level+1" for which no data exists // in levels greater than "level+1". - bool IsBaseLevelForKey(const Slice& user_key); + bool IsBaseLevelForKey(const Slice &user_key); // Returns true iff we should stop building the current output // before processing "internal_key". - bool ShouldStopBefore(const Slice& internal_key); + bool ShouldStopBefore(const Slice &internal_key); // Release the input version for the compaction, once the compaction // is successful. void ReleaseInputs(); - private: +private: friend class Version; friend class VersionSet; - Compaction(const Options* options, int level); + Compaction(const Options *options, int level); int level_; uint64_t max_output_file_size_; - Version* input_version_; + Version *input_version_; VersionEdit edit_; // Each compaction reads inputs from "level_" and "level_+1" - std::vector inputs_[2]; // The two sets of inputs + std::vector inputs_[2]; // The two sets of inputs // State used to check for number of overlapping grandparent files // (parent == level_ + 1, grandparent == level_ + 2) - std::vector grandparents_; - size_t grandparent_index_; // Index in grandparent_starts_ - bool seen_key_; // Some output key has been seen - int64_t overlapped_bytes_; // Bytes of overlap between current output - // and grandparent files + std::vector grandparents_; + size_t grandparent_index_; // Index in grandparent_starts_ + bool seen_key_; // Some output key has been seen + int64_t overlapped_bytes_; // Bytes of overlap between current output + // and grandparent files // State for implementing IsBaseLevelForKey @@ -392,6 +484,6 @@ class Compaction { size_t level_ptrs_[config::kNumLevels]; }; -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_DB_VERSION_SET_H_ +#endif // STORAGE_LEVELDB_DB_VERSION_SET_H_ diff --git a/db/write_batch_internal.h b/db/write_batch_internal.h index 9448ef7..438f876 100644 --- a/db/write_batch_internal.h +++ b/db/write_batch_internal.h @@ -14,37 +14,55 @@ class MemTable; // WriteBatchInternal provides static methods for manipulating a // WriteBatch that we don't want in the public WriteBatch interface. +/** + * @brief + * WriteBatchInternal提供了静态方法来操作我们不希望出现在公共WriteBatch接口中的WriteBatch。 + */ class WriteBatchInternal { - public: +public: // Return the number of entries in the batch. - static int Count(const WriteBatch* batch); + /** + *@brief返回batch中的条目数。 + */ + static int Count(const WriteBatch *batch); // Set the count for the number of entries in the batch. - static void SetCount(WriteBatch* batch, int n); + /** + *@brief 设置batch中条目数的count。 + */ + static void SetCount(WriteBatch *batch, int n); // Return the sequence number for the start of this batch. - static SequenceNumber Sequence(const WriteBatch* batch); + /** + *@brief 返回此batch开始的序列号。 + */ + static SequenceNumber Sequence(const WriteBatch *batch); // Store the specified number as the sequence number for the start of // this batch. - static void SetSequence(WriteBatch* batch, SequenceNumber seq); + /** + * @brief 将指定的数字存储为此批处理开始时的序列号。 + */ + static void SetSequence(WriteBatch *batch, SequenceNumber seq); - static Slice Contents(const WriteBatch* batch) { - return Slice(batch->rep_); - } + static Slice Contents(const WriteBatch *batch) { return Slice(batch->rep_); } - static size_t ByteSize(const WriteBatch* batch) { - return batch->rep_.size(); - } + static size_t ByteSize(const WriteBatch *batch) { return batch->rep_.size(); } - static void SetContents(WriteBatch* batch, const Slice& contents); + static void SetContents(WriteBatch *batch, const Slice &contents); - static Status InsertInto(const WriteBatch* batch, MemTable* memtable); + /** + * @brief 将batch应用到memtable + */ + static Status InsertInto(const WriteBatch *batch, MemTable *memtable); - static void Append(WriteBatch* dst, const WriteBatch* src); + /** + * @brief 将src的内容复制到dst中 + * 注意,只需要拷贝src的Content + */ + static void Append(WriteBatch *dst, const WriteBatch *src); }; -} // namespace leveldb +} // namespace leveldb - -#endif // STORAGE_LEVELDB_DB_WRITE_BATCH_INTERNAL_H_ +#endif // STORAGE_LEVELDB_DB_WRITE_BATCH_INTERNAL_H_ diff --git a/demo/.gitignore b/demo/.gitignore new file mode 100644 index 0000000..7951405 --- /dev/null +++ b/demo/.gitignore @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt new file mode 100644 index 0000000..9c241e7 --- /dev/null +++ b/demo/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.9) + +project(case VERSION 0.1) + +set(CMAKE_BUILD_TYPE "Debug") + +add_subdirectory(case) + diff --git a/demo/case/CMakeLists.txt b/demo/case/CMakeLists.txt new file mode 100644 index 0000000..ce7fed2 --- /dev/null +++ b/demo/case/CMakeLists.txt @@ -0,0 +1,20 @@ + +set(include_dir_path /home/lutar/code/c/leveldb/demo/lib/include) +set(lib_dir_path /home/lutar/code/c/leveldb/demo/lib/lib) + +set(leveldb_DIR /home/lutar/code/c/leveldb/demo/lib/lib/cmake/leveldb) +# find_package(leveldb REQUIRED PATHS /home/lutar/code/c/leveldb/demo/lib/lib/cmake/leveldb) +find_package(leveldb REQUIRED) + +macro(single_target_cmp name src_path) + add_executable(${name} ${src_path}/${name}.cc) + target_include_directories(${name} PUBLIC ${include_dir_path}) + target_link_directories(${name} PUBLIC ${lib_dir_path}) + target_link_libraries(${name} PUBLIC leveldb) +endmacro(single_target_cmp) + +macro(case_cmp name) + single_target_cmp(${name} /home/lutar/code/c/leveldb/demo/case) +endmacro(case_cmp) + +case_cmp(open_close) diff --git a/demo/case/get.cc b/demo/case/get.cc new file mode 100644 index 0000000..e69de29 diff --git a/demo/case/open_close.cc b/demo/case/open_close.cc new file mode 100644 index 0000000..8b2aaca --- /dev/null +++ b/demo/case/open_close.cc @@ -0,0 +1,20 @@ +#include + +#include "leveldb/db.h" + +int main() { + std::cout << "hello world" << std::endl; + + leveldb::DB *db; + leveldb::Options options; + options.create_if_missing = true; + leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db); + assert(status.ok()); + + delete db; // This closes the database + + status = leveldb::DestroyDB("/tmp/testdb", options); + assert(status.ok()); + + return 0; +} \ No newline at end of file diff --git a/demo/case/put.cc b/demo/case/put.cc new file mode 100644 index 0000000..e69de29 diff --git a/doc/learn/db.md b/doc/learn/db.md new file mode 100644 index 0000000..bbcace8 --- /dev/null +++ b/doc/learn/db.md @@ -0,0 +1,203 @@ +## DB + +``` c++ +static Status Open(const Options &options, const std::string &name, + DB **dbptr); + +virtual Status Put(const WriteOptions &options, const Slice &key, + const Slice &value) = 0; + +virtual Status Delete(const WriteOptions &options, const Slice &key) = 0; + +virtual Status Write(const WriteOptions &options, WriteBatch *updates) = 0; + +virtual Status Get(const ReadOptions &options, const Slice &key, + std::string *value) = 0; + +/** +* @brief 返回数据库内容上的堆分配的迭代器。 +* NewIterator()的结果最初是无效的(调用者在使用迭代器之前必须调用其中一个Seek方法)。 +* @note 当不再需要迭代器时,调用者应该删除它。 +* 返回的迭代器应该在删除此数据库之前删除。 +*/ +virtual Iterator *NewIterator(const ReadOptions &options) = 0; + +virtual const Snapshot *GetSnapshot() = 0; + +virtual void ReleaseSnapshot(const Snapshot *snapshot) = 0; + +virtual bool GetProperty(const Slice &property, std::string *value) = 0; + +virtual void GetApproximateSizes(const Range *range, int n, + uint64_t *sizes) = 0; + +virtual void CompactRange(const Slice *begin, const Slice *end) = 0; +``` + + + + + +## DBImpl + +### Open + +创建DB实例 + +DB实例恢复状态 + + + +- **如果之前创建过数据库** + - 调用DB实例的Revoer方法,并得到VersionEdit + +- **如果是第一次创建数据库** + + - 创建log实例,分配一个新的文件号,根据此文件号创建日志文件 + + - 为VersionEdit设置文件号 + + - 更新DB实例 + + - 关联日志文件 + + - 文件号 + + - 创建memtable,并增加引用 + + + +为versionedit设置 + +- prevlognumber = 0 +- 设置文件号 +- 将versionedit应用到versionset中 + + + +删除过时的文件 + +后台开启压缩调度线程 + +返回DB实例 + + + + + +### Write + +> 需要先去了解[WriteBatch](##WriteBatch) + +创建Writer,并入队列writers,等待队列 + +调用MakeRoomForWrite + +调用BuildBatchGroup + + + +## WriteBatch + +### WriteBatch + +WriteBatch示例 + + + + + + + + + + + + + + + + + + + + + + + + + +
sequence(fixed64)count(fixed32)
kTypeValuekeyvalue
kTypeValuekeyvalue
kTypeDeletionkey
.........
+ + + +WriteBatch格式 + +| sequence | count | data | +| --------- | --------- | ---------- | +| *fixed64* | *fixed32* | record列表 | + +- 首部长度为12-byte:8-byte seuqnece number + 4-byte count + + + +record类型 + +- kTypeValue:*varstring varstring* ,即存储key value +- kTypeDeletion:*varstring*,即存储key + + + +varstring格式 + +| len | data | +| ---------- | ------------ | +| *varint32* | *uint8[len]* | + + + +接口 + +``` c++ +void Put(const Slice &key, const Slice &value); +void Delete(const Slice &key); +void Clear(); +size_t ApproximateSize() const; +void Append(const WriteBatch &source); +Status Iterate(Handler *handler) const; +``` + +WriteBatch#Handler + +``` c++ +virtual void Put(const Slice &key, const Slice &value) = 0; +virtual void Delete(const Slice &key) = 0; +``` + + + + + +### WriteBatchInternal + +WriteBatchInternal提供了静态方法来操作我们不希望出现在公共WriteBatch接口中的WriteBatch。 + +``` c++ +// batch中的条目数 +static int Count(const WriteBatch *batch); +static void SetCount(WriteBatch *batch, int n); + +// batch开始的序列号。 +static SequenceNumber Sequence(const WriteBatch *batch); +static void SetSequence(WriteBatch *batch, SequenceNumber seq); + +// 返回包含header在内的所有内容 +static Slice Contents(const WriteBatch *batch); +static size_t ByteSize(const WriteBatch *batch); +static void SetContents(WriteBatch *batch, const Slice &contents); +static Status InsertInto(const WriteBatch *batch, MemTable *memtable); +static void Append(WriteBatch *dst, const WriteBatch *src); +``` + + + diff --git a/include/leveldb/cache.h b/include/leveldb/cache.h index e416ea5..a238063 100644 --- a/include/leveldb/cache.h +++ b/include/leveldb/cache.h @@ -18,9 +18,9 @@ #ifndef STORAGE_LEVELDB_INCLUDE_CACHE_H_ #define STORAGE_LEVELDB_INCLUDE_CACHE_H_ -#include #include "leveldb/export.h" #include "leveldb/slice.h" +#include namespace leveldb { @@ -28,21 +28,21 @@ class LEVELDB_EXPORT Cache; // Create a new cache with a fixed size capacity. This implementation // of Cache uses a least-recently-used eviction policy. -LEVELDB_EXPORT Cache* NewLRUCache(size_t capacity); +LEVELDB_EXPORT Cache *NewLRUCache(size_t capacity); class LEVELDB_EXPORT Cache { - public: +public: Cache() = default; - Cache(const Cache&) = delete; - Cache& operator=(const Cache&) = delete; + Cache(const Cache &) = delete; + Cache &operator=(const Cache &) = delete; // Destroys all existing entries by calling the "deleter" // function that was passed to the constructor. virtual ~Cache(); // Opaque handle to an entry stored in the cache. - struct Handle { }; + struct Handle {}; // Insert a mapping from key->value into the cache and assign it // the specified charge against the total cache capacity. @@ -53,36 +53,69 @@ class LEVELDB_EXPORT Cache { // // When the inserted entry is no longer needed, the key and // value will be passed to "deleter". - virtual Handle* Insert(const Slice& key, void* value, size_t charge, - void (*deleter)(const Slice& key, void* value)) = 0; + + /** + * @brief 将一个从key->value的映射插入到缓存中,并将指定的总缓存容量分配给它。 + * + * 返回对应于映射的句柄。当返回的映射不再需要时,调用者必须调用this->Release(handle)。 + * 当插入的条目不再需要时,键和值将传递给“deleter”。 + */ + virtual Handle *Insert(const Slice &key, void *value, size_t charge, + void (*deleter)(const Slice &key, void *value)) = 0; // If the cache has no mapping for "key", returns nullptr. // // Else return a handle that corresponds to the mapping. The caller // must call this->Release(handle) when the returned mapping is no // longer needed. - virtual Handle* Lookup(const Slice& key) = 0; + + /** + * @brief 如果缓存没有“key”的映射,则返回nullptr。 + * 否则返回对应于映射的句柄。 + * @note 当返回的映射不再需要时,调用者必须调用this->Release(handle)。 + */ + virtual Handle *Lookup(const Slice &key) = 0; // Release a mapping returned by a previous Lookup(). // REQUIRES: handle must not have been released yet. // REQUIRES: handle must have been returned by a method on *this. - virtual void Release(Handle* handle) = 0; + + /** + * @brief 释放先前Lookup()返回的映射。 + */ + virtual void Release(Handle *handle) = 0; // Return the value encapsulated in a handle returned by a // successful Lookup(). // REQUIRES: handle must not have been released yet. // REQUIRES: handle must have been returned by a method on *this. - virtual void* Value(Handle* handle) = 0; + + /** + * @brief 返回封装在成功Lookup()返回的句柄中的值。 + */ + virtual void *Value(Handle *handle) = 0; // If the cache contains entry for key, erase it. Note that the // underlying entry will be kept around until all existing handles // to it have been released. - virtual void Erase(const Slice& key) = 0; + + /** + * @brief + * 如果缓存包含键项,则擦除它。注意,底层条目将一直保留,直到所有现有的句柄都被释放。 + */ + virtual void Erase(const Slice &key) = 0; // Return a new numeric id. May be used by multiple clients who are // sharing the same cache to partition the key space. Typically the // client will allocate a new id at startup and prepend the id to // its cache keys. + + /** + * @brief 返回一个新的数字id。 + * + * 可以由共享同一缓存的多个客户端使用,以对键空间进行分区。 + * 通常,客户机将在启动时分配一个新id,并将该id添加到其缓存键。 + */ virtual uint64_t NewId() = 0; // Remove all cache entries that are not actively in use. Memory-constrained @@ -90,21 +123,33 @@ class LEVELDB_EXPORT Cache { // Default implementation of Prune() does nothing. Subclasses are strongly // encouraged to override the default implementation. A future release of // leveldb may change Prune() to a pure abstract method. + + /** + * @brief 删除所有未活跃使用的缓存项。 + * + * 内存受限的应用程序可能希望调用此方法来减少内存使用。 + * Prune()的默认实现不做任何事情。强烈建议子类覆盖默认实现。 + * leveldb的未来版本可能会将Prune()更改为纯抽象方法。 + */ virtual void Prune() {} // Return an estimate of the combined charges of all elements stored in the // cache. + + /** + * @brief 返回缓存中存储的所有元素的组合费用的估计值。 + */ virtual size_t TotalCharge() const = 0; - private: - void LRU_Remove(Handle* e); - void LRU_Append(Handle* e); - void Unref(Handle* e); +private: + void LRU_Remove(Handle *e); + void LRU_Append(Handle *e); + void Unref(Handle *e); struct Rep; - Rep* rep_; + Rep *rep_; }; -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_INCLUDE_CACHE_H_ +#endif // STORAGE_LEVELDB_INCLUDE_CACHE_H_ diff --git a/include/leveldb/comparator.h b/include/leveldb/comparator.h index 9b09684..9a19ebe 100644 --- a/include/leveldb/comparator.h +++ b/include/leveldb/comparator.h @@ -5,8 +5,8 @@ #ifndef STORAGE_LEVELDB_INCLUDE_COMPARATOR_H_ #define STORAGE_LEVELDB_INCLUDE_COMPARATOR_H_ -#include #include "leveldb/export.h" +#include namespace leveldb { @@ -16,15 +16,31 @@ class Slice; // used as keys in an sstable or a database. A Comparator implementation // must be thread-safe since leveldb may invoke its methods concurrently // from multiple threads. + +/** + * @brief Comparator对象提供across + * string的总顺序,这些string在sstable或数据库中用作键。 + */ class LEVELDB_EXPORT Comparator { - public: +public: virtual ~Comparator(); // Three-way comparison. Returns value: // < 0 iff "a" < "b", // == 0 iff "a" == "b", // > 0 iff "a" > "b" - virtual int Compare(const Slice& a, const Slice& b) const = 0; + + /** + * @brief 比较a和b + * + * @return + * < 0 iff "a" < "b", + * + * == 0 iff "a" == "b", + * + * > 0 iff "a" > "b" + */ + virtual int Compare(const Slice &a, const Slice &b) const = 0; // The name of the comparator. Used to check for comparator // mismatches (i.e., a DB created with one comparator is @@ -36,7 +52,17 @@ class LEVELDB_EXPORT Comparator { // // Names starting with "leveldb." are reserved and should not be used // by any clients of this package. - virtual const char* Name() const = 0; + + /** + * @brief 返回比较器的名字 + * + * 用于检查比较器不匹配(即,使用一个比较器创建的DB使用不同的比较器访问)。 + * + * @note + * 每当比较器实现发生变化,导致任意两个键的相对顺序发生变化时,这个包的客户机就应该切换到一个新名称。 + * @note 以“leveldb.”开头的名字是保留的,不应该被这个包的任何客户端使用。 + */ + virtual const char *Name() const = 0; // Advanced functions: these are used to reduce the space requirements // for internal data structures like index blocks. @@ -44,21 +70,36 @@ class LEVELDB_EXPORT Comparator { // If *start < limit, changes *start to a short string in [start,limit). // Simple comparator implementations may return with *start unchanged, // i.e., an implementation of this method that does nothing is correct. - virtual void FindShortestSeparator( - std::string* start, - const Slice& limit) const = 0; + + /** + * @brief 找到一个最短的字符串 seperator,使得 start <= seperator < limit, + * 并将结果保存在 start 中。 + * + * 用于 SST 中 Data Block 的索引构建。 + * https://blog.csdn.net/sinat_38293503/article/details/134942252 + */ + virtual void FindShortestSeparator(std::string *start, + const Slice &limit) const = 0; // Changes *key to a short string >= *key. // Simple comparator implementations may return with *key unchanged, // i.e., an implementation of this method that does nothing is correct. - virtual void FindShortSuccessor(std::string* key) const = 0; + + /** + * @brief 找到一个最短的字符串 successor,使得 key <= successor, + * 并将结果保存在 key 中。 + * + * 用于 SST 中最后一个 Data Block 的索引构建,因为没有后续的 Key 了。 + * https://blog.csdn.net/sinat_38293503/article/details/134942252 + */ + virtual void FindShortSuccessor(std::string *key) const = 0; }; // Return a builtin comparator that uses lexicographic byte-wise // ordering. The result remains the property of this module and // must not be deleted. -LEVELDB_EXPORT const Comparator* BytewiseComparator(); +LEVELDB_EXPORT const Comparator *BytewiseComparator(); -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_INCLUDE_COMPARATOR_H_ +#endif // STORAGE_LEVELDB_INCLUDE_COMPARATOR_H_ diff --git a/include/leveldb/db.h b/include/leveldb/db.h index 0239593..8da7bf7 100644 --- a/include/leveldb/db.h +++ b/include/leveldb/db.h @@ -5,11 +5,11 @@ #ifndef STORAGE_LEVELDB_INCLUDE_DB_H_ #define STORAGE_LEVELDB_INCLUDE_DB_H_ -#include -#include #include "leveldb/export.h" #include "leveldb/iterator.h" #include "leveldb/options.h" +#include +#include namespace leveldb { @@ -25,58 +25,83 @@ class WriteBatch; // Abstract handle to particular state of a DB. // A Snapshot is an immutable object and can therefore be safely // accessed from multiple threads without any external synchronization. + +/** + * @brief 数据库特定状态的抽象句柄。 + * + * 快照是一个不可变的对象,因此可以在没有任何外部同步的情况下从多个线程安全地访问。 + */ class LEVELDB_EXPORT Snapshot { - protected: +protected: virtual ~Snapshot(); }; // A range of keys struct LEVELDB_EXPORT Range { - Slice start; // Included in the range - Slice limit; // Not included in the range + Slice start; // Included in the range + Slice limit; // Not included in the range - Range() { } - Range(const Slice& s, const Slice& l) : start(s), limit(l) { } + Range() {} + Range(const Slice &s, const Slice &l) : start(s), limit(l) {} }; // A DB is a persistent ordered map from keys to values. // A DB is safe for concurrent access from multiple threads without // any external synchronization. + +/** + * @brief DB是从键到值的持久有序映射。 + * 数据库对于来自多个线程的并发访问是安全的,无需任何外部同步。 + */ class LEVELDB_EXPORT DB { - public: +public: // Open the database with the specified "name". // Stores a pointer to a heap-allocated database in *dbptr and returns // OK on success. // Stores nullptr in *dbptr and returns a non-OK status on error. // Caller should delete *dbptr when it is no longer needed. - static Status Open(const Options& options, - const std::string& name, - DB** dbptr); + static Status Open(const Options &options, const std::string &name, + DB **dbptr); DB() = default; - DB(const DB&) = delete; - DB& operator=(const DB&) = delete; + DB(const DB &) = delete; + DB &operator=(const DB &) = delete; virtual ~DB(); // Set the database entry for "key" to "value". Returns OK on success, // and a non-OK status on error. // Note: consider setting options.sync = true. - virtual Status Put(const WriteOptions& options, - const Slice& key, - const Slice& value) = 0; + /** + * @brief 将“key”的数据库条目设置为“value”。 + * @return 成功时返回OK, 错误时non-OK + * @note 考虑设置 options.sync = true. + */ + virtual Status Put(const WriteOptions &options, const Slice &key, + const Slice &value) = 0; // Remove the database entry (if any) for "key". Returns OK on // success, and a non-OK status on error. It is not an error if "key" // did not exist in the database. // Note: consider setting options.sync = true. - virtual Status Delete(const WriteOptions& options, const Slice& key) = 0; + /** + * @brief 删除“key”的数据库条目(如果有的话)。 + * @return 成功时返回OK, + * 错误时non-OK,如果数据库中不存在“key”,则不会出现错误。 + * @note 考虑设置 options.sync = true. + */ + virtual Status Delete(const WriteOptions &options, const Slice &key) = 0; // Apply the specified updates to the database. // Returns OK on success, non-OK on failure. // Note: consider setting options.sync = true. - virtual Status Write(const WriteOptions& options, WriteBatch* updates) = 0; + /** + * @brief 将指定的更新应用于数据库。 + * @return 成功时返回OK, 失败时non-OK + * @note 考虑设置 options.sync = true. + */ + virtual Status Write(const WriteOptions &options, WriteBatch *updates) = 0; // If the database contains an entry for "key" store the // corresponding value in *value and return OK. @@ -85,8 +110,14 @@ class LEVELDB_EXPORT DB { // a status for which Status::IsNotFound() returns true. // // May return some other Status on an error. - virtual Status Get(const ReadOptions& options, - const Slice& key, std::string* value) = 0; + /** + * @brief 根据key获取值 + * @return 如果数据库包含“key”条目,则在*value中存储相应的值并返回OK。 + * 如果没有“key”条目,则保持*value不变,并返回一个status::IsNotFound()返回true的状态。 + * 在发生错误时返回其他状态。 + */ + virtual Status Get(const ReadOptions &options, const Slice &key, + std::string *value) = 0; // Return a heap-allocated iterator over the contents of the database. // The result of NewIterator() is initially invalid (caller must @@ -94,17 +125,31 @@ class LEVELDB_EXPORT DB { // // Caller should delete the iterator when it is no longer needed. // The returned iterator should be deleted before this db is deleted. - virtual Iterator* NewIterator(const ReadOptions& options) = 0; + /** + * @brief 返回数据库内容上的堆分配的迭代器。 + * NewIterator()的结果最初是无效的(调用者在使用迭代器之前必须调用其中一个Seek方法)。 + * @note 当不再需要迭代器时,调用者应该删除它。 + * 返回的迭代器应该在删除此数据库之前删除。 + */ + virtual Iterator *NewIterator(const ReadOptions &options) = 0; // Return a handle to the current DB state. Iterators created with // this handle will all observe a stable snapshot of the current DB // state. The caller must call ReleaseSnapshot(result) when the // snapshot is no longer needed. - virtual const Snapshot* GetSnapshot() = 0; + /** + * @brief 返回当前DB状态的handle。 + * 使用此handle创建的迭代器都将观察到当前DB状态的稳定快照。 + * 当不再需要快照时,调用者必须调用ReleaseSnapshot(result)。 + */ + virtual const Snapshot *GetSnapshot() = 0; // Release a previously acquired snapshot. The caller must not // use "snapshot" after this call. - virtual void ReleaseSnapshot(const Snapshot* snapshot) = 0; + /** + * @brief 释放先前获取的快照。在此调用之后,调用者不得使用“snapshot”。 + */ + virtual void ReleaseSnapshot(const Snapshot *snapshot) = 0; // DB implementations can export properties about their state // via this method. If "property" is a valid property understood by this @@ -122,7 +167,24 @@ class LEVELDB_EXPORT DB { // of the sstables that make up the db contents. // "leveldb.approximate-memory-usage" - returns the approximate number of // bytes of memory in use by the DB. - virtual bool GetProperty(const Slice& property, std::string* value) = 0; + + /** + * @brief 数据库实现可以通过此方法暴露有关其状态的属性。 + * + * @details + * 有效的属性名称包括: + * "leveldb.num-files-at-level" + * -返回级别的文件数,其中是级别号的ASCII表示形式(例如:“0”)。 + * + * "leveldb.stats"-返回一个多行字符串,描述有关DB内部操作的统计信息。 + * "leveldb.sstables"-返回一个多行字符串,描述构成db内容的所有sstable。 + * "leveldb.approximate-memory-usage"-返回DB使用的内存的大致字节数。 + * + * @return + * 如果“property”是该DB实现所理解的有效属性,则用其当前值填充“*value”并返回true。 + * 否则返回false。 + */ + virtual bool GetProperty(const Slice &property, std::string *value) = 0; // For each i in [0,n-1], store in "sizes[i]", the approximate // file system space used by keys in "[range[i].start .. range[i].limit)". @@ -132,8 +194,16 @@ class LEVELDB_EXPORT DB { // sizes will be one-tenth the size of the corresponding user data size. // // The results may not include the sizes of recently written data. - virtual void GetApproximateSizes(const Range* range, int n, - uint64_t* sizes) = 0; + + /** + * @brief 对于[0,n-1]中的每个i,存储在“sizes[i]”中,即“[range[i].start ... + * range[i].limit)”中键使用的近似文件系统空间。 + * @note + * 请注意,返回的大小度量文件系统空间使用情况,因此如果用户数据按10倍压缩,则返回的大小将是相应用户数据大小的十分之一。 + * 结果可能不包括最近写入数据的大小。 + */ + virtual void GetApproximateSizes(const Range *range, int n, + uint64_t *sizes) = 0; // Compact the underlying storage for the key range [*begin,*end]. // In particular, deleted and overwritten versions are discarded, @@ -145,7 +215,15 @@ class LEVELDB_EXPORT DB { // end==nullptr is treated as a key after all keys in the database. // Therefore the following call will compact the entire database: // db->CompactRange(nullptr, nullptr); - virtual void CompactRange(const Slice* begin, const Slice* end) = 0; + /** + * @brief 压缩键范围[*begin,*end]的底层存储。 + * 特别是,删除和覆盖的版本被丢弃,数据被重新排列,以减少访问数据所需的操作成本。 + * 此操作通常只应由了解底层实现的用户调用。 + * @param begin nullptr被视为数据库中所有键之前的键。 + * @param end nullptr被视为数据库中所有键之后的一个键。 + * 因此下面的调用将压缩整个数据库:db->CompactRange(nullptr, nullptr); + */ + virtual void CompactRange(const Slice *begin, const Slice *end) = 0; }; // Destroy the contents of the specified database. @@ -153,16 +231,27 @@ class LEVELDB_EXPORT DB { // // Note: For backwards compatibility, if DestroyDB is unable to list the // database files, Status::OK() will still be returned masking this failure. -LEVELDB_EXPORT Status DestroyDB(const std::string& name, - const Options& options); +/** + * @brief 销毁指定数据库的内容。 + * 使用这种方法要非常小心。 + * @note + * 注意:为了向后兼容,如果DestroyDB无法列出数据库文件,Status::OK()仍然会返回,以掩盖此失败。 + */ +LEVELDB_EXPORT Status DestroyDB(const std::string &name, + const Options &options); // If a DB cannot be opened, you may attempt to call this method to // resurrect as much of the contents of the database as possible. // Some data may be lost, so be careful when calling this function -// on a database that contains important information. -LEVELDB_EXPORT Status RepairDB(const std::string& dbname, - const Options& options); -} // namespace leveldb +/** + * @brief + * 如果无法打开数据库,则可以尝试调用此方法来恢复数据库中尽可能多的内容。 + * 可能会丢失一些数据,因此在包含重要信息的数据库上调用此函数时要小心。 + */ +LEVELDB_EXPORT Status RepairDB(const std::string &dbname, + const Options &options); + +} // namespace leveldb -#endif // STORAGE_LEVELDB_INCLUDE_DB_H_ +#endif // STORAGE_LEVELDB_INCLUDE_DB_H_ diff --git a/include/leveldb/env.h b/include/leveldb/env.h index 946ea98..a46fa8d 100644 --- a/include/leveldb/env.h +++ b/include/leveldb/env.h @@ -13,12 +13,12 @@ #ifndef STORAGE_LEVELDB_INCLUDE_ENV_H_ #define STORAGE_LEVELDB_INCLUDE_ENV_H_ +#include "leveldb/export.h" +#include "leveldb/status.h" #include #include #include #include -#include "leveldb/export.h" -#include "leveldb/status.h" #if defined(_WIN32) // The leveldb::Env class below contains a DeleteFile method. @@ -38,8 +38,8 @@ #if defined(DeleteFile) #undef DeleteFile #define LEVELDB_DELETEFILE_UNDEFINED -#endif // defined(DeleteFile) -#endif // defined(_WIN32) +#endif // defined(DeleteFile) +#endif // defined(_WIN32) namespace leveldb { @@ -51,11 +51,11 @@ class Slice; class WritableFile; class LEVELDB_EXPORT Env { - public: +public: Env() = default; - Env(const Env&) = delete; - Env& operator=(const Env&) = delete; + Env(const Env &) = delete; + Env &operator=(const Env &) = delete; virtual ~Env(); @@ -64,7 +64,13 @@ class LEVELDB_EXPORT Env { // implementation instead of relying on this default environment. // // The result of Default() belongs to leveldb and must never be deleted. - static Env* Default(); + + /** + * @brief 返回适合当前操作系统的默认环境。 + * 资深用户可能希望提供他们自己的Env实现,而不是依赖于这个默认环境。 + * Default()的结果属于leveldb,永远不能删除。 + */ + static Env *Default(); // Create an object that sequentially reads the file with the specified name. // On success, stores a pointer to the new file in *result and returns OK. @@ -73,8 +79,16 @@ class LEVELDB_EXPORT Env { // NotFound status when the file does not exist. // // The returned file will only be accessed by one thread at a time. - virtual Status NewSequentialFile(const std::string& fname, - SequentialFile** result) = 0; + + /** + * @brief 创建一个对象,以顺序读取具有指定名称的文件。 + * 返回的文件一次只能被一个线程访问。 + * @return 如果成功,将指向新文件的指针存储在*result中并返回OK。 + * 失败时将nullptr存储在*result中并返回非ok。 + * 如果文件不存在,则返回non-OK状态。当文件不存在时,实现应该返回NotFound状态。 + */ + virtual Status NewSequentialFile(const std::string &fname, + SequentialFile **result) = 0; // Create an object supporting random-access reads from the file with the // specified name. On success, stores a pointer to the new file in @@ -84,8 +98,17 @@ class LEVELDB_EXPORT Env { // not exist. // // The returned file may be concurrently accessed by multiple threads. - virtual Status NewRandomAccessFile(const std::string& fname, - RandomAccessFile** result) = 0; + + /** + * @brief 创建一个支持从指定名称的文件中随机读取的对象。 + * 返回的文件一次只能被一个线程访问。 + * @return + * 如果成功,将指向新文件的指针存储在*result中并返回OK。 + * 失败时将nullptr存储在*result中并返回non-OK。 + * 如果文件不存在,则返回non-OK状态。当文件不存在时,实现应该返回NotFound状态。 + */ + virtual Status NewRandomAccessFile(const std::string &fname, + RandomAccessFile **result) = 0; // Create an object that writes to a new file with the specified // name. Deletes any existing file with the same name and creates a @@ -94,8 +117,16 @@ class LEVELDB_EXPORT Env { // returns non-OK. // // The returned file will only be accessed by one thread at a time. - virtual Status NewWritableFile(const std::string& fname, - WritableFile** result) = 0; + + /** + * @brief + * 创建一个对象,写入具有指定名称的新文件。删除所有同名的现有文件,并创建一个新文件。 + * 返回的文件一次只能被一个线程访问。 + * @return 如果成功,将指向新文件的指针存储在*result中并返回OK。 + * 失败时将nullptr存储在*result中并返回non-OK。 + */ + virtual Status NewWritableFile(const std::string &fname, + WritableFile **result) = 0; // Create an object that either appends to an existing file, or // writes to a new file (if the file does not exist to begin with). @@ -109,33 +140,60 @@ class LEVELDB_EXPORT Env { // not allow appending to an existing file. Users of Env (including // the leveldb implementation) must be prepared to deal with // an Env that does not support appending. - virtual Status NewAppendableFile(const std::string& fname, - WritableFile** result); + + /** + * @brief + * 创建一个对象,该对象要么追加到现有文件,要么写入新文件(如果该文件一开始就不存在)。 + * 返回的文件一次只能被一个线程访问。 + * @return + * - 如果成功,将指向新文件的指针存储在*result中并返回OK。 + * + * - 失败时将nullptr存储在*result中并返回non-OK。 + * + * - 如果此环境不允许追加到现有文件,可能会返回IsNotSupportedError错误。 + * Env的用户(包括leveldb实现)必须准备好处理不支持追加的Env。 + */ + virtual Status NewAppendableFile(const std::string &fname, + WritableFile **result); // Returns true iff the named file exists. - virtual bool FileExists(const std::string& fname) = 0; + virtual bool FileExists(const std::string &fname) = 0; // Store in *result the names of the children of the specified directory. // The names are relative to "dir". // Original contents of *results are dropped. - virtual Status GetChildren(const std::string& dir, - std::vector* result) = 0; + + /** + * @brief 在*result中存储指定目录的子目录名。 + * 这些名称是相对于“dir”的。 + * *results 的原始内容将被删除。 + */ + virtual Status GetChildren(const std::string &dir, + std::vector *result) = 0; // Delete the named file. - virtual Status DeleteFile(const std::string& fname) = 0; + virtual Status DeleteFile(const std::string &fname) = 0; // Create the specified directory. - virtual Status CreateDir(const std::string& dirname) = 0; + virtual Status CreateDir(const std::string &dirname) = 0; // Delete the specified directory. - virtual Status DeleteDir(const std::string& dirname) = 0; + virtual Status DeleteDir(const std::string &dirname) = 0; // Store the size of fname in *file_size. - virtual Status GetFileSize(const std::string& fname, uint64_t* file_size) = 0; + virtual Status GetFileSize(const std::string &fname, uint64_t *file_size) = 0; // Rename file src to target. - virtual Status RenameFile(const std::string& src, - const std::string& target) = 0; + + /** + * @brief 删除指定文件。 + * 默认实现调用DeleteFile,以支持旧的Env实现。 + * 更新后的Env实现必须覆盖RemoveFile并忽略DeleteFile的存在。 + * 更新后的代码调用Env API必须调用RemoveFile而不是DeleteFile。 + * @note 未来的版本将删除DeleteDir和RemoveDir的默认实现。 + */ + virtual Status RenameFile(const std::string &src, + const std::string &target) = 0; // Lock the specified file. Used to prevent concurrent access to // the same db by multiple processes. On failure, stores nullptr in @@ -151,12 +209,28 @@ class LEVELDB_EXPORT Env { // to go away. // // May create the named file if it does not already exist. - virtual Status LockFile(const std::string& fname, FileLock** lock) = 0; + + /** + * @brief + * 锁定指定的文件。用于防止多个进程并发访问同一个数据库。 + * 如果其他人已经持有锁,则立即以失败结束。也就是说,这个调用不会等待现有的锁消失。 + * 如果命名文件不存在,可以创建该文件。 + * @return + * - 如果失败,将nullptr存储在*lock中并返回非ok。 + * + * - 成功时,在*lock中存储一个指向表示获取的锁的对象的指针,并返回OK。 + * @note 调用者应该调用UnlockFile(*lock)来释放锁。如果进程退出,锁将自动释放。 + */ + virtual Status LockFile(const std::string &fname, FileLock **lock) = 0; // Release the lock acquired by a previous successful call to LockFile. // REQUIRES: lock was returned by a successful LockFile() call // REQUIRES: lock has not already been unlocked. - virtual Status UnlockFile(FileLock* lock) = 0; + + /** + * @brief 释放先前成功调用LockFile所获得的锁。 + */ + virtual Status UnlockFile(FileLock *lock) = 0; // Arrange to run "(*function)(arg)" once in a background thread. // @@ -164,38 +238,66 @@ class LEVELDB_EXPORT Env { // added to the same Env may run concurrently in different threads. // I.e., the caller may not assume that background work items are // serialized. - virtual void Schedule( - void (*function)(void* arg), - void* arg) = 0; + + /** + * @brief 安排在后台线程中运行一次"(*function)(arg)"。 + * "function"可以在未指定的线程中运行。 + * 添加到同一个Env中的多个函数可以在不同的线程中并发运行。 + * 也就是说,调用者不能假定后台工作项是序列化的。 + */ + virtual void Schedule(void (*function)(void *arg), void *arg) = 0; // Start a new thread, invoking "function(arg)" within the new thread. // When "function(arg)" returns, the thread will be destroyed. - virtual void StartThread(void (*function)(void* arg), void* arg) = 0; + + /** + * @brief 启动一个新线程,在新线程中调用"function(arg)"。 + * 当"function(arg)"返回时,线程将被销毁。 + */ + virtual void StartThread(void (*function)(void *arg), void *arg) = 0; // *path is set to a temporary directory that can be used for testing. It may // or many not have just been created. The directory may or may not differ // between runs of the same process, but subsequent calls will return the // same directory. - virtual Status GetTestDirectory(std::string* path) = 0; + + /** + * @brief + * *path被设置为一个临时目录,可以用于测试。它可能是刚刚创建的,也可能不是。 + * 在运行同一进程之间,目录可能相同,也可能不同,但后续调用将返回相同的目录。 + */ + virtual Status GetTestDirectory(std::string *path) = 0; // Create and return a log file for storing informational messages. - virtual Status NewLogger(const std::string& fname, Logger** result) = 0; + + /** + * @brief 创建并返回用于存储信息消息的日志文件。 + */ + virtual Status NewLogger(const std::string &fname, Logger **result) = 0; // Returns the number of micro-seconds since some fixed point in time. Only // useful for computing deltas of time. + + /** + * @brief 返回自某个固定时间点以来的微秒数。 只对计算时间函数有用。 + */ virtual uint64_t NowMicros() = 0; // Sleep/delay the thread for the prescribed number of micro-seconds. + + /** + * @brief 在规定的微秒数内sleep/delay线程。 + */ virtual void SleepForMicroseconds(int micros) = 0; }; // A file abstraction for reading sequentially through a file class LEVELDB_EXPORT SequentialFile { - public: +public: SequentialFile() = default; - SequentialFile(const SequentialFile&) = delete; - SequentialFile& operator=(const SequentialFile&) = delete; + SequentialFile(const SequentialFile &) = delete; + SequentialFile &operator=(const SequentialFile &) = delete; virtual ~SequentialFile(); @@ -207,7 +309,7 @@ class LEVELDB_EXPORT SequentialFile { // If an error was encountered, returns a non-OK status. // // REQUIRES: External synchronization - virtual Status Read(size_t n, Slice* result, char* scratch) = 0; + virtual Status Read(size_t n, Slice *result, char *scratch) = 0; // Skip "n" bytes from the file. This is guaranteed to be no // slower that reading the same data, but may be faster. @@ -221,11 +323,11 @@ class LEVELDB_EXPORT SequentialFile { // A file abstraction for randomly reading the contents of a file. class LEVELDB_EXPORT RandomAccessFile { - public: +public: RandomAccessFile() = default; - RandomAccessFile(const RandomAccessFile&) = delete; - RandomAccessFile& operator=(const RandomAccessFile&) = delete; + RandomAccessFile(const RandomAccessFile &) = delete; + RandomAccessFile &operator=(const RandomAccessFile &) = delete; virtual ~RandomAccessFile(); @@ -238,23 +340,23 @@ class LEVELDB_EXPORT RandomAccessFile { // status. // // Safe for concurrent use by multiple threads. - virtual Status Read(uint64_t offset, size_t n, Slice* result, - char* scratch) const = 0; + virtual Status Read(uint64_t offset, size_t n, Slice *result, + char *scratch) const = 0; }; // A file abstraction for sequential writing. The implementation // must provide buffering since callers may append small fragments // at a time to the file. class LEVELDB_EXPORT WritableFile { - public: +public: WritableFile() = default; - WritableFile(const WritableFile&) = delete; - WritableFile& operator=(const WritableFile&) = delete; + WritableFile(const WritableFile &) = delete; + WritableFile &operator=(const WritableFile &) = delete; virtual ~WritableFile(); - virtual Status Append(const Slice& data) = 0; + virtual Status Append(const Slice &data) = 0; virtual Status Close() = 0; virtual Status Flush() = 0; virtual Status Sync() = 0; @@ -262,120 +364,118 @@ class LEVELDB_EXPORT WritableFile { // An interface for writing log messages. class LEVELDB_EXPORT Logger { - public: +public: Logger() = default; - Logger(const Logger&) = delete; - Logger& operator=(const Logger&) = delete; + Logger(const Logger &) = delete; + Logger &operator=(const Logger &) = delete; virtual ~Logger(); // Write an entry to the log file with the specified format. - virtual void Logv(const char* format, va_list ap) = 0; + virtual void Logv(const char *format, va_list ap) = 0; }; // Identifies a locked file. class LEVELDB_EXPORT FileLock { - public: +public: FileLock() = default; - FileLock(const FileLock&) = delete; - FileLock& operator=(const FileLock&) = delete; + FileLock(const FileLock &) = delete; + FileLock &operator=(const FileLock &) = delete; virtual ~FileLock(); }; // Log the specified data to *info_log if info_log is non-null. -void Log(Logger* info_log, const char* format, ...) -# if defined(__GNUC__) || defined(__clang__) - __attribute__((__format__ (__printf__, 2, 3))) -# endif +void Log(Logger *info_log, const char *format, ...) +#if defined(__GNUC__) || defined(__clang__) + __attribute__((__format__(__printf__, 2, 3))) +#endif ; // A utility routine: write "data" to the named file. -LEVELDB_EXPORT Status WriteStringToFile(Env* env, const Slice& data, - const std::string& fname); +LEVELDB_EXPORT Status WriteStringToFile(Env *env, const Slice &data, + const std::string &fname); // A utility routine: read contents of named file into *data -LEVELDB_EXPORT Status ReadFileToString(Env* env, const std::string& fname, - std::string* data); +LEVELDB_EXPORT Status ReadFileToString(Env *env, const std::string &fname, + std::string *data); // An implementation of Env that forwards all calls to another Env. // May be useful to clients who wish to override just part of the // functionality of another Env. class LEVELDB_EXPORT EnvWrapper : public Env { - public: +public: // Initialize an EnvWrapper that delegates all calls to *t. - explicit EnvWrapper(Env* t) : target_(t) { } + explicit EnvWrapper(Env *t) : target_(t) {} virtual ~EnvWrapper(); // Return the target to which this Env forwards all calls. - Env* target() const { return target_; } + Env *target() const { return target_; } // The following text is boilerplate that forwards all methods to target(). - Status NewSequentialFile(const std::string& f, SequentialFile** r) override { + Status NewSequentialFile(const std::string &f, SequentialFile **r) override { return target_->NewSequentialFile(f, r); } - Status NewRandomAccessFile(const std::string& f, - RandomAccessFile** r) override { + Status NewRandomAccessFile(const std::string &f, + RandomAccessFile **r) override { return target_->NewRandomAccessFile(f, r); } - Status NewWritableFile(const std::string& f, WritableFile** r) override { + Status NewWritableFile(const std::string &f, WritableFile **r) override { return target_->NewWritableFile(f, r); } - Status NewAppendableFile(const std::string& f, WritableFile** r) override { + Status NewAppendableFile(const std::string &f, WritableFile **r) override { return target_->NewAppendableFile(f, r); } - bool FileExists(const std::string& f) override { + bool FileExists(const std::string &f) override { return target_->FileExists(f); } - Status GetChildren(const std::string& dir, - std::vector* r) override { + Status GetChildren(const std::string &dir, + std::vector *r) override { return target_->GetChildren(dir, r); } - Status DeleteFile(const std::string& f) override { + Status DeleteFile(const std::string &f) override { return target_->DeleteFile(f); } - Status CreateDir(const std::string& d) override { + Status CreateDir(const std::string &d) override { return target_->CreateDir(d); } - Status DeleteDir(const std::string& d) override { + Status DeleteDir(const std::string &d) override { return target_->DeleteDir(d); } - Status GetFileSize(const std::string& f, uint64_t* s) override { + Status GetFileSize(const std::string &f, uint64_t *s) override { return target_->GetFileSize(f, s); } - Status RenameFile(const std::string& s, const std::string& t) override { + Status RenameFile(const std::string &s, const std::string &t) override { return target_->RenameFile(s, t); } - Status LockFile(const std::string& f, FileLock** l) override { + Status LockFile(const std::string &f, FileLock **l) override { return target_->LockFile(f, l); } - Status UnlockFile(FileLock* l) override { return target_->UnlockFile(l); } - void Schedule(void (*f)(void*), void* a) override { + Status UnlockFile(FileLock *l) override { return target_->UnlockFile(l); } + void Schedule(void (*f)(void *), void *a) override { return target_->Schedule(f, a); } - void StartThread(void (*f)(void*), void* a) override { + void StartThread(void (*f)(void *), void *a) override { return target_->StartThread(f, a); } - Status GetTestDirectory(std::string* path) override { + Status GetTestDirectory(std::string *path) override { return target_->GetTestDirectory(path); } - Status NewLogger(const std::string& fname, Logger** result) override { + Status NewLogger(const std::string &fname, Logger **result) override { return target_->NewLogger(fname, result); } - uint64_t NowMicros() override { - return target_->NowMicros(); - } + uint64_t NowMicros() override { return target_->NowMicros(); } void SleepForMicroseconds(int micros) override { target_->SleepForMicroseconds(micros); } - private: - Env* target_; +private: + Env *target_; }; -} // namespace leveldb +} // namespace leveldb // Redefine DeleteFile if necessary. #if defined(_WIN32) && defined(LEVELDB_DELETEFILE_UNDEFINED) @@ -383,7 +483,7 @@ class LEVELDB_EXPORT EnvWrapper : public Env { #define DeleteFile DeleteFileW #else #define DeleteFile DeleteFileA -#endif // defined(UNICODE) -#endif // defined(_WIN32) && defined(LEVELDB_DELETEFILE_UNDEFINED) +#endif // defined(UNICODE) +#endif // defined(_WIN32) && defined(LEVELDB_DELETEFILE_UNDEFINED) -#endif // STORAGE_LEVELDB_INCLUDE_ENV_H_ +#endif // STORAGE_LEVELDB_INCLUDE_ENV_H_ diff --git a/include/leveldb/iterator.h b/include/leveldb/iterator.h index 6c1d91b..9568d19 100644 --- a/include/leveldb/iterator.h +++ b/include/leveldb/iterator.h @@ -22,51 +22,89 @@ namespace leveldb { class LEVELDB_EXPORT Iterator { - public: +public: Iterator(); - Iterator(const Iterator&) = delete; - Iterator& operator=(const Iterator&) = delete; + Iterator(const Iterator &) = delete; + Iterator &operator=(const Iterator &) = delete; virtual ~Iterator(); // An iterator is either positioned at a key/value pair, or // not valid. This method returns true iff the iterator is valid. + + /** + * @brief 迭代器要么位于键/值对,要么无效。 + * @return 如果迭代器有效,该方法返回true。 + */ virtual bool Valid() const = 0; // Position at the first key in the source. The iterator is Valid() // after this call iff the source is not empty. + + /** + * @brief + * 在源中的第一个键处定位。如果源不为空,则在此调用之后迭代器为Valid()。 + */ virtual void SeekToFirst() = 0; // Position at the last key in the source. The iterator is // Valid() after this call iff the source is not empty. + + /** + * @brief + * 在源文件的最后一个键处定位。如果源不为空,则在此调用之后迭代器为Valid()。 + */ virtual void SeekToLast() = 0; // Position at the first key in the source that is at or past target. // The iterator is Valid() after this call iff the source contains // an entry that comes at or past target. - virtual void Seek(const Slice& target) = 0; + + /** + * @brief 在源中位于或超过目标的第一个键位置。 + * 如果源包含到达或超过目标的条目,则在此调用之后迭代器为Valid()。 + */ + virtual void Seek(const Slice &target) = 0; // Moves to the next entry in the source. After this call, Valid() is // true iff the iterator was not positioned at the last entry in the source. // REQUIRES: Valid() + + /** + * @brief 移动到源文件中的下一个条目。 + * 在调用此调用之后,如果迭代器没有定位在源中的最后一项,Valid()为真。 + */ virtual void Next() = 0; // Moves to the previous entry in the source. After this call, Valid() is // true iff the iterator was not positioned at the first entry in source. // REQUIRES: Valid() + + /** + * @brief 移动到源文件中的上一个条目。 + * 调用之后,如果迭代器没有定位在source中的第一个条目,Valid()为真。 + */ virtual void Prev() = 0; // Return the key for the current entry. The underlying storage for // the returned slice is valid only until the next modification of // the iterator. // REQUIRES: Valid() + + /** + * @brief 返回当前条目的键。返回切片的底层存储仅在迭代器的下一次修改之前有效。 + */ virtual Slice key() const = 0; // Return the value for the current entry. The underlying storage for // the returned slice is valid only until the next modification of // the iterator. // REQUIRES: Valid() + + /** + * @brief 返回当前条目的值。返回切片的底层存储仅在迭代器的下一次修改之前有效。 + */ virtual Slice value() const = 0; // If an error has occurred, return it. Else return an ok status. @@ -77,33 +115,41 @@ class LEVELDB_EXPORT Iterator { // // Note that unlike all of the preceding methods, this method is // not abstract and therefore clients should not override it. - using CleanupFunction = void (*)(void* arg1, void* arg2); - void RegisterCleanup(CleanupFunction function, void* arg1, void* arg2); + using CleanupFunction = void (*)(void *arg1, void *arg2); + void RegisterCleanup(CleanupFunction function, void *arg1, void *arg2); - private: +private: // Cleanup functions are stored in a single-linked list. // The list's head node is inlined in the iterator. + + /** + * 清理函数存储在一个单链表中。 + * 列表的头节点内联在迭代器中。 + */ struct CleanupNode { // The head node is used if the function pointer is not null. CleanupFunction function; - void* arg1; - void* arg2; - CleanupNode* next; + void *arg1; + void *arg2; + CleanupNode *next; // True if the node is not used. Only head nodes might be unused. bool IsEmpty() const { return function == nullptr; } // Invokes the cleanup function. - void Run() { assert(function != nullptr); (*function)(arg1, arg2); } + void Run() { + assert(function != nullptr); + (*function)(arg1, arg2); + } }; CleanupNode cleanup_head_; }; // Return an empty iterator (yields nothing). -LEVELDB_EXPORT Iterator* NewEmptyIterator(); +LEVELDB_EXPORT Iterator *NewEmptyIterator(); // Return an empty iterator with the specified status. -LEVELDB_EXPORT Iterator* NewErrorIterator(const Status& status); +LEVELDB_EXPORT Iterator *NewErrorIterator(const Status &status); -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_INCLUDE_ITERATOR_H_ +#endif // STORAGE_LEVELDB_INCLUDE_ITERATOR_H_ diff --git a/include/leveldb/options.h b/include/leveldb/options.h index 90aa19e..43f483c 100644 --- a/include/leveldb/options.h +++ b/include/leveldb/options.h @@ -5,8 +5,8 @@ #ifndef STORAGE_LEVELDB_INCLUDE_OPTIONS_H_ #define STORAGE_LEVELDB_INCLUDE_OPTIONS_H_ -#include #include "leveldb/export.h" +#include namespace leveldb { @@ -24,7 +24,7 @@ class Snapshot; enum CompressionType { // NOTE: do not change the values of existing entries, as these are // part of the persistent format on disk. - kNoCompression = 0x0, + kNoCompression = 0x0, kSnappyCompression = 0x1 }; @@ -39,9 +39,13 @@ struct LEVELDB_EXPORT Options { // REQUIRES: The client must ensure that the comparator supplied // here has the same name and orders keys *exactly* the same as the // comparator provided to previous open calls on the same DB. - const Comparator* comparator; + const Comparator *comparator; // If true, the database will be created if it is missing. + + /** + * @brief 如果为true,则在数据库缺失时创建该数据库。 + */ bool create_if_missing = false; // If true, an error is raised if the database already exists. @@ -52,17 +56,29 @@ struct LEVELDB_EXPORT Options { // errors. This may have unforeseen ramifications: for example, a // corruption of one DB entry may cause a large number of entries to // become unreadable or for the entire DB to become unopenable. + + /** + * @brief + * 如果为true,则实现将对正在处理的数据进行积极检查,如果检测到任何错误,将提前停止。 + * 这可能会产生不可预见的后果:例如,一个DB条目的损坏可能会导致大量条目变得不可读,或者整个DB变得不可打开。 + */ bool paranoid_checks = false; // Use the specified object to interact with the environment, // e.g. to read/write files, schedule background work, etc. // Default: Env::Default() - Env* env; + Env *env; // Any internal progress/error information generated by the db will // be written to info_log if it is non-null, or to a file stored // in the same directory as the DB contents if info_log is null. - Logger* info_log = nullptr; + + /** + * @brief + * 如果info_log不为空,则db生成的任何内部进度/错误信息将被写入到info_log中; + * 如果info_log为空,则写入到与db内容相同目录下的文件中。 + */ + Logger *info_log = nullptr; // ------------------- // Parameters that affect performance @@ -75,11 +91,25 @@ struct LEVELDB_EXPORT Options { // so you may wish to adjust this parameter to control memory usage. // Also, a larger write buffer will result in a longer recovery time // the next time the database is opened. + + /** + * @brief + * 在转换为已排序的磁盘文件之前,要在内存中构建的数据量(由磁盘上未排序的日志提供支持)。 + * + * 较大的值可以提高性能,特别是在批量加载时。 + * 最多可以同时在内存中保存两个写缓冲区,因此您可能希望调整此参数以控制内存使用。 + * 另外,更大的写缓冲区将导致下次打开数据库时更长的恢复时间。 + */ size_t write_buffer_size = 4 * 1024 * 1024; // Number of open files that can be used by the DB. You may need to // increase this if your database has a large working set (budget // one open file per 2MB of working set). + + /** + * @brief 数据库可以使用的打开文件数。 + * + */ int max_open_files = 1000; // Control over blocks (user data is stored in a set of blocks, and @@ -87,7 +117,7 @@ struct LEVELDB_EXPORT Options { // If non-null, use the specified cache for blocks. // If null, leveldb will automatically create and use an 8MB internal cache. - Cache* block_cache = nullptr; + Cache *block_cache = nullptr; // Approximate size of user data packed per block. Note that the // block size specified here corresponds to uncompressed data. The @@ -98,6 +128,12 @@ struct LEVELDB_EXPORT Options { // Number of keys between restart points for delta encoding of keys. // This parameter can be changed dynamically. Most clients should // leave this parameter alone. + + /** + * @brief key增量编码的restart points之间的keys数量。 + * + * 该参数可以动态修改。大多数客户端不应该使用这个参数。 + */ int block_restart_interval = 16; // Leveldb will write up to this amount of bytes to a file before @@ -130,12 +166,19 @@ struct LEVELDB_EXPORT Options { // when a database is opened. This can significantly speed up open. // // Default: currently false, but may become true later. + + /** + * @brief + * EXPERIMENTAL:如果为true,则在打开数据库时追加到现有的MANIFEST和日志文件。这可以显著加快打开速度。 + * + * Default:当前为false,但以后可能变为true。 + */ bool reuse_logs = false; // If non-null, use the specified filter policy to reduce disk reads. // Many applications will benefit from passing the result of // NewBloomFilterPolicy() here. - const FilterPolicy* filter_policy = nullptr; + const FilterPolicy *filter_policy = nullptr; // Create an Options object with default values for all fields. Options(); @@ -145,17 +188,32 @@ struct LEVELDB_EXPORT Options { struct LEVELDB_EXPORT ReadOptions { // If true, all data read from underlying storage will be // verified against corresponding checksums. + + /** + * @brief 如果为true,则将根据相应的校验和验证从底层存储读取的所有数据。 + */ bool verify_checksums = false; // Should the data read for this iteration be cached in memory? // Callers may wish to set this field to false for bulk scans. + + /** + * @brief + * 这个迭代所读取的数据应该缓存在内存中吗?对于批量扫描,调用方可能希望将此字段设置为false。 + */ bool fill_cache = true; // If "snapshot" is non-null, read as of the supplied snapshot // (which must belong to the DB that is being read and which must // not have been released). If "snapshot" is null, use an implicit // snapshot of the state at the beginning of this read operation. - const Snapshot* snapshot = nullptr; + + /** + * @brief + * 如果"snapshot"非空,则读取所提供的快照(该快照必须属于正在读取的DB,并且必须未被释放)。 + * 如果"snapshot"为null,则使用该读操作开始时的状态的隐式快照。 + */ + const Snapshot *snapshot = nullptr; ReadOptions() = default; }; @@ -181,6 +239,6 @@ struct LEVELDB_EXPORT WriteOptions { WriteOptions() = default; }; -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_INCLUDE_OPTIONS_H_ +#endif // STORAGE_LEVELDB_INCLUDE_OPTIONS_H_ diff --git a/include/leveldb/table.h b/include/leveldb/table.h index e9f6641..5423667 100644 --- a/include/leveldb/table.h +++ b/include/leveldb/table.h @@ -5,9 +5,9 @@ #ifndef STORAGE_LEVELDB_INCLUDE_TABLE_H_ #define STORAGE_LEVELDB_INCLUDE_TABLE_H_ -#include #include "leveldb/export.h" #include "leveldb/iterator.h" +#include namespace leveldb { @@ -23,7 +23,7 @@ class TableCache; // immutable and persistent. A Table may be safely accessed from // multiple threads without external synchronization. class LEVELDB_EXPORT Table { - public: +public: // Attempt to open the table that is stored in bytes [0..file_size) // of "file", and read the metadata entries necessary to allow // retrieving data from the table. @@ -36,20 +36,34 @@ class LEVELDB_EXPORT Table { // for the duration of the returned table's lifetime. // // *file must remain live while this Table is in use. - static Status Open(const Options& options, - RandomAccessFile* file, - uint64_t file_size, - Table** table); - Table(const Table&) = delete; - void operator=(const Table&) = delete; + /** + * @brief 尝试打开以bytes[0..file_size]存储的“file”表, + * 并读取从表中检索数据所需的 metadata entries。 + * + * 如果成功,返回ok并将“*table”设置为新打开的表。 + * 当不再需要时,客户端应该删除“*table”。 + * 如果在初始化表时出现错误,则将“*table”设置为nullptr并返回non-ok状态。 + * 不获取“*source”的所有权,但客户端必须确保“source”在返回表的生命周期内保持有效。 + * @param *file 必须在该表被使用时保持活动状态。 + */ + static Status Open(const Options &options, RandomAccessFile *file, + uint64_t file_size, Table **table); + + Table(const Table &) = delete; + void operator=(const Table &) = delete; ~Table(); // Returns a new iterator over the table contents. // The result of NewIterator() is initially invalid (caller must // call one of the Seek methods on the iterator before using it). - Iterator* NewIterator(const ReadOptions&) const; + + /** + * @brief 返回表内容上的新迭代器。 + * NewIterator()的结果最初是无效的(调用者在使用迭代器之前必须调用其中一个Seek方法)。 + */ + Iterator *NewIterator(const ReadOptions &) const; // Given a key, return an approximate byte offset in the file where // the data for that key begins (or would begin if the key were @@ -57,29 +71,38 @@ class LEVELDB_EXPORT Table { // bytes, and so includes effects like compression of the underlying data. // E.g., the approximate offset of the last key in the table will // be close to the file length. - uint64_t ApproximateOffsetOf(const Slice& key) const; - private: + /** + * @brief + * 给定一个键,返回该键数据开始的文件中的一个近似字节偏移量(如果该键存在于文件中,则返回该键数据开始的位置)。 + * 返回值以文件字节为单位,因此包含诸如压缩底层数据之类的效果。例如,表中最后一个键的近似偏移量将接近文件长度。 + */ + uint64_t ApproximateOffsetOf(const Slice &key) const; + +private: struct Rep; - Rep* rep_; + Rep *rep_; - explicit Table(Rep* rep) { rep_ = rep; } - static Iterator* BlockReader(void*, const ReadOptions&, const Slice&); + explicit Table(Rep *rep) { rep_ = rep; } + static Iterator *BlockReader(void *, const ReadOptions &, const Slice &); // Calls (*handle_result)(arg, ...) with the entry found after a call // to Seek(key). May not make such a call if filter policy says // that key is not present. friend class TableCache; - Status InternalGet( - const ReadOptions&, const Slice& key, - void* arg, - void (*handle_result)(void* arg, const Slice& k, const Slice& v)); + /** + * 调用 (*handle_result)(arg, ...) 并在调用 Seek(key) 后找到条目。 + * 如果筛选策略显示该键不存在,则可能不会进行此类调用。 + */ + Status InternalGet(const ReadOptions &, const Slice &key, void *arg, + void (*handle_result)(void *arg, const Slice &k, + const Slice &v)); - void ReadMeta(const Footer& footer); - void ReadFilter(const Slice& filter_handle_value); + void ReadMeta(const Footer &footer); + void ReadFilter(const Slice &filter_handle_value); }; -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_INCLUDE_TABLE_H_ +#endif // STORAGE_LEVELDB_INCLUDE_TABLE_H_ diff --git a/include/leveldb/table_builder.h b/include/leveldb/table_builder.h index 8d05d33..5863e76 100644 --- a/include/leveldb/table_builder.h +++ b/include/leveldb/table_builder.h @@ -13,10 +13,10 @@ #ifndef STORAGE_LEVELDB_INCLUDE_TABLE_BUILDER_H_ #define STORAGE_LEVELDB_INCLUDE_TABLE_BUILDER_H_ -#include #include "leveldb/export.h" #include "leveldb/options.h" #include "leveldb/status.h" +#include namespace leveldb { @@ -25,14 +25,14 @@ class BlockHandle; class WritableFile; class LEVELDB_EXPORT TableBuilder { - public: +public: // Create a builder that will store the contents of the table it is // building in *file. Does not close the file. It is up to the // caller to close the file after calling Finish(). - TableBuilder(const Options& options, WritableFile* file); + TableBuilder(const Options &options, WritableFile *file); - TableBuilder(const TableBuilder&) = delete; - void operator=(const TableBuilder&) = delete; + TableBuilder(const TableBuilder &) = delete; + void operator=(const TableBuilder &) = delete; // REQUIRES: Either Finish() or Abandon() has been called. ~TableBuilder(); @@ -43,25 +43,57 @@ class LEVELDB_EXPORT TableBuilder { // passed to the constructor is different from its value in the // structure passed to this method, this method will return an error // without changing any fields. - Status ChangeOptions(const Options& options); + + /** + * @brief 更改此构建器使用的选项。 + * @note 只有部分选项字段可以在构建后更改。如果不允许动态更改字段, + * 并且传递给构造函数的结构中的值与传递给此方法的结构中的值不同, + * 则此方法将在不更改任何字段的情况下返回错误。 + */ + Status ChangeOptions(const Options &options); // Add key,value to the table being constructed. // REQUIRES: key is after any previously added key according to comparator. // REQUIRES: Finish(), Abandon() have not been called - void Add(const Slice& key, const Slice& value); + + /** + * @brief 向正在构造的表添加键、值。 + * + * REQUIRES: 根据比较器,键位于先前添加的键之后。 + * + * REQUIRES: 没有调用Finish(), Abandon() + * + */ + void Add(const Slice &key, const Slice &value); // Advanced operation: flush any buffered key/value pairs to file. // Can be used to ensure that two adjacent entries never live in // the same data block. Most clients should not need to use this method. // REQUIRES: Finish(), Abandon() have not been called + + /** + * @brief 高级操作:将所有缓冲的键/值对刷新到文件中。 + * + * 可以用来确保两个相邻的条目永远不会存在于同一个数据块中。大多数客户端不需要使用这种方法。 + * + * REQUIRES: 没有调用Finish(), Abandon() + */ void Flush(); // Return non-ok iff some error has been detected. + + /** + * @brief 如果有错误发生,返回non-ok + */ Status status() const; // Finish building the table. Stops using the file passed to the // constructor after this function returns. // REQUIRES: Finish(), Abandon() have not been called + + /** + * @brief 完成表的构建。在此函数返回后停止使用传递给构造函数的文件。 + */ Status Finish(); // Indicate that the contents of this builder should be abandoned. Stops @@ -69,24 +101,39 @@ class LEVELDB_EXPORT TableBuilder { // If the caller is not going to call Finish(), it must call Abandon() // before destroying this builder. // REQUIRES: Finish(), Abandon() have not been called + + /** + * @brief 指示应该放弃此构造器的内容。 + * 在此函数返回后停止使用传递给构造函数的文件。 + * 如果调用者不打算调用Finish(),它必须在销毁这个构造器之前调用Abandon()。 + */ void Abandon(); // Number of calls to Add() so far. + + /** + * @brief 到目前为止调用Add()的次数。 + */ uint64_t NumEntries() const; // Size of the file generated so far. If invoked after a successful // Finish() call, returns the size of the final generated file. + + /** + * @brief 到目前为止生成的文件大小。 + * 如果在成功调用Finish()之后调用,则返回最终生成文件的大小。 + */ uint64_t FileSize() const; - private: +private: bool ok() const { return status().ok(); } - void WriteBlock(BlockBuilder* block, BlockHandle* handle); - void WriteRawBlock(const Slice& data, CompressionType, BlockHandle* handle); + void WriteBlock(BlockBuilder *block, BlockHandle *handle); + void WriteRawBlock(const Slice &data, CompressionType, BlockHandle *handle); struct Rep; - Rep* rep_; + Rep *rep_; }; -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_INCLUDE_TABLE_BUILDER_H_ +#endif // STORAGE_LEVELDB_INCLUDE_TABLE_BUILDER_H_ diff --git a/include/leveldb/write_batch.h b/include/leveldb/write_batch.h index 9b319f0..767eb2f 100644 --- a/include/leveldb/write_batch.h +++ b/include/leveldb/write_batch.h @@ -21,37 +21,63 @@ #ifndef STORAGE_LEVELDB_INCLUDE_WRITE_BATCH_H_ #define STORAGE_LEVELDB_INCLUDE_WRITE_BATCH_H_ -#include #include "leveldb/export.h" #include "leveldb/status.h" +#include namespace leveldb { class Slice; +/** + * @brief WriteBatch保存了一组自动应用于数据库的更新。 + * + * 更新按照添加到WriteBatch的顺序应用。例如,写入以下批后,key的值将为v3: + * batch.Put("key", "v1"); + * batch.Delete("key"); + * batch.Put("key", "v2"); + * batch.Put("key", "v3"); + * + * 多个线程可以在没有外部同步的情况下调用WriteBatch上的const method, + * 但是如果任何一个线程可以调用non-const method, + * 那么访问同一个WriteBatch的所有线程都必须使用外部同步。 + */ class LEVELDB_EXPORT WriteBatch { - public: +public: WriteBatch(); // Intentionally copyable. - WriteBatch(const WriteBatch&) = default; - WriteBatch& operator =(const WriteBatch&) = default; + WriteBatch(const WriteBatch &) = default; + WriteBatch &operator=(const WriteBatch &) = default; ~WriteBatch(); // Store the mapping "key->value" in the database. - void Put(const Slice& key, const Slice& value); + /** + * @brief 将映射“key->value”存储在数据库中。 + */ + void Put(const Slice &key, const Slice &value); // If the database contains a mapping for "key", erase it. Else do nothing. - void Delete(const Slice& key); + /** + * @brief 如果数据库包含“key”的映射,则擦除它。否则什么都不做。 + */ + void Delete(const Slice &key); // Clear all updates buffered in this batch. + /** + * @brief 清除此批处理中缓存的所有更新。 + */ void Clear(); // The size of the database changes caused by this batch. // // This number is tied to implementation details, and may change across // releases. It is intended for LevelDB usage metrics. + /** + * @brief 此批处理导致数据库大小发生变化。 + * 这个数字与实现细节有关,并且可能在不同版本之间发生变化。它用于LevelDB使用度量。 + */ size_t ApproximateSize() const; // Copies the operations in "source" to this batch. @@ -59,23 +85,39 @@ class LEVELDB_EXPORT WriteBatch { // This runs in O(source size) time. However, the constant factor is better // than calling Iterate() over the source batch with a Handler that replicates // the operations into this batch. - void Append(const WriteBatch& source); + /** + * 将source中的操作复制到此批处理。 + * + * 这在O(source size)时间内运行。 + * 但是,常量因素比使用Handler在源批处理上调用Iterate()要好,该Handler将操作复制到此批处理中。 + */ + void Append(const WriteBatch &source); // Support for iterating over the contents of a batch. + /** + * @brief 用于迭代时执行的执行器 + * 当我们调用迭代器时 + */ class Handler { - public: + public: virtual ~Handler(); - virtual void Put(const Slice& key, const Slice& value) = 0; - virtual void Delete(const Slice& key) = 0; + virtual void Put(const Slice &key, const Slice &value) = 0; + virtual void Delete(const Slice &key) = 0; }; - Status Iterate(Handler* handler) const; - - private: + /** + * @brief 遍历每个元素,并对每个元素执行此执行器 + * + * 场景,如创建一个MemTableInserter,用以向Memtable插入或删除数据。 + * 遍历WriteBatch的每个元素,根据每个元素的类型(插入或删除),调用Memtable的插入或删除。 + */ + Status Iterate(Handler *handler) const; + +private: friend class WriteBatchInternal; - std::string rep_; // See comment in write_batch.cc for the format of rep_ + std::string rep_; // See comment in write_batch.cc for the format of rep_ }; -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_INCLUDE_WRITE_BATCH_H_ +#endif // STORAGE_LEVELDB_INCLUDE_WRITE_BATCH_H_ -- Gitee From c6e68748f19ff12b6a0f62610957cb2e08728cf5 Mon Sep 17 00:00:00 2001 From: hellolutar Date: Sat, 12 Oct 2024 17:48:53 +0800 Subject: [PATCH 02/22] chore: some comments --- db/db_impl.cc | 24 ++++-- db/db_impl.h | 4 +- db/dbformat.cc | 7 +- db/dbformat.h | 16 +++- db/log_writer.cc | 33 ++++---- db/memtable.cc | 98 ++++++++++++------------ db/version_edit.h | 59 ++++++++------- db/version_set.cc | 1 + db/version_set.h | 149 +++++++++++++++++++++++++++++++------ doc/learn/db.md | 149 ++++++++++++++++++++++++++++++++++++- include/leveldb/iterator.h | 10 +++ table/iterator_wrapper.h | 71 ++++++++++++------ table/merger.h | 17 ++++- util/coding.h | 80 ++++++++++++-------- util/logging.h | 36 +++++++-- 15 files changed, 565 insertions(+), 189 deletions(-) diff --git a/db/db_impl.cc b/db/db_impl.cc index a826cd2..438206a 100644 --- a/db/db_impl.cc +++ b/db/db_impl.cc @@ -569,6 +569,7 @@ void DBImpl::CompactRange(const Slice *begin, const Slice *end) { } TEST_CompactMemTable(); // TODO(sanjay): Skip if memtable does not overlap for (int level = 0; level < max_level_with_files; level++) { + // 压缩逻辑 TEST_CompactRange(level, begin, end); } } @@ -601,6 +602,7 @@ void DBImpl::TEST_CompactRange(int level, const Slice *begin, bg_error_.ok()) { if (manual_compaction_ == nullptr) { // Idle manual_compaction_ = &manual; + // 压缩逻辑 MaybeScheduleCompaction(); } else { // Running either my compaction or another compaction. background_work_finished_signal_.Wait(); @@ -614,6 +616,7 @@ void DBImpl::TEST_CompactRange(int level, const Slice *begin, Status DBImpl::TEST_CompactMemTable() { // nullptr batch means just wait for earlier writes to be done + // nullptr批处理意味着只等待先前的写操作完成,即先让所有写完成 Status s = Write(WriteOptions(), nullptr); if (s.ok()) { // Wait until the compaction completes @@ -665,6 +668,7 @@ void DBImpl::BackgroundCall() { } else if (!bg_error_.ok()) { // No more background work after a background error. } else { + // 压缩逻辑 BackgroundCompaction(); } @@ -672,6 +676,7 @@ void DBImpl::BackgroundCall() { // Previous compaction may have produced too many files in a level, // so reschedule another compaction if needed. + // 以前的压缩可能在一个级别中产生了太多的文件,因此如果需要,请重新安排另一次压缩。 MaybeScheduleCompaction(); background_work_finished_signal_.SignalAll(); } @@ -1058,22 +1063,28 @@ Iterator *DBImpl::NewInternalIterator(const ReadOptions &options, *latest_snapshot = versions_->LastSequence(); // Collect together all needed child iterators + // 收集所有需要的子迭代器 std::vector list; - list.push_back(mem_->NewIterator()); + list.push_back(mem_->NewIterator()); // 收集mem迭代器 mem_->Ref(); - if (imm_ != nullptr) { + if (imm_ != nullptr) { // 收集imm迭代器 list.push_back(imm_->NewIterator()); imm_->Ref(); } + // 收集level0的所有文件的迭代器 versions_->current()->AddIterators(options, &list); + // 合并所有迭代器为一个迭代器 Iterator *internal_iter = NewMergingIterator(&internal_comparator_, &list[0], list.size()); versions_->current()->Ref(); IterState *cleanup = new IterState(&mutex_, mem_, imm_, versions_->current()); + + // 注册一个迭代器销毁时需调用的函数,当CleanupIteratorState被调用时,unref + // cleanup中的对象 internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, nullptr); - *seed = ++seed_; + *seed = ++seed_; // todo(lutar) 不理解具体的作用 mutex_.Unlock(); return internal_iter; } @@ -1116,6 +1127,7 @@ Status DBImpl::Get(const ReadOptions &options, const Slice &key, { mutex_.Unlock(); // First look in the memtable, then in the immutable memtable (if any). + // 先在memtable中找,没找到则在immtable中找,没找到则在current version中查找 LookupKey lkey(key, snapshot); if (mem->Get(lkey, value, &s)) { // Done @@ -1194,6 +1206,7 @@ Status DBImpl::Write(const WriteOptions &options, WriteBatch *my_batch) { // May temporarily unlock and wait. Status status = MakeRoomForWrite(my_batch == nullptr); + // 从versionset中获取last_sequence uint64_t last_sequence = versions_->LastSequence(); Writer *last_writer = &w; if (status.ok() && my_batch != nullptr) { // nullptr batch is for compactions @@ -1207,6 +1220,7 @@ Status DBImpl::Write(const WriteOptions &options, WriteBatch *my_batch) { // into mem_. { mutex_.Unlock(); + // 先写log,然后写memtable status = log_->AddRecord(WriteBatchInternal::Contents(updates)); bool sync_error = false; if (status.ok() && options.sync) { @@ -1226,9 +1240,9 @@ Status DBImpl::Write(const WriteOptions &options, WriteBatch *my_batch) { RecordBackgroundError(status); } } - if (updates == tmp_batch_) + if (updates == tmp_batch_) // todo(lutar) 这里不理解 tmp_batch_->Clear(); - + // 更新versionset的lastsequence versions_->SetLastSequence(last_sequence); } diff --git a/db/db_impl.h b/db/db_impl.h index 78ec079..672a5bb 100644 --- a/db/db_impl.h +++ b/db/db_impl.h @@ -181,7 +181,9 @@ private: */ bool background_compaction_scheduled_ GUARDED_BY(mutex_); - // Information for a manual compaction + /** + * @brief Information for a manual compaction + */ struct ManualCompaction { int level; bool done; diff --git a/db/dbformat.cc b/db/dbformat.cc index 20a7ca4..b82fe6a 100644 --- a/db/dbformat.cc +++ b/db/dbformat.cc @@ -127,11 +127,14 @@ LookupKey::LookupKey(const Slice& user_key, SequenceNumber s) { } else { dst = new char[needed]; } - start_ = dst; + start_ = dst; // size开始的位置 + // 编码entry大小 dst = EncodeVarint32(dst, usize + 8); - kstart_ = dst; + kstart_ = dst; // user key 开始的位置 + // 编码 user key memcpy(dst, user_key.data(), usize); dst += usize; + // 编码sequence number, type EncodeFixed64(dst, PackSequenceAndType(s, kValueTypeForSeek)); dst += 8; end_ = dst; diff --git a/db/dbformat.h b/db/dbformat.h index a8bf9fa..e5b0d50 100644 --- a/db/dbformat.h +++ b/db/dbformat.h @@ -47,7 +47,8 @@ static const int kL0_StopWritesTrigger = 12; /* 如果新的压缩后的memtable不会产生重叠,则将其推至的最高级别。 我们尝试推到级别2,以避免相对昂贵的级别0=>1压缩,并避免一些昂贵的manifest文件操作。 -我们不会一直推到最大级别,因为如果重复覆盖相同的key space,可能会产生大量浪费的磁盘空间。 +我们不会一直推到最大级别,因为如果重复覆盖相同的key +space,可能会产生大量浪费的磁盘空间。 */ static const int kMaxMemCompactLevel = 2; @@ -202,12 +203,25 @@ public: ~LookupKey(); // Return a key suitable for lookup in a MemTable. + /** + * @brief 返回适用于MemTbale查找的key + * + * 格式:size, key, sequence number, type + */ Slice memtable_key() const { return Slice(start_, end_ - start_); } // Return an internal key (suitable for passing to an internal iterator) + /** + * @brief 返回internal key (适用于传递给internal iterator) + * + * 格式:key, sequence number, type + */ Slice internal_key() const { return Slice(kstart_, end_ - kstart_); } // Return the user key + /** + * @brief 返回user key,即key,value 的key + */ Slice user_key() const { return Slice(kstart_, end_ - kstart_ - 8); } private: diff --git a/db/log_writer.cc b/db/log_writer.cc index 74a0327..b770586 100644 --- a/db/log_writer.cc +++ b/db/log_writer.cc @@ -4,37 +4,34 @@ #include "db/log_writer.h" -#include #include "leveldb/env.h" #include "util/coding.h" #include "util/crc32c.h" +#include namespace leveldb { namespace log { -static void InitTypeCrc(uint32_t* type_crc) { +static void InitTypeCrc(uint32_t *type_crc) { for (int i = 0; i <= kMaxRecordType; i++) { char t = static_cast(i); type_crc[i] = crc32c::Value(&t, 1); } } -Writer::Writer(WritableFile* dest) - : dest_(dest), - block_offset_(0) { +Writer::Writer(WritableFile *dest) : dest_(dest), block_offset_(0) { InitTypeCrc(type_crc_); } -Writer::Writer(WritableFile* dest, uint64_t dest_length) +Writer::Writer(WritableFile *dest, uint64_t dest_length) : dest_(dest), block_offset_(dest_length % kBlockSize) { InitTypeCrc(type_crc_); } -Writer::~Writer() { -} +Writer::~Writer() {} -Status Writer::AddRecord(const Slice& slice) { - const char* ptr = slice.data(); +Status Writer::AddRecord(const Slice &slice) { + const char *ptr = slice.data(); size_t left = slice.size(); // Fragment the record if necessary and emit it. Note that if slice @@ -45,6 +42,7 @@ Status Writer::AddRecord(const Slice& slice) { do { const int leftover = kBlockSize - block_offset_; assert(leftover >= 0); + // block剩余的空间已经无法存下一个header,则将block剩余空间全部填充为0 if (leftover < kHeaderSize) { // Switch to a new block if (leftover > 0) { @@ -59,6 +57,7 @@ Status Writer::AddRecord(const Slice& slice) { assert(kBlockSize - block_offset_ - kHeaderSize >= 0); const size_t avail = kBlockSize - block_offset_ - kHeaderSize; + // min(content大小,可用空间) const size_t fragment_length = (left < avail) ? left : avail; RecordType type; @@ -81,8 +80,8 @@ Status Writer::AddRecord(const Slice& slice) { return s; } -Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) { - assert(n <= 0xffff); // Must fit in two bytes +Status Writer::EmitPhysicalRecord(RecordType t, const char *ptr, size_t n) { + assert(n <= 0xffff); // Must fit in two bytes assert(block_offset_ + kHeaderSize + n <= kBlockSize); // Format the header @@ -93,13 +92,13 @@ Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) { // Compute the crc of the record type and the payload. uint32_t crc = crc32c::Extend(type_crc_[t], ptr, n); - crc = crc32c::Mask(crc); // Adjust for storage + crc = crc32c::Mask(crc); // Adjust for storage EncodeFixed32(buf, crc); // Write the header and the payload - Status s = dest_->Append(Slice(buf, kHeaderSize)); + Status s = dest_->Append(Slice(buf, kHeaderSize)); // header if (s.ok()) { - s = dest_->Append(Slice(ptr, n)); + s = dest_->Append(Slice(ptr, n)); // payload if (s.ok()) { s = dest_->Flush(); } @@ -108,5 +107,5 @@ Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) { return s; } -} // namespace log -} // namespace leveldb +} // namespace log +} // namespace leveldb diff --git a/db/memtable.cc b/db/memtable.cc index 287afdb..e909eb4 100644 --- a/db/memtable.cc +++ b/db/memtable.cc @@ -11,27 +11,27 @@ namespace leveldb { -static Slice GetLengthPrefixedSlice(const char* data) { +/** + * @brief 从char*中获取slice + * + * NOTE: 要求格式满足:size(varint32), data(char[size]) + */ +static Slice GetLengthPrefixedSlice(const char *data) { uint32_t len; - const char* p = data; - p = GetVarint32Ptr(p, p + 5, &len); // +5: we assume "p" is not corrupted + const char *p = data; + p = GetVarint32Ptr(p, p + 5, &len); // +5: we assume "p" is not corrupted return Slice(p, len); } -MemTable::MemTable(const InternalKeyComparator& cmp) - : comparator_(cmp), - refs_(0), - table_(comparator_, &arena_) { -} +MemTable::MemTable(const InternalKeyComparator &cmp) + : comparator_(cmp), refs_(0), table_(comparator_, &arena_) {} -MemTable::~MemTable() { - assert(refs_ == 0); -} +MemTable::~MemTable() { assert(refs_ == 0); } size_t MemTable::ApproximateMemoryUsage() { return arena_.MemoryUsage(); } -int MemTable::KeyComparator::operator()(const char* aptr, const char* bptr) - const { +int MemTable::KeyComparator::operator()(const char *aptr, + const char *bptr) const { // Internal keys are encoded as length-prefixed strings. Slice a = GetLengthPrefixedSlice(aptr); Slice b = GetLengthPrefixedSlice(bptr); @@ -41,19 +41,19 @@ int MemTable::KeyComparator::operator()(const char* aptr, const char* bptr) // Encode a suitable internal key target for "target" and return it. // Uses *scratch as scratch space, and the returned pointer will point // into this scratch space. -static const char* EncodeKey(std::string* scratch, const Slice& target) { +static const char *EncodeKey(std::string *scratch, const Slice &target) { scratch->clear(); PutVarint32(scratch, target.size()); scratch->append(target.data(), target.size()); return scratch->data(); } -class MemTableIterator: public Iterator { - public: - explicit MemTableIterator(MemTable::Table* table) : iter_(table) { } +class MemTableIterator : public Iterator { +public: + explicit MemTableIterator(MemTable::Table *table) : iter_(table) {} virtual bool Valid() const { return iter_.Valid(); } - virtual void Seek(const Slice& k) { iter_.Seek(EncodeKey(&tmp_, k)); } + virtual void Seek(const Slice &k) { iter_.Seek(EncodeKey(&tmp_, k)); } virtual void SeekToFirst() { iter_.SeekToFirst(); } virtual void SeekToLast() { iter_.SeekToLast(); } virtual void Next() { iter_.Next(); } @@ -66,22 +66,19 @@ class MemTableIterator: public Iterator { virtual Status status() const { return Status::OK(); } - private: +private: MemTable::Table::Iterator iter_; - std::string tmp_; // For passing to EncodeKey + std::string tmp_; // For passing to EncodeKey // No copying allowed - MemTableIterator(const MemTableIterator&); - void operator=(const MemTableIterator&); + MemTableIterator(const MemTableIterator &); + void operator=(const MemTableIterator &); }; -Iterator* MemTable::NewIterator() { - return new MemTableIterator(&table_); -} +Iterator *MemTable::NewIterator() { return new MemTableIterator(&table_); } -void MemTable::Add(SequenceNumber s, ValueType type, - const Slice& key, - const Slice& value) { +void MemTable::Add(SequenceNumber s, ValueType type, const Slice &key, + const Slice &value) { // Format of an entry is concatenation of: // key_size : varint32 of internal_key.size() // key bytes : char[internal_key.size()] @@ -90,22 +87,26 @@ void MemTable::Add(SequenceNumber s, ValueType type, size_t key_size = key.size(); size_t val_size = value.size(); size_t internal_key_size = key_size + 8; - const size_t encoded_len = - VarintLength(internal_key_size) + internal_key_size + - VarintLength(val_size) + val_size; - char* buf = arena_.Allocate(encoded_len); - char* p = EncodeVarint32(buf, internal_key_size); + const size_t encoded_len = VarintLength(internal_key_size) + + internal_key_size + VarintLength(val_size) + + val_size; + char *buf = arena_.Allocate(encoded_len); + // 编码key + char *p = EncodeVarint32(buf, internal_key_size); memcpy(p, key.data(), key_size); p += key_size; + // 编码sequence number, type EncodeFixed64(p, (s << 8) | type); p += 8; + // 编码value p = EncodeVarint32(p, val_size); memcpy(p, value.data(), val_size); assert(p + val_size == buf + encoded_len); + table_.Insert(buf); } -bool MemTable::Get(const LookupKey& key, std::string* value, Status* s) { +bool MemTable::Get(const LookupKey &key, std::string *value, Status *s) { Slice memkey = key.memtable_key(); Table::Iterator iter(&table_); iter.Seek(memkey.data()); @@ -119,27 +120,30 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s) { // Check that it belongs to same user key. We do not check the // sequence number since the Seek() call above should have skipped // all entries with overly large sequence numbers. - const char* entry = iter.key(); + // 检查它是否属于相同的用户密钥。我们不检查序列号,因为上述的的Seek()调用应该跳过所有序列号过大的条目。 + const char *entry = iter.key(); uint32_t key_length; - const char* key_ptr = GetVarint32Ptr(entry, entry+5, &key_length); + // 因为是获取varint32,即4-byte,所以将limit限制在5即可 + const char *key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length); + auto parsed_user_key = Slice(key_ptr, key_length - 8); if (comparator_.comparator.user_comparator()->Compare( - Slice(key_ptr, key_length - 8), - key.user_key()) == 0) { + parsed_user_key, key.user_key()) == 0) { // Correct user key const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8); switch (static_cast(tag & 0xff)) { - case kTypeValue: { - Slice v = GetLengthPrefixedSlice(key_ptr + key_length); - value->assign(v.data(), v.size()); - return true; - } - case kTypeDeletion: - *s = Status::NotFound(Slice()); - return true; + case kTypeValue: { + // 获取value:将指针直接跳到value开始的位置去获取 + Slice v = GetLengthPrefixedSlice(key_ptr + key_length); + value->assign(v.data(), v.size()); + return true; + } + case kTypeDeletion: + *s = Status::NotFound(Slice()); // 该entry已被删除 + return true; } } } return false; } -} // namespace leveldb +} // namespace leveldb diff --git a/db/version_edit.h b/db/version_edit.h index 0262193..52e696b 100644 --- a/db/version_edit.h +++ b/db/version_edit.h @@ -5,10 +5,10 @@ #ifndef STORAGE_LEVELDB_DB_VERSION_EDIT_H_ #define STORAGE_LEVELDB_DB_VERSION_EDIT_H_ +#include "db/dbformat.h" #include #include #include -#include "db/dbformat.h" namespace leveldb { @@ -16,23 +16,28 @@ class VersionSet; struct FileMetaData { int refs; - int allowed_seeks; // Seeks allowed until compaction + + // Seeks allowed until compaction + /** + * @brief 允许seek直到compaction为止 + */ + int allowed_seeks; uint64_t number; - uint64_t file_size; // File size in bytes - InternalKey smallest; // Smallest internal key served by table - InternalKey largest; // Largest internal key served by table + uint64_t file_size; // File size in bytes + InternalKey smallest; // Smallest internal key served by table + InternalKey largest; // Largest internal key served by table - FileMetaData() : refs(0), allowed_seeks(1 << 30), file_size(0) { } + FileMetaData() : refs(0), allowed_seeks(1 << 30), file_size(0) {} }; class VersionEdit { - public: +public: VersionEdit() { Clear(); } - ~VersionEdit() { } + ~VersionEdit() {} void Clear(); - void SetComparatorName(const Slice& name) { + void SetComparatorName(const Slice &name) { has_comparator_ = true; comparator_ = name.ToString(); } @@ -52,7 +57,7 @@ class VersionEdit { has_last_sequence_ = true; last_sequence_ = seq; } - void SetCompactPointer(int level, const InternalKey& key) { + void SetCompactPointer(int level, const InternalKey &key) { compact_pointers_.push_back(std::make_pair(level, key)); } @@ -61,16 +66,14 @@ class VersionEdit { // REQUIRES: "smallest" and "largest" are smallest and largest keys in file /** - * 添加 信息 - * - * 要求: 此版本未被保存(参见VersionSet::SaveTo) - * - * 要求: "smallest"和 "largest"是文件中最小和最大的key - */ - void AddFile(int level, uint64_t file, - uint64_t file_size, - const InternalKey& smallest, - const InternalKey& largest) { + * 添加 信息 + * + * 要求: 此版本未被保存(参见VersionSet::SaveTo) + * + * 要求: "smallest"和 "largest"是文件中最小和最大的key + */ + void AddFile(int level, uint64_t file, uint64_t file_size, + const InternalKey &smallest, const InternalKey &largest) { FileMetaData f; f.number = file; f.file_size = file_size; @@ -84,15 +87,15 @@ class VersionEdit { deleted_files_.insert(std::make_pair(level, file)); } - void EncodeTo(std::string* dst) const; - Status DecodeFrom(const Slice& src); + void EncodeTo(std::string *dst) const; + Status DecodeFrom(const Slice &src); std::string DebugString() const; - private: +private: friend class VersionSet; - typedef std::set< std::pair > DeletedFileSet; + typedef std::set> DeletedFileSet; std::string comparator_; uint64_t log_number_; @@ -105,11 +108,11 @@ class VersionEdit { bool has_next_file_number_; bool has_last_sequence_; - std::vector< std::pair > compact_pointers_; + std::vector> compact_pointers_; DeletedFileSet deleted_files_; - std::vector< std::pair > new_files_; + std::vector> new_files_; }; -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_DB_VERSION_EDIT_H_ +#endif // STORAGE_LEVELDB_DB_VERSION_EDIT_H_ diff --git a/db/version_set.cc b/db/version_set.cc index 56493ac..124ae6a 100644 --- a/db/version_set.cc +++ b/db/version_set.cc @@ -234,6 +234,7 @@ Iterator* Version::NewConcatenatingIterator(const ReadOptions& options, void Version::AddIterators(const ReadOptions& options, std::vector* iters) { // Merge all level zero files together since they may overlap + // 合并所有level0文件,因为它们可能重叠 for (size_t i = 0; i < files_[0].size(); i++) { iters->push_back( vset_->table_cache_->NewIterator( diff --git a/db/version_set.h b/db/version_set.h index cb4d814..7826297 100644 --- a/db/version_set.h +++ b/db/version_set.h @@ -12,6 +12,15 @@ // Version,VersionSet are thread-compatible, but require external // synchronization on all accesses. +/* + * DBImpl的表示由一组version组成。 最新的Version被称为“current”。 + * 可以保留旧版本,以便为活动迭代器提供一致的视图。 + * + * 每个Version在每个level记录一组Table文件。在VersionSet中维护整个版本集。 + * + * Version和VersionSet是线程兼容的,但在所有访问时都需要外部同步。 + */ + #ifndef STORAGE_LEVELDB_DB_VERSION_SET_H_ #define STORAGE_LEVELDB_DB_VERSION_SET_H_ @@ -50,6 +59,16 @@ int FindFile(const InternalKeyComparator &icmp, // largest==nullptr represents a key largest than all keys in the DB. // REQUIRES: If disjoint_sorted_files, files[] contains disjoint ranges // in sorted order. + +/** + * 如果"files"中的某些文件与用户键范围[smallest,largest]重叠,则返回true。 + * + * smallest==nullptr表示一个比DB中所有键都小的键。 + * + * largest==nullptr表示一个键比DB中所有键都大。 + * + * 要求:如果disjoint_sorted_files, files[]包含按排序顺序排列的不相交范围。 + */ bool SomeFileOverlapsRange(const InternalKeyComparator &icmp, bool disjoint_sorted_files, const std::vector &files, @@ -61,31 +80,59 @@ public: // Append to *iters a sequence of iterators that will // yield the contents of this Version when merged together. // REQUIRES: This version has been saved (see VersionSet::SaveTo) + /** + * @brief 向*iters添加一个迭代器序列(所有level0文件,当合并在一起时将产生this version的内容。) + * + * REQUIRES: 此version已被保存(参见VersionSet::SaveTo) + */ void AddIterators(const ReadOptions &, std::vector *iters); - // Lookup the value for key. If found, store it in *val and - // return OK. Else return a non-OK status. Fills *stats. - // REQUIRES: lock is not held struct GetStats { FileMetaData *seek_file; int seek_file_level; }; + + // Lookup the value for key. If found, store it in *val and + // return OK. Else return a non-OK status. Fills *stats. + // REQUIRES: lock is not held + /** + * @brief 查找key的值。 + * 如果找到了则将其存储在*val中,返回OK。 否则返回non-OK。 填充*stats + * + * REQUIRES: 持有lock + */ Status Get(const ReadOptions &, const LookupKey &key, std::string *val, GetStats *stats); // Adds "stats" into the current state. Returns true if a new // compaction may need to be triggered, false otherwise. // REQUIRES: lock is held + /** + * @brief + * 将“stats”添加到当前状态。如果可能需要触发新的压缩,则返回true,否则返回false。 + * + * 要求:锁不被持有 + */ bool UpdateStats(const GetStats &stats); // Record a sample of bytes read at the specified internal key. // Samples are taken approximately once every config::kReadBytesPeriod // bytes. Returns true if a new compaction may need to be triggered. // REQUIRES: lock is held + /** + * @brief 记录在指定的internal key上读取的字节样本。 + * + * 大约每config::kReadBytesPeriod字节采集一次样本。如果可能需要触发新的压缩,则返回true。 + * + * 要求:锁不被持有 + */ bool RecordReadSample(Slice key); // Reference count management (so Versions do not disappear out from // under live iterators) + /** + * @brief 引用计数管理(所以版本不会从活跃迭代器下消失) + */ void Ref(); void Unref(); @@ -99,11 +146,27 @@ public: // some part of [*smallest_user_key,*largest_user_key]. // smallest_user_key==nullptr represents a key smaller than all the DB's keys. // largest_user_key==nullptr represents a key largest than all the DB's keys. + + /** + * @brief + * 如果指定级别中的某些文件与[smallst_user_key,largest_user_key]的某些部分重叠, + * 则返回true。 + * + * smallst_user_key ==nullptr表示一个小于所有DB键的键。 + * + * largest_user_key==nullptr表示一个比所有DB的键都大的键。 + * + */ bool OverlapInLevel(int level, const Slice *smallest_user_key, const Slice *largest_user_key); // Return the level at which we should place a new memtable compaction // result that covers the range [smallest_user_key,largest_user_key]. + /** + * 返回需要压缩的级别: level+1存在重叠, level+2重叠字节数太多 + * 返回应该放置新的memtable compaction result的级别, + * 该级别覆盖范围[smallst_user_key,largest_user_key]。 + */ int PickLevelForMemTableOutput(const Slice &smallest_user_key, const Slice &largest_user_key); @@ -124,24 +187,60 @@ private: // false, makes no more calls. // // REQUIRES: user portion of internal_key == user_key. + + /** + * @brief 对每个与user_key重叠的文件按从最新到最旧的顺序调用func(arg, level, + * f)。 如果调用函数返回false,则不再调用。 + * + * REQUIRES:internal_key == user_key的用户部分。 + */ void ForEachOverlapping(Slice user_key, Slice internal_key, void *arg, bool (*func)(void *, int, FileMetaData *)); - VersionSet *vset_; // VersionSet to which this Version belongs - Version *next_; // Next version in linked list - Version *prev_; // Previous version in linked list - int refs_; // Number of live refs to this version + // VersionSet to which this Version belongs + /** + * @brief 此版本所属的VersionSet + */ + VersionSet *vset_; + + // Next version in linked list + /** + * @brief 链表中的下一个版本 + */ + Version *next_; + + // Previous version in linked list + /** + * @brief 链接表中的上一个版本 + */ + Version *prev_; + + // Number of live refs to this version + /** + * @brief 此版本的引用数 + */ + int refs_; // List of files per level + /** + * @brief 每个level的文件列表 + */ std::vector files_[config::kNumLevels]; // Next file to compact based on seek stats. + /** + * @brief 基于seek stats进行compact的下一个文件。 + */ FileMetaData *file_to_compact_; int file_to_compact_level_; // Level that should be compacted next and its compaction score. // Score < 1 means compaction is not strictly needed. These fields // are initialized by Finalize(). + /** + * @brief 下一步应压实的级别及其压实评分。 + * score < 1表示不需要严格压缩。这些字段由Finalize()初始化。 + */ double compaction_score_; int compaction_level_; @@ -169,6 +268,7 @@ public: // REQUIRES: *mu is held on entry. // REQUIRES: no other thread concurrently calls LogAndApply() /** + * @brief * 对当前版本应用*edit以形成一个新的描述符,该描述符既保存为持久状态,又作为新的当前版本安装。 * 将在实际写入文件时释放*mu。 * @@ -180,25 +280,25 @@ public: // Recover the last saved descriptor from persistent storage. /** - * 从持久存储恢复最后保存的描述符。 + * @brief 从持久存储恢复最后保存的描述符。 */ Status Recover(bool *save_manifest); // Return the current version. /** - * 返回当前版本。 + * @brief 返回当前版本。 */ Version *current() const { return current_; } // Return the current manifest file number /** - * 返回当前manifest文件号 + * @brief 返回当前manifest文件号 */ uint64_t ManifestFileNumber() const { return manifest_file_number_; } // Allocate and return a new file number /** - * 分配并返回一个新的文件号 + * @brief 分配并返回一个新的文件号 */ uint64_t NewFileNumber() { return next_file_number_++; } @@ -206,7 +306,7 @@ public: // already been allocated. // REQUIRES: "file_number" was returned by a call to NewFileNumber(). /** - * 安排重用“file_number”,除非已经分配了新的文件号。 + * @brief 安排重用“file_number”,除非已经分配了新的文件号。 * * require: "file_number"是通过调用NewFileNumber()返回的。 */ @@ -218,13 +318,13 @@ public: // Return the number of Table files at the specified level. /** - * 返回指定级别的Table文件数量。 + * @brief 返回指定级别的Table文件数量。 */ int NumLevelFiles(int level) const; // Return the combined file size of all files at the specified level. /** - * 返回指定级别上所有文件的总文件大小。 + * @brief 返回指定级别上所有文件的总文件大小。 */ int64_t NumLevelBytes(int level) const; @@ -236,7 +336,7 @@ public: // Set the last sequence number to s. /** - * 设置s为the last sequence number to s + * @brief 设置s为the last sequence number to s */ void SetLastSequence(uint64_t s) { assert(s >= last_sequence_); @@ -245,19 +345,20 @@ public: // Mark the specified file number as used. /** - * 将指定的文件编号标记为已使用。 + * @brief 将指定的文件编号标记为已使用。 */ void MarkFileNumberUsed(uint64_t number); // Return the current log file number. /** - * 返回当前日志文件号。 + * @brief 返回当前日志文件号。 */ uint64_t LogNumber() const { return log_number_; } // Return the log file number for the log file that is currently // being compacted, or zero if there is no such log file. /** + * @brief * 返回当前正在压缩的日志文件的日志文件号,如果没有这样的日志文件,则返回零。 */ uint64_t PrevLogNumber() const { return prev_log_number_; } @@ -267,7 +368,7 @@ public: // Otherwise returns a pointer to a heap-allocated object that // describes the compaction. Caller should delete the result. /** - * 为new compaction 选择level和inputs + * @brief 为new compaction 选择level和inputs。 * 如果不进行压缩,则返回nullptr。 * 否则返回指向描述压缩的堆分配对象的指针。 * @@ -280,7 +381,7 @@ public: // level that overlaps the specified range. Caller should delete // the result. /** - * 返回一个压缩对象,用于在指定级别压缩范围[begin,end]。 + * @brief 返回一个压缩对象,用于在指定级别压缩范围[begin,end]。 * * 如果该级别中没有任何内容与指定范围重叠,则返回nullptr。 * @@ -292,14 +393,15 @@ public: // Return the maximum overlapping data (in bytes) at next level for any // file at a level >= 1. /** - * 对于level >= 1的任何文件,返回next level的最大重叠数据(以字节为单位)。 + * @brief 对于level >= 1的任何文件,返回next + * level的最大重叠数据(以字节为单位)。 */ int64_t MaxNextLevelOverlappingBytes(); // Create an iterator that reads over the compaction inputs for "*c". // The caller should delete the iterator when no longer needed. /** - * 创建一个迭代器,读取“*c”的压缩输入。 + * @brief 创建一个迭代器,读取“*c”的压缩输入。 * * @note 当不再需要迭代器时,调用者应该删除该迭代器。 */ @@ -307,7 +409,7 @@ public: // Returns true iff some level needs a compaction. /** - * 如果某个级别需要压缩,则返回true。 + * @brief 如果某个级别需要压缩,则返回true。 */ bool NeedsCompaction() const { Version *v = current_; @@ -324,13 +426,14 @@ public: // Return the approximate offset in the database of the data for // "key" as of version "v". /** - * 返回版本“v”的“key”数据在数据库中的近似偏移量。 + * @brief 返回版本“v”的“key”数据在数据库中的近似偏移量。 */ uint64_t ApproximateOffsetOf(Version *v, const InternalKey &key); // Return a human-readable short (single-line) summary of the number // of files per level. Uses *scratch as backing store. /** + * @brief * 返回一个人类可读的简短(单行)的每层(level)文件数量摘要。使用*scratch作为后备存储。 */ struct LevelSummaryStorage { diff --git a/doc/learn/db.md b/doc/learn/db.md index bbcace8..d5ec086 100644 --- a/doc/learn/db.md +++ b/doc/learn/db.md @@ -89,12 +89,66 @@ DB实例恢复状态 > 需要先去了解[WriteBatch](##WriteBatch) -创建Writer,并入队列writers,等待队列 +创建Writer,并入队列writers,循环等待队列 调用MakeRoomForWrite 调用BuildBatchGroup +从versionset中获取last sequence + +先写log,后写memtable,随后更新versionset的last sequence + + + +### Get + +从versionset中获取last sequence,从versionset中获取current version。 + +将key和last sequence组合成[LookupKey](##LookupKey)。 + +先在memtable中找,没找到则在immtable中找,没找到则在current version中查找。 + +如果是在current version中找到的并且需要压缩,则调度压缩线程。 + +- 需要压缩的判断:current version 的FileMetaData.allowed_seeks<=1,并且current version.file_to_compact_为nullptr. + + + + + +### NewIterator + +调用NewInternalIterator + +- 从versionset拿到last sequence +- 收集所有需要的子迭代器,如memtable、immtable、level0的所有文件 +- 将所有迭代器合并为一个迭代器internal_iter +- 在internal_iter上注册注销时被调用的函数,此函数作用是unref mem、imm、current version +- 返回internal_iter + + + +### GetSnapshot + +[SnapshotList](###SnapshotList).New()创建一个新的Snapshot + + + + + +### CompactRange + + + +### BackgroundCompaction + +调用路径:DBImpl.CompactRange > DBImpl.TEST_CompactRange > DBImpl.MaybeScheduleCompaction > 由env.Schedule调用DBImpl.BGWork > DBImpl.BackgroundCall > + + + +如果`imm_`不为nullptr,则调用DBImpl.CompactMemTable + ## WriteBatch @@ -201,3 +255,96 @@ static void Append(WriteBatch *dst, const WriteBatch *src); + + +## Snapshot + +### SnapshotList + + + +## LookupKey + +由key和sequence number组成 + +格式 + +| size(varint32) | user_key | seuqnce number(int56) | type(int8) | +| -------------- | -------- | --------------------- | ---------- | + + + + + +## MemTable + +### Add + +调用路径:DBImpl.Write > WriteBatchInternal.InsertInto > MemTableInserter.Put/Delete > + + + +entry格式 + + + + + + + + + + +
key size(varint32)key bytessequence number(int56)ValueType(int8)value size(varint32)value bytes
+ + + +调用skiplist table的Insert方法,存储编码后的内容 + + + +### Get + +调用路径:DBImpl.Get > + +创建一个Table::Iterator,Seek到key + + + +## Writer + +### AddRecord + +调用路径:DBImpl.Write > + + + +如果block剩余空间无法放下一个header,则剩余空间填充为0. + +如果block空间可以完全装下数据,则kFullType + +如果block空间无法完全装下数据,则kFirstType,kMilldleType,kLastType + +调用EmitPhysicalRecord将数据存储到磁盘上 + +- 编码header,格式:header长度为7-byte + + | crc(uint32) | size&0xff (uint8) | size>>8 (uint8) | RecordType(uint8) | + | ----------- | ----------------- | --------------- | ----------------- | + +- 写到磁盘: WritableFile.Append( parsed header ) + +- WritableFile.Append( payload ) + +- WritableFile.Flush() + + + +## Table + +### Table::Iterator + + + +## Version + diff --git a/include/leveldb/iterator.h b/include/leveldb/iterator.h index 9568d19..dc095c2 100644 --- a/include/leveldb/iterator.h +++ b/include/leveldb/iterator.h @@ -115,7 +115,17 @@ public: // // Note that unlike all of the preceding methods, this method is // not abstract and therefore clients should not override it. + + /** + * @brief 当此迭代器被销毁时将被调用。 + */ using CleanupFunction = void (*)(void *arg1, void *arg2); + + /** + * @brief 允许客户端注册function/arg1/arg2三元组,当此迭代器被销毁时将被调用。 + * + * NOTE: 与前面的所有方法不同,这个方法不是抽象的,因此客户机不应该覆盖它。 + */ void RegisterCleanup(CleanupFunction function, void *arg1, void *arg2); private: diff --git a/table/iterator_wrapper.h b/table/iterator_wrapper.h index f1814ca..32178a2 100644 --- a/table/iterator_wrapper.h +++ b/table/iterator_wrapper.h @@ -15,17 +15,18 @@ namespace leveldb { // This can help avoid virtual function calls and also gives better // cache locality. class IteratorWrapper { - public: - IteratorWrapper(): iter_(nullptr), valid_(false) { } - explicit IteratorWrapper(Iterator* iter): iter_(nullptr) { - Set(iter); - } +public: + IteratorWrapper() : iter_(nullptr), valid_(false) {} + explicit IteratorWrapper(Iterator *iter) : iter_(nullptr) { Set(iter); } ~IteratorWrapper() { delete iter_; } - Iterator* iter() const { return iter_; } + Iterator *iter() const { return iter_; } // Takes ownership of "iter" and will delete it when destroyed, or // when Set() is invoked again. - void Set(Iterator* iter) { + /** + * @brief 获取"iter"的所有权,并在销毁或再次调用Set()时将其删除。 + */ + void Set(Iterator *iter) { delete iter_; iter_ = iter; if (iter_ == nullptr) { @@ -35,20 +36,48 @@ class IteratorWrapper { } } - // Iterator interface methods - bool Valid() const { return valid_; } - Slice key() const { assert(Valid()); return key_; } - Slice value() const { assert(Valid()); return iter_->value(); } + bool Valid() const { return valid_; } + Slice key() const { + assert(Valid()); + return key_; + } + Slice value() const { + assert(Valid()); + return iter_->value(); + } // Methods below require iter() != nullptr - Status status() const { assert(iter_); return iter_->status(); } - void Next() { assert(iter_); iter_->Next(); Update(); } - void Prev() { assert(iter_); iter_->Prev(); Update(); } - void Seek(const Slice& k) { assert(iter_); iter_->Seek(k); Update(); } - void SeekToFirst() { assert(iter_); iter_->SeekToFirst(); Update(); } - void SeekToLast() { assert(iter_); iter_->SeekToLast(); Update(); } + Status status() const { + assert(iter_); + return iter_->status(); + } + void Next() { + assert(iter_); + iter_->Next(); + Update(); + } + void Prev() { + assert(iter_); + iter_->Prev(); + Update(); + } + void Seek(const Slice &k) { + assert(iter_); + iter_->Seek(k); + Update(); + } + void SeekToFirst() { + assert(iter_); + iter_->SeekToFirst(); + Update(); + } + void SeekToLast() { + assert(iter_); + iter_->SeekToLast(); + Update(); + } - private: +private: void Update() { valid_ = iter_->Valid(); if (valid_) { @@ -56,11 +85,11 @@ class IteratorWrapper { } } - Iterator* iter_; + Iterator *iter_; bool valid_; Slice key_; }; -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_TABLE_ITERATOR_WRAPPER_H_ +#endif // STORAGE_LEVELDB_TABLE_ITERATOR_WRAPPER_H_ diff --git a/table/merger.h b/table/merger.h index bafdf5a..e8e3e50 100644 --- a/table/merger.h +++ b/table/merger.h @@ -18,9 +18,18 @@ class Iterator; // key is present in K child iterators, it will be yielded K times. // // REQUIRES: n >= 0 -Iterator* NewMergingIterator( - const Comparator* comparator, Iterator** children, int n); -} // namespace leveldb +/** + * @brief 返回一个迭代器,该迭代器提供children[0,n-1]的并集。 + * 返回的迭代器拥有child iterators的所有权,会在在删除此迭代器时删除它们。 + * + * 结果没有重复抑制。也就是说,如果一个特定的键存在于K个子迭代器中,它将被产生K次。 + * + * REQUIRES: n >= 0 + */ +Iterator *NewMergingIterator(const Comparator *comparator, Iterator **children, + int n); -#endif // STORAGE_LEVELDB_TABLE_MERGER_H_ +} // namespace leveldb + +#endif // STORAGE_LEVELDB_TABLE_MERGER_H_ diff --git a/util/coding.h b/util/coding.h index f0fa2cb..38b5d35 100644 --- a/util/coding.h +++ b/util/coding.h @@ -7,6 +7,13 @@ // * In addition we support variable length "varint" encoding // * Strings are encoded prefixed by their length in varint format +/** + * Endian-neutral编码: + * - Fixed-length的数字首先用最低有效(least-significant)字节编码 + * - 此外,我们还支持可变长度的“varint”编码 + * - 字符串以可变格式的长度作为前缀进行编码 + */ + #ifndef STORAGE_LEVELDB_UTIL_CODING_H_ #define STORAGE_LEVELDB_UTIL_CODING_H_ @@ -21,61 +28,70 @@ namespace leveldb { // Standard Put... routines append to a string -void PutFixed32(std::string* dst, uint32_t value); -void PutFixed64(std::string* dst, uint64_t value); -void PutVarint32(std::string* dst, uint32_t value); -void PutVarint64(std::string* dst, uint64_t value); -void PutLengthPrefixedSlice(std::string* dst, const Slice& value); +void PutFixed32(std::string *dst, uint32_t value); +void PutFixed64(std::string *dst, uint64_t value); +void PutVarint32(std::string *dst, uint32_t value); +void PutVarint64(std::string *dst, uint64_t value); +void PutLengthPrefixedSlice(std::string *dst, const Slice &value); // Standard Get... routines parse a value from the beginning of a Slice // and advance the slice past the parsed value. -bool GetVarint32(Slice* input, uint32_t* value); -bool GetVarint64(Slice* input, uint64_t* value); -bool GetLengthPrefixedSlice(Slice* input, Slice* result); +bool GetVarint32(Slice *input, uint32_t *value); +bool GetVarint64(Slice *input, uint64_t *value); +bool GetLengthPrefixedSlice(Slice *input, Slice *result); // Pointer-based variants of GetVarint... These either store a value // in *v and return a pointer just past the parsed value, or return // nullptr on error. These routines only look at bytes in the range // [p..limit-1] -const char* GetVarint32Ptr(const char* p, const char* limit, uint32_t* v); -const char* GetVarint64Ptr(const char* p, const char* limit, uint64_t* v); + +/** + * @brief 在[p..limit-1]范围内获取int32值*v,并返回该值的指针。 + * 在错误时返回nullptr。 + */ +const char *GetVarint32Ptr(const char *p, const char *limit, uint32_t *v); +/** + * @brief 在[p..limit-1]范围内获取int64值*v,并返回该值的指针。 + * 在错误时返回nullptr。 + */ +const char *GetVarint64Ptr(const char *p, const char *limit, uint64_t *v); // Returns the length of the varint32 or varint64 encoding of "v" int VarintLength(uint64_t v); // Lower-level versions of Put... that write directly into a character buffer // REQUIRES: dst has enough space for the value being written -void EncodeFixed32(char* dst, uint32_t value); -void EncodeFixed64(char* dst, uint64_t value); +void EncodeFixed32(char *dst, uint32_t value); +void EncodeFixed64(char *dst, uint64_t value); // Lower-level versions of Put... that write directly into a character buffer // and return a pointer just past the last byte written. // REQUIRES: dst has enough space for the value being written -char* EncodeVarint32(char* dst, uint32_t value); -char* EncodeVarint64(char* dst, uint64_t value); +char *EncodeVarint32(char *dst, uint32_t value); +char *EncodeVarint64(char *dst, uint64_t value); // Lower-level versions of Get... that read directly from a character buffer // without any bounds checking. -inline uint32_t DecodeFixed32(const char* ptr) { +inline uint32_t DecodeFixed32(const char *ptr) { if (port::kLittleEndian) { // Load the raw bytes uint32_t result; - memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load + memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load return result; } else { - return ((static_cast(static_cast(ptr[0]))) - | (static_cast(static_cast(ptr[1])) << 8) - | (static_cast(static_cast(ptr[2])) << 16) - | (static_cast(static_cast(ptr[3])) << 24)); + return ((static_cast(static_cast(ptr[0]))) | + (static_cast(static_cast(ptr[1])) << 8) | + (static_cast(static_cast(ptr[2])) << 16) | + (static_cast(static_cast(ptr[3])) << 24)); } } -inline uint64_t DecodeFixed64(const char* ptr) { +inline uint64_t DecodeFixed64(const char *ptr) { if (port::kLittleEndian) { // Load the raw bytes uint64_t result; - memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load + memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load return result; } else { uint64_t lo = DecodeFixed32(ptr); @@ -85,14 +101,16 @@ inline uint64_t DecodeFixed64(const char* ptr) { } // Internal routine for use by fallback path of GetVarint32Ptr -const char* GetVarint32PtrFallback(const char* p, - const char* limit, - uint32_t* value); -inline const char* GetVarint32Ptr(const char* p, - const char* limit, - uint32_t* value) { + +/** + * @brief GetVarint32Ptr的回退路径使用的内部例程 + */ +const char *GetVarint32PtrFallback(const char *p, const char *limit, + uint32_t *value); +inline const char *GetVarint32Ptr(const char *p, const char *limit, + uint32_t *value) { if (p < limit) { - uint32_t result = *(reinterpret_cast(p)); + uint32_t result = *(reinterpret_cast(p)); if ((result & 128) == 0) { *value = result; return p + 1; @@ -101,6 +119,6 @@ inline const char* GetVarint32Ptr(const char* p, return GetVarint32PtrFallback(p, limit, value); } -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_UTIL_CODING_H_ +#endif // STORAGE_LEVELDB_UTIL_CODING_H_ diff --git a/util/logging.h b/util/logging.h index 13351a2..f42bf77 100644 --- a/util/logging.h +++ b/util/logging.h @@ -8,10 +8,10 @@ #ifndef STORAGE_LEVELDB_UTIL_LOGGING_H_ #define STORAGE_LEVELDB_UTIL_LOGGING_H_ -#include +#include "port/port.h" #include +#include #include -#include "port/port.h" namespace leveldb { @@ -19,25 +19,45 @@ class Slice; class WritableFile; // Append a human-readable printout of "num" to *str -void AppendNumberTo(std::string* str, uint64_t num); +/** + * @brief 将一个人类可读的“num”打印输出追加到*str + */ +void AppendNumberTo(std::string *str, uint64_t num); // Append a human-readable printout of "value" to *str. // Escapes any non-printable characters found in "value". -void AppendEscapedStringTo(std::string* str, const Slice& value); +/** + * @brief 向*str追加一个人类可读的“value”输出。 + * 转义在“value”中找到的任何不可打印字符。 + */ +void AppendEscapedStringTo(std::string *str, const Slice &value); // Return a human-readable printout of "num" +/** + * @brief 返回一个人类可读的“num”打印输出 + */ std::string NumberToString(uint64_t num); // Return a human-readable version of "value". // Escapes any non-printable characters found in "value". -std::string EscapeString(const Slice& value); +/** + * @brief 返回一个人类可读的“value”版本。 + * 转义在“value”中找到的任何不可打印字符。 + */ +std::string EscapeString(const Slice &value); // Parse a human-readable number from "*in" into *value. On success, // advances "*in" past the consumed number and sets "*val" to the // numeric value. Otherwise, returns false and leaves *in in an // unspecified state. -bool ConsumeDecimalNumber(Slice* in, uint64_t* val); +/** + * @brief 将一个人类可读的数字从“*in”解析为*value。 + * + * 如果成功,将“*in”向前移动到消耗的数字之后,并将“*val”设置为数值。 + * 否则,返回false并使*in处于未指定状态。 + */ +bool ConsumeDecimalNumber(Slice *in, uint64_t *val); -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_UTIL_LOGGING_H_ +#endif // STORAGE_LEVELDB_UTIL_LOGGING_H_ -- Gitee From 04a9dc609b3642b4ad332177fde68103bf162489 Mon Sep 17 00:00:00 2001 From: hellolutar Date: Mon, 14 Oct 2024 18:01:04 +0800 Subject: [PATCH 03/22] chore: some comments --- db/db_impl.cc | 43 ++- db/db_impl.h | 7 + db/dbformat.h | 6 + db/version_set.cc | 554 ++++++++++++++++++------------------- db/version_set.h | 76 ++++- demo/CMakeLists.txt | 9 + demo/case/CMakeLists.txt | 22 +- demo/case/compaction.cc | 35 +++ demo/mutex.h | 264 ++++++++++++++++++ demo/race/CMakeLists.txt | 8 + demo/race/race.cc | 28 ++ demo/race/race_safe.cc | 32 +++ demo/race/w_thread_safe.cc | 33 +++ demo/util.cmake | 25 ++ doc/learn/compaction.md | 15 + doc/learn/db.md | 14 + include/leveldb/cache.h | 22 ++ include/leveldb/table.h | 1 + util/cache.cc | 216 ++++++++------- 19 files changed, 998 insertions(+), 412 deletions(-) create mode 100644 demo/case/compaction.cc create mode 100644 demo/mutex.h create mode 100644 demo/race/CMakeLists.txt create mode 100644 demo/race/race.cc create mode 100644 demo/race/race_safe.cc create mode 100644 demo/race/w_thread_safe.cc create mode 100644 demo/util.cmake create mode 100644 doc/learn/compaction.md diff --git a/db/db_impl.cc b/db/db_impl.cc index 438206a..05daedd 100644 --- a/db/db_impl.cc +++ b/db/db_impl.cc @@ -58,9 +58,18 @@ struct DBImpl::CompactionState { // will never have to service a snapshot below smallest_snapshot. // Therefore if we have seen a sequence number S <= smallest_snapshot, // we can drop all entries for the same key with sequence numbers < S. + /** + * @brief Sequence numbers < smallst_snapshot不重要, + * 因为我们永远不必服务小于smallst_snapshot的快照。 + * 因此,如果我们看到序列号S <= smallest_snapshot , + * 我们可以删除 sequence numbers < S的相同键的所有条目。 + */ SequenceNumber smallest_snapshot; // Files produced by compaction + /** + * @brief 由compaction产生的文件 + */ struct Output { uint64_t number; uint64_t file_size; @@ -69,6 +78,9 @@ struct DBImpl::CompactionState { std::vector outputs; // State kept for output being generated + /** + * @brief 为生成输出而保留的状态 + */ WritableFile *outfile; TableBuilder *builder; @@ -694,6 +706,7 @@ void DBImpl::BackgroundCompaction() { InternalKey manual_end; if (is_manual) { ManualCompaction *m = manual_compaction_; + // 压缩逻辑 c = versions_->CompactRange(m->level, m->begin, m->end); m->done = (c == nullptr); if (c != nullptr) { @@ -705,6 +718,7 @@ void DBImpl::BackgroundCompaction() { (m->end ? m->end->DebugString().c_str() : "(end)"), (m->done ? "(end)" : manual_end.DebugString().c_str())); } else { + // 压缩逻辑 c = versions_->PickCompaction(); } @@ -816,6 +830,7 @@ Status DBImpl::FinishCompactionOutputFile(CompactionState *compact, // Check for iterator errors Status s = input->status(); const uint64_t current_entries = compact->builder->NumEntries(); + // 正常迭代结束,没有异常发生 if (s.ok()) { s = compact->builder->Finish(); } else { @@ -839,6 +854,8 @@ Status DBImpl::FinishCompactionOutputFile(CompactionState *compact, if (s.ok() && current_entries > 0) { // Verify that the table is usable + // 验证table是否可用:sstable将footer放置在文件的最后,current_bytes即表示文件的最后, + // 若无法通过current_bytes读取到一个sstable,则表示此次压缩失败 Iterator *iter = table_cache_->NewIterator(ReadOptions(), output_number, current_bytes); s = iter->status(); @@ -871,6 +888,14 @@ Status DBImpl::InstallCompactionResults(CompactionState *compact) { return versions_->LogAndApply(compact->compaction->edit(), &mutex_); } +/** + * @brief 压缩 + * + * 通过Versions_->MakeInputIterator()拿到需要压缩的迭代器input。 + * 对input进行迭代,调用OpenCompactionOutputFile创建压缩输出文件。 + * + * + */ Status DBImpl::DoCompactionWork(CompactionState *compact) { const uint64_t start_micros = env_->NowMicros(); int64_t imm_micros = 0; // Micros spent doing imm_ compactions @@ -899,8 +924,12 @@ Status DBImpl::DoCompactionWork(CompactionState *compact) { std::string current_user_key; bool has_current_user_key = false; SequenceNumber last_sequence_for_key = kMaxSequenceNumber; + + // input 迭代 for (; input->Valid() && !shutting_down_.load(std::memory_order_acquire);) { + // Prioritize immutable compaction work + // 先对immutable进行压缩 if (has_imm_.load(std::memory_order_relaxed)) { const uint64_t imm_start = env_->NowMicros(); mutex_.Lock(); @@ -929,7 +958,7 @@ Status DBImpl::DoCompactionWork(CompactionState *compact) { current_user_key.clear(); has_current_user_key = false; last_sequence_for_key = kMaxSequenceNumber; - } else { + } else { // 成功解析internal key if (!has_current_user_key || user_comparator()->Compare(ikey.user_key, Slice(current_user_key)) != 0) { @@ -952,6 +981,14 @@ Status DBImpl::DoCompactionWork(CompactionState *compact) { // smaller sequence numbers will be dropped in the next // few iterations of this loop (by rule (A) above). // Therefore this deletion marker is obsolete and can be dropped. + + /* + For this user key: + (1) 更高level没有数据 + (2) 较低level的数据将具有较大的序列号 + (3) + 在这里被压缩并且具有较小序号的lvel中的数据将在此循环的下几个迭代中被丢弃(根据上面的规则(A))。 + */ drop = true; } @@ -979,6 +1016,8 @@ Status DBImpl::DoCompactionWork(CompactionState *compact) { compact->current_output()->smallest.DecodeFrom(key); } compact->current_output()->largest.DecodeFrom(key); + + // 将需要压缩时需要保留的记录添加到Compaction的输出文件中 compact->builder->Add(key, input->value()); // Close output file if it is big enough @@ -991,7 +1030,7 @@ Status DBImpl::DoCompactionWork(CompactionState *compact) { } } - input->Next(); + input->Next(); // 继续input迭代 } if (status.ok() && shutting_down_.load(std::memory_order_acquire)) { diff --git a/db/db_impl.h b/db/db_impl.h index 672a5bb..7e354e5 100644 --- a/db/db_impl.h +++ b/db/db_impl.h @@ -126,6 +126,13 @@ private: Status DoCompactionWork(CompactionState *compact) EXCLUSIVE_LOCKS_REQUIRED(mutex_); + /** + * @brief 创建用于Compaction的输出文件 + * + * 创建CompactionState::Output,将其入队列compact->outputs。 + * 创建writable file。 + * 为compact创建TableBuilder + */ Status OpenCompactionOutputFile(CompactionState *compact); Status FinishCompactionOutputFile(CompactionState *compact, Iterator *input); Status InstallCompactionResults(CompactionState *compact) diff --git a/db/dbformat.h b/db/dbformat.h index e5b0d50..a9366f3 100644 --- a/db/dbformat.h +++ b/db/dbformat.h @@ -105,6 +105,12 @@ void AppendInternalKey(std::string *result, const ParsedInternalKey &key); // stores the parsed data in "*result", and returns true. // // On error, returns false, leaves "*result" in an undefined state. +/** + * @brief 尝试从“internal_key”解析一个 internal key。 + * + * 如果成功,将数据存储在*result,并返回true。 + * 如果错误,*result为未定义状态,并返回false + */ bool ParseInternalKey(const Slice &internal_key, ParsedInternalKey *result); // Returns the user key portion of an internal key. diff --git a/db/version_set.cc b/db/version_set.cc index 124ae6a..378a7e9 100644 --- a/db/version_set.cc +++ b/db/version_set.cc @@ -4,8 +4,6 @@ #include "db/version_set.h" -#include -#include #include "db/filename.h" #include "db/log_reader.h" #include "db/log_writer.h" @@ -17,27 +15,29 @@ #include "table/two_level_iterator.h" #include "util/coding.h" #include "util/logging.h" +#include +#include namespace leveldb { -static size_t TargetFileSize(const Options* options) { +static size_t TargetFileSize(const Options *options) { return options->max_file_size; } // Maximum bytes of overlaps in grandparent (i.e., level+2) before we // stop building a single file in a level->level+1 compaction. -static int64_t MaxGrandParentOverlapBytes(const Options* options) { +static int64_t MaxGrandParentOverlapBytes(const Options *options) { return 10 * TargetFileSize(options); } // Maximum number of bytes in all compacted files. We avoid expanding // the lower level file set of a compaction if it would make the // total compaction cover more than this many bytes. -static int64_t ExpandedCompactionByteSizeLimit(const Options* options) { +static int64_t ExpandedCompactionByteSizeLimit(const Options *options) { return 25 * TargetFileSize(options); } -static double MaxBytesForLevel(const Options* options, int level) { +static double MaxBytesForLevel(const Options *options, int level) { // Note: the result for level zero is not really used since we set // the level-0 compaction threshold based on number of files. @@ -50,12 +50,12 @@ static double MaxBytesForLevel(const Options* options, int level) { return result; } -static uint64_t MaxFileSizeForLevel(const Options* options, int level) { +static uint64_t MaxFileSizeForLevel(const Options *options, int level) { // We could vary per level to reduce number of files? return TargetFileSize(options); } -static int64_t TotalFileSize(const std::vector& files) { +static int64_t TotalFileSize(const std::vector &files) { int64_t sum = 0; for (size_t i = 0; i < files.size(); i++) { sum += files[i]->file_size; @@ -73,7 +73,7 @@ Version::~Version() { // Drop references to files for (int level = 0; level < config::kNumLevels; level++) { for (size_t i = 0; i < files_[level].size(); i++) { - FileMetaData* f = files_[level][i]; + FileMetaData *f = files_[level][i]; assert(f->refs > 0); f->refs--; if (f->refs <= 0) { @@ -83,14 +83,13 @@ Version::~Version() { } } -int FindFile(const InternalKeyComparator& icmp, - const std::vector& files, - const Slice& key) { +int FindFile(const InternalKeyComparator &icmp, + const std::vector &files, const Slice &key) { uint32_t left = 0; uint32_t right = files.size(); while (left < right) { uint32_t mid = (left + right) / 2; - const FileMetaData* f = files[mid]; + const FileMetaData *f = files[mid]; if (icmp.InternalKeyComparator::Compare(f->largest.Encode(), key) < 0) { // Key at "mid.largest" is < "target". Therefore all // files at or before "mid" are uninteresting. @@ -104,36 +103,35 @@ int FindFile(const InternalKeyComparator& icmp, return right; } -static bool AfterFile(const Comparator* ucmp, - const Slice* user_key, const FileMetaData* f) { +static bool AfterFile(const Comparator *ucmp, const Slice *user_key, + const FileMetaData *f) { // null user_key occurs before all keys and is therefore never after *f return (user_key != nullptr && ucmp->Compare(*user_key, f->largest.user_key()) > 0); } -static bool BeforeFile(const Comparator* ucmp, - const Slice* user_key, const FileMetaData* f) { +static bool BeforeFile(const Comparator *ucmp, const Slice *user_key, + const FileMetaData *f) { // null user_key occurs after all keys and is therefore never before *f return (user_key != nullptr && ucmp->Compare(*user_key, f->smallest.user_key()) < 0); } -bool SomeFileOverlapsRange( - const InternalKeyComparator& icmp, - bool disjoint_sorted_files, - const std::vector& files, - const Slice* smallest_user_key, - const Slice* largest_user_key) { - const Comparator* ucmp = icmp.user_comparator(); +bool SomeFileOverlapsRange(const InternalKeyComparator &icmp, + bool disjoint_sorted_files, + const std::vector &files, + const Slice *smallest_user_key, + const Slice *largest_user_key) { + const Comparator *ucmp = icmp.user_comparator(); if (!disjoint_sorted_files) { // Need to check against all files for (size_t i = 0; i < files.size(); i++) { - const FileMetaData* f = files[i]; + const FileMetaData *f = files[i]; if (AfterFile(ucmp, smallest_user_key, f) || BeforeFile(ucmp, largest_user_key, f)) { // No overlap } else { - return true; // Overlap + return true; // Overlap } } return false; @@ -143,7 +141,8 @@ bool SomeFileOverlapsRange( uint32_t index = 0; if (smallest_user_key != nullptr) { // Find the earliest possible internal key for smallest_user_key - InternalKey small_key(*smallest_user_key, kMaxSequenceNumber,kValueTypeForSeek); + InternalKey small_key(*smallest_user_key, kMaxSequenceNumber, + kValueTypeForSeek); index = FindFile(icmp, files, small_key.Encode()); } @@ -161,17 +160,13 @@ bool SomeFileOverlapsRange( // 16-byte value containing the file number and file size, both // encoded using EncodeFixed64. class Version::LevelFileNumIterator : public Iterator { - public: - LevelFileNumIterator(const InternalKeyComparator& icmp, - const std::vector* flist) - : icmp_(icmp), - flist_(flist), - index_(flist->size()) { // Marks as invalid - } - virtual bool Valid() const { - return index_ < flist_->size(); +public: + LevelFileNumIterator(const InternalKeyComparator &icmp, + const std::vector *flist) + : icmp_(icmp), flist_(flist), index_(flist->size()) { // Marks as invalid } - virtual void Seek(const Slice& target) { + virtual bool Valid() const { return index_ < flist_->size(); } + virtual void Seek(const Slice &target) { index_ = FindFile(icmp_, *flist_, target); } virtual void SeekToFirst() { index_ = 0; } @@ -185,7 +180,7 @@ class Version::LevelFileNumIterator : public Iterator { virtual void Prev() { assert(Valid()); if (index_ == 0) { - index_ = flist_->size(); // Marks as invalid + index_ = flist_->size(); // Marks as invalid } else { index_--; } @@ -197,48 +192,46 @@ class Version::LevelFileNumIterator : public Iterator { Slice value() const { assert(Valid()); EncodeFixed64(value_buf_, (*flist_)[index_]->number); - EncodeFixed64(value_buf_+8, (*flist_)[index_]->file_size); + EncodeFixed64(value_buf_ + 8, (*flist_)[index_]->file_size); return Slice(value_buf_, sizeof(value_buf_)); } virtual Status status() const { return Status::OK(); } - private: + +private: const InternalKeyComparator icmp_; - const std::vector* const flist_; + const std::vector *const flist_; uint32_t index_; // Backing store for value(). Holds the file number and size. mutable char value_buf_[16]; }; -static Iterator* GetFileIterator(void* arg, - const ReadOptions& options, - const Slice& file_value) { - TableCache* cache = reinterpret_cast(arg); +static Iterator *GetFileIterator(void *arg, const ReadOptions &options, + const Slice &file_value) { + TableCache *cache = reinterpret_cast(arg); if (file_value.size() != 16) { return NewErrorIterator( Status::Corruption("FileReader invoked with unexpected value")); } else { - return cache->NewIterator(options, - DecodeFixed64(file_value.data()), + return cache->NewIterator(options, DecodeFixed64(file_value.data()), DecodeFixed64(file_value.data() + 8)); } } -Iterator* Version::NewConcatenatingIterator(const ReadOptions& options, +Iterator *Version::NewConcatenatingIterator(const ReadOptions &options, int level) const { return NewTwoLevelIterator( - new LevelFileNumIterator(vset_->icmp_, &files_[level]), - &GetFileIterator, vset_->table_cache_, options); + new LevelFileNumIterator(vset_->icmp_, &files_[level]), &GetFileIterator, + vset_->table_cache_, options); } -void Version::AddIterators(const ReadOptions& options, - std::vector* iters) { +void Version::AddIterators(const ReadOptions &options, + std::vector *iters) { // Merge all level zero files together since they may overlap // 合并所有level0文件,因为它们可能重叠 for (size_t i = 0; i < files_[0].size(); i++) { - iters->push_back( - vset_->table_cache_->NewIterator( - options, files_[0][i]->number, files_[0][i]->file_size)); + iters->push_back(vset_->table_cache_->NewIterator( + options, files_[0][i]->number, files_[0][i]->file_size)); } // For levels > 0, we can use a concatenating iterator that sequentially @@ -261,13 +254,13 @@ enum SaverState { }; struct Saver { SaverState state; - const Comparator* ucmp; + const Comparator *ucmp; Slice user_key; - std::string* value; + std::string *value; }; -} -static void SaveValue(void* arg, const Slice& ikey, const Slice& v) { - Saver* s = reinterpret_cast(arg); +} // namespace +static void SaveValue(void *arg, const Slice &ikey, const Slice &v) { + Saver *s = reinterpret_cast(arg); ParsedInternalKey parsed_key; if (!ParseInternalKey(ikey, &parsed_key)) { s->state = kCorrupt; @@ -281,21 +274,20 @@ static void SaveValue(void* arg, const Slice& ikey, const Slice& v) { } } -static bool NewestFirst(FileMetaData* a, FileMetaData* b) { +static bool NewestFirst(FileMetaData *a, FileMetaData *b) { return a->number > b->number; } -void Version::ForEachOverlapping(Slice user_key, Slice internal_key, - void* arg, - bool (*func)(void*, int, FileMetaData*)) { +void Version::ForEachOverlapping(Slice user_key, Slice internal_key, void *arg, + bool (*func)(void *, int, FileMetaData *)) { // TODO(sanjay): Change Version::Get() to use this function. - const Comparator* ucmp = vset_->icmp_.user_comparator(); + const Comparator *ucmp = vset_->icmp_.user_comparator(); // Search level-0 in order from newest to oldest. - std::vector tmp; + std::vector tmp; tmp.reserve(files_[0].size()); for (uint32_t i = 0; i < files_[0].size(); i++) { - FileMetaData* f = files_[0][i]; + FileMetaData *f = files_[0][i]; if (ucmp->Compare(user_key, f->smallest.user_key()) >= 0 && ucmp->Compare(user_key, f->largest.user_key()) <= 0) { tmp.push_back(f); @@ -313,12 +305,13 @@ void Version::ForEachOverlapping(Slice user_key, Slice internal_key, // Search other levels. for (int level = 1; level < config::kNumLevels; level++) { size_t num_files = files_[level].size(); - if (num_files == 0) continue; + if (num_files == 0) + continue; // Binary search to find earliest index whose largest key >= internal_key. uint32_t index = FindFile(vset_->icmp_, files_[level], internal_key); if (index < num_files) { - FileMetaData* f = files_[level][index]; + FileMetaData *f = files_[level][index]; if (ucmp->Compare(user_key, f->smallest.user_key()) < 0) { // All of "f" is past any data for user_key } else { @@ -330,43 +323,43 @@ void Version::ForEachOverlapping(Slice user_key, Slice internal_key, } } -Status Version::Get(const ReadOptions& options, - const LookupKey& k, - std::string* value, - GetStats* stats) { +Status Version::Get(const ReadOptions &options, const LookupKey &k, + std::string *value, GetStats *stats) { Slice ikey = k.internal_key(); Slice user_key = k.user_key(); - const Comparator* ucmp = vset_->icmp_.user_comparator(); + const Comparator *ucmp = vset_->icmp_.user_comparator(); Status s; stats->seek_file = nullptr; stats->seek_file_level = -1; - FileMetaData* last_file_read = nullptr; + FileMetaData *last_file_read = nullptr; int last_file_read_level = -1; // We can search level-by-level since entries never hop across // levels. Therefore we are guaranteed that if we find data // in a smaller level, later levels are irrelevant. - std::vector tmp; - FileMetaData* tmp2; + std::vector tmp; + FileMetaData *tmp2; for (int level = 0; level < config::kNumLevels; level++) { size_t num_files = files_[level].size(); - if (num_files == 0) continue; + if (num_files == 0) + continue; // Get the list of files to search in this level - FileMetaData* const* files = &files_[level][0]; + FileMetaData *const *files = &files_[level][0]; if (level == 0) { // Level-0 files may overlap each other. Find all files that // overlap user_key and process them in order from newest to oldest. tmp.reserve(num_files); for (uint32_t i = 0; i < num_files; i++) { - FileMetaData* f = files[i]; + FileMetaData *f = files[i]; if (ucmp->Compare(user_key, f->smallest.user_key()) >= 0 && ucmp->Compare(user_key, f->largest.user_key()) <= 0) { tmp.push_back(f); } } - if (tmp.empty()) continue; + if (tmp.empty()) + continue; std::sort(tmp.begin(), tmp.end(), NewestFirst); files = &tmp[0]; @@ -397,7 +390,7 @@ Status Version::Get(const ReadOptions& options, stats->seek_file_level = last_file_read_level; } - FileMetaData* f = files[i]; + FileMetaData *f = files[i]; last_file_read = f; last_file_read_level = level; @@ -406,31 +399,31 @@ Status Version::Get(const ReadOptions& options, saver.ucmp = ucmp; saver.user_key = user_key; saver.value = value; - s = vset_->table_cache_->Get(options, f->number, f->file_size, - ikey, &saver, SaveValue); + s = vset_->table_cache_->Get(options, f->number, f->file_size, ikey, + &saver, SaveValue); if (!s.ok()) { return s; } switch (saver.state) { - case kNotFound: - break; // Keep searching in other files - case kFound: - return s; - case kDeleted: - s = Status::NotFound(Slice()); // Use empty error message for speed - return s; - case kCorrupt: - s = Status::Corruption("corrupted key for ", user_key); - return s; + case kNotFound: + break; // Keep searching in other files + case kFound: + return s; + case kDeleted: + s = Status::NotFound(Slice()); // Use empty error message for speed + return s; + case kCorrupt: + s = Status::Corruption("corrupted key for ", user_key); + return s; } } } - return Status::NotFound(Slice()); // Use an empty error message for speed + return Status::NotFound(Slice()); // Use an empty error message for speed } -bool Version::UpdateStats(const GetStats& stats) { - FileMetaData* f = stats.seek_file; +bool Version::UpdateStats(const GetStats &stats) { + FileMetaData *f = stats.seek_file; if (f != nullptr) { f->allowed_seeks--; if (f->allowed_seeks <= 0 && file_to_compact_ == nullptr) { @@ -449,11 +442,11 @@ bool Version::RecordReadSample(Slice internal_key) { } struct State { - GetStats stats; // Holds first matching file + GetStats stats; // Holds first matching file int matches; - static bool Match(void* arg, int level, FileMetaData* f) { - State* state = reinterpret_cast(arg); + static bool Match(void *arg, int level, FileMetaData *f) { + State *state = reinterpret_cast(arg); state->matches++; if (state->matches == 1) { // Remember first match. @@ -480,9 +473,7 @@ bool Version::RecordReadSample(Slice internal_key) { return false; } -void Version::Ref() { - ++refs_; -} +void Version::Ref() { ++refs_; } void Version::Unref() { assert(this != &vset_->dummy_versions_); @@ -493,23 +484,21 @@ void Version::Unref() { } } -bool Version::OverlapInLevel(int level, - const Slice* smallest_user_key, - const Slice* largest_user_key) { +bool Version::OverlapInLevel(int level, const Slice *smallest_user_key, + const Slice *largest_user_key) { return SomeFileOverlapsRange(vset_->icmp_, (level > 0), files_[level], smallest_user_key, largest_user_key); } -int Version::PickLevelForMemTableOutput( - const Slice& smallest_user_key, - const Slice& largest_user_key) { +int Version::PickLevelForMemTableOutput(const Slice &smallest_user_key, + const Slice &largest_user_key) { int level = 0; if (!OverlapInLevel(0, &smallest_user_key, &largest_user_key)) { // Push to next level if there is no overlap in next level, // and the #bytes overlapping in the level after that are limited. InternalKey start(smallest_user_key, kMaxSequenceNumber, kValueTypeForSeek); InternalKey limit(largest_user_key, 0, static_cast(0)); - std::vector overlaps; + std::vector overlaps; while (level < config::kMaxMemCompactLevel) { if (OverlapInLevel(level + 1, &smallest_user_key, &largest_user_key)) { break; @@ -529,11 +518,9 @@ int Version::PickLevelForMemTableOutput( } // Store in "*inputs" all files in "level" that overlap [begin,end] -void Version::GetOverlappingInputs( - int level, - const InternalKey* begin, - const InternalKey* end, - std::vector* inputs) { +void Version::GetOverlappingInputs(int level, const InternalKey *begin, + const InternalKey *end, + std::vector *inputs) { assert(level >= 0); assert(level < config::kNumLevels); inputs->clear(); @@ -544,9 +531,9 @@ void Version::GetOverlappingInputs( if (end != nullptr) { user_end = end->user_key(); } - const Comparator* user_cmp = vset_->icmp_.user_comparator(); - for (size_t i = 0; i < files_[level].size(); ) { - FileMetaData* f = files_[level][i++]; + const Comparator *user_cmp = vset_->icmp_.user_comparator(); + for (size_t i = 0; i < files_[level].size();) { + FileMetaData *f = files_[level][i++]; const Slice file_start = f->smallest.user_key(); const Slice file_limit = f->largest.user_key(); if (begin != nullptr && user_cmp->Compare(file_limit, user_begin) < 0) { @@ -562,8 +549,8 @@ void Version::GetOverlappingInputs( user_begin = file_start; inputs->clear(); i = 0; - } else if (end != nullptr && user_cmp->Compare(file_limit, - user_end) > 0) { + } else if (end != nullptr && + user_cmp->Compare(file_limit, user_end) > 0) { user_end = file_limit; inputs->clear(); i = 0; @@ -583,7 +570,7 @@ std::string Version::DebugString() const { r.append("--- level "); AppendNumberTo(&r, level); r.append(" ---\n"); - const std::vector& files = files_[level]; + const std::vector &files = files_[level]; for (size_t i = 0; i < files.size(); i++) { r.push_back(' '); AppendNumberTo(&r, files[i]->number); @@ -603,12 +590,12 @@ std::string Version::DebugString() const { // of edits to a particular state without creating intermediate // Versions that contain full copies of the intermediate state. class VersionSet::Builder { - private: +private: // Helper to sort by v->files_[file_number].smallest struct BySmallestKey { - const InternalKeyComparator* internal_comparator; + const InternalKeyComparator *internal_comparator; - bool operator()(FileMetaData* f1, FileMetaData* f2) const { + bool operator()(FileMetaData *f1, FileMetaData *f2) const { int r = internal_comparator->Compare(f1->smallest, f2->smallest); if (r != 0) { return (r < 0); @@ -619,21 +606,19 @@ class VersionSet::Builder { } }; - typedef std::set FileSet; + typedef std::set FileSet; struct LevelState { std::set deleted_files; - FileSet* added_files; + FileSet *added_files; }; - VersionSet* vset_; - Version* base_; + VersionSet *vset_; + Version *base_; LevelState levels_[config::kNumLevels]; - public: +public: // Initialize a builder with the files from *base and other info from *vset - Builder(VersionSet* vset, Version* base) - : vset_(vset), - base_(base) { + Builder(VersionSet *vset, Version *base) : vset_(vset), base_(base) { base_->Ref(); BySmallestKey cmp; cmp.internal_comparator = &vset_->icmp_; @@ -644,16 +629,16 @@ class VersionSet::Builder { ~Builder() { for (int level = 0; level < config::kNumLevels; level++) { - const FileSet* added = levels_[level].added_files; - std::vector to_unref; + const FileSet *added = levels_[level].added_files; + std::vector to_unref; to_unref.reserve(added->size()); - for (FileSet::const_iterator it = added->begin(); - it != added->end(); ++it) { + for (FileSet::const_iterator it = added->begin(); it != added->end(); + ++it) { to_unref.push_back(*it); } delete added; for (uint32_t i = 0; i < to_unref.size(); i++) { - FileMetaData* f = to_unref[i]; + FileMetaData *f = to_unref[i]; f->refs--; if (f->refs <= 0) { delete f; @@ -664,7 +649,7 @@ class VersionSet::Builder { } // Apply all of the edits in *edit to the current state. - void Apply(VersionEdit* edit) { + void Apply(VersionEdit *edit) { // Update compaction pointers for (size_t i = 0; i < edit->compact_pointers_.size(); i++) { const int level = edit->compact_pointers_[i].first; @@ -673,10 +658,9 @@ class VersionSet::Builder { } // Delete files - const VersionEdit::DeletedFileSet& del = edit->deleted_files_; + const VersionEdit::DeletedFileSet &del = edit->deleted_files_; for (VersionEdit::DeletedFileSet::const_iterator iter = del.begin(); - iter != del.end(); - ++iter) { + iter != del.end(); ++iter) { const int level = iter->first; const uint64_t number = iter->second; levels_[level].deleted_files.insert(number); @@ -685,7 +669,7 @@ class VersionSet::Builder { // Add new files for (size_t i = 0; i < edit->new_files_.size(); i++) { const int level = edit->new_files_[i].first; - FileMetaData* f = new FileMetaData(edit->new_files_[i].second); + FileMetaData *f = new FileMetaData(edit->new_files_[i].second); f->refs = 1; // We arrange to automatically compact this file after @@ -702,7 +686,8 @@ class VersionSet::Builder { // conservative and allow approximately one seek for every 16KB // of data before triggering a compaction. f->allowed_seeks = static_cast((f->file_size / 16384U)); - if (f->allowed_seeks < 100) f->allowed_seeks = 100; + if (f->allowed_seeks < 100) + f->allowed_seeks = 100; levels_[level].deleted_files.erase(f->number); levels_[level].added_files->insert(f); @@ -710,25 +695,24 @@ class VersionSet::Builder { } // Save the current state in *v. - void SaveTo(Version* v) { + void SaveTo(Version *v) { BySmallestKey cmp; cmp.internal_comparator = &vset_->icmp_; for (int level = 0; level < config::kNumLevels; level++) { // Merge the set of added files with the set of pre-existing files. // Drop any deleted files. Store the result in *v. - const std::vector& base_files = base_->files_[level]; - std::vector::const_iterator base_iter = base_files.begin(); - std::vector::const_iterator base_end = base_files.end(); - const FileSet* added = levels_[level].added_files; + const std::vector &base_files = base_->files_[level]; + std::vector::const_iterator base_iter = + base_files.begin(); + std::vector::const_iterator base_end = base_files.end(); + const FileSet *added = levels_[level].added_files; v->files_[level].reserve(base_files.size() + added->size()); for (FileSet::const_iterator added_iter = added->begin(); - added_iter != added->end(); - ++added_iter) { + added_iter != added->end(); ++added_iter) { // Add all smaller files listed in base_ - for (std::vector::const_iterator bpos - = std::upper_bound(base_iter, base_end, *added_iter, cmp); - base_iter != bpos; - ++base_iter) { + for (std::vector::const_iterator bpos = + std::upper_bound(base_iter, base_end, *added_iter, cmp); + base_iter != bpos; ++base_iter) { MaybeAddFile(v, level, *base_iter); } @@ -744,8 +728,8 @@ class VersionSet::Builder { // Make sure there is no overlap in levels > 0 if (level > 0) { for (uint32_t i = 1; i < v->files_[level].size(); i++) { - const InternalKey& prev_end = v->files_[level][i-1]->largest; - const InternalKey& this_begin = v->files_[level][i]->smallest; + const InternalKey &prev_end = v->files_[level][i - 1]->largest; + const InternalKey &this_begin = v->files_[level][i]->smallest; if (vset_->icmp_.Compare(prev_end, this_begin) >= 0) { fprintf(stderr, "overlapping ranges in same level %s vs. %s\n", prev_end.DebugString().c_str(), @@ -758,14 +742,14 @@ class VersionSet::Builder { } } - void MaybeAddFile(Version* v, int level, FileMetaData* f) { + void MaybeAddFile(Version *v, int level, FileMetaData *f) { if (levels_[level].deleted_files.count(f->number) > 0) { // File is deleted: do nothing } else { - std::vector* files = &v->files_[level]; + std::vector *files = &v->files_[level]; if (level > 0 && !files->empty()) { // Must not overlap - assert(vset_->icmp_.Compare((*files)[files->size()-1]->largest, + assert(vset_->icmp_.Compare((*files)[files->size() - 1]->largest, f->smallest) < 0); } f->refs++; @@ -774,35 +758,26 @@ class VersionSet::Builder { } }; -VersionSet::VersionSet(const std::string& dbname, - const Options* options, - TableCache* table_cache, - const InternalKeyComparator* cmp) - : env_(options->env), - dbname_(dbname), - options_(options), - table_cache_(table_cache), - icmp_(*cmp), - next_file_number_(2), - manifest_file_number_(0), // Filled by Recover() - last_sequence_(0), - log_number_(0), - prev_log_number_(0), - descriptor_file_(nullptr), - descriptor_log_(nullptr), - dummy_versions_(this), - current_(nullptr) { +VersionSet::VersionSet(const std::string &dbname, const Options *options, + TableCache *table_cache, + const InternalKeyComparator *cmp) + : env_(options->env), dbname_(dbname), options_(options), + table_cache_(table_cache), icmp_(*cmp), next_file_number_(2), + manifest_file_number_(0), // Filled by Recover() + last_sequence_(0), log_number_(0), prev_log_number_(0), + descriptor_file_(nullptr), descriptor_log_(nullptr), + dummy_versions_(this), current_(nullptr) { AppendVersion(new Version(this)); } VersionSet::~VersionSet() { current_->Unref(); - assert(dummy_versions_.next_ == &dummy_versions_); // List must be empty + assert(dummy_versions_.next_ == &dummy_versions_); // List must be empty delete descriptor_log_; delete descriptor_file_; } -void VersionSet::AppendVersion(Version* v) { +void VersionSet::AppendVersion(Version *v) { // Make "v" current assert(v->refs_ == 0); assert(v != current_); @@ -819,7 +794,7 @@ void VersionSet::AppendVersion(Version* v) { v->next_->prev_ = v; } -Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu) { +Status VersionSet::LogAndApply(VersionEdit *edit, port::Mutex *mu) { if (edit->has_log_number_) { assert(edit->log_number_ >= log_number_); assert(edit->log_number_ < next_file_number_); @@ -834,7 +809,7 @@ Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu) { edit->SetNextFile(next_file_number_); edit->SetLastSequence(last_sequence_); - Version* v = new Version(this); + Version *v = new Version(this); { Builder builder(this, current_); builder.Apply(edit); @@ -906,9 +881,10 @@ Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu) { Status VersionSet::Recover(bool *save_manifest) { struct LogReporter : public log::Reader::Reporter { - Status* status; - virtual void Corruption(size_t bytes, const Status& s) { - if (this->status->ok()) *this->status = s; + Status *status; + virtual void Corruption(size_t bytes, const Status &s) { + if (this->status->ok()) + *this->status = s; } }; @@ -918,18 +894,18 @@ Status VersionSet::Recover(bool *save_manifest) { if (!s.ok()) { return s; } - if (current.empty() || current[current.size()-1] != '\n') { + if (current.empty() || current[current.size() - 1] != '\n') { return Status::Corruption("CURRENT file does not end with newline"); } current.resize(current.size() - 1); std::string dscname = dbname_ + "/" + current; - SequentialFile* file; + SequentialFile *file; s = env_->NewSequentialFile(dscname, &file); if (!s.ok()) { if (s.IsNotFound()) { - return Status::Corruption( - "CURRENT points to a non-existent file", s.ToString()); + return Status::Corruption("CURRENT points to a non-existent file", + s.ToString()); } return s; } @@ -947,7 +923,8 @@ Status VersionSet::Recover(bool *save_manifest) { { LogReporter reporter; reporter.status = &s; - log::Reader reader(file, &reporter, true/*checksum*/, 0/*initial_offset*/); + log::Reader reader(file, &reporter, true /*checksum*/, + 0 /*initial_offset*/); Slice record; std::string scratch; while (reader.ReadRecord(&record, &scratch) && s.ok()) { @@ -1008,7 +985,7 @@ Status VersionSet::Recover(bool *save_manifest) { } if (s.ok()) { - Version* v = new Version(this); + Version *v = new Version(this); builder.SaveTo(v); // Install recovered version Finalize(v); @@ -1030,8 +1007,8 @@ Status VersionSet::Recover(bool *save_manifest) { return s; } -bool VersionSet::ReuseManifest(const std::string& dscname, - const std::string& dscbase) { +bool VersionSet::ReuseManifest(const std::string &dscname, + const std::string &dscbase) { if (!options_->reuse_logs) { return false; } @@ -1067,12 +1044,12 @@ void VersionSet::MarkFileNumberUsed(uint64_t number) { } } -void VersionSet::Finalize(Version* v) { +void VersionSet::Finalize(Version *v) { // Precomputed best level for next compaction int best_level = -1; double best_score = -1; - for (int level = 0; level < config::kNumLevels-1; level++) { + for (int level = 0; level < config::kNumLevels - 1; level++) { double score; if (level == 0) { // We treat level-0 specially by bounding the number of files @@ -1087,7 +1064,7 @@ void VersionSet::Finalize(Version* v) { // setting, or very high compression ratios, or lots of // overwrites/deletions). score = v->files_[level].size() / - static_cast(config::kL0_CompactionTrigger); + static_cast(config::kL0_CompactionTrigger); } else { // Compute the ratio of current size to size limit. const uint64_t level_bytes = TotalFileSize(v->files_[level]); @@ -1105,7 +1082,7 @@ void VersionSet::Finalize(Version* v) { v->compaction_score_ = best_score; } -Status VersionSet::WriteSnapshot(log::Writer* log) { +Status VersionSet::WriteSnapshot(log::Writer *log) { // TODO: Break up into multiple records to reduce memory usage on recovery? // Save metadata @@ -1123,9 +1100,9 @@ Status VersionSet::WriteSnapshot(log::Writer* log) { // Save files for (int level = 0; level < config::kNumLevels; level++) { - const std::vector& files = current_->files_[level]; + const std::vector &files = current_->files_[level]; for (size_t i = 0; i < files.size(); i++) { - const FileMetaData* f = files[i]; + const FileMetaData *f = files[i]; edit.AddFile(level, f->number, f->file_size, f->smallest, f->largest); } } @@ -1141,25 +1118,21 @@ int VersionSet::NumLevelFiles(int level) const { return current_->files_[level].size(); } -const char* VersionSet::LevelSummary(LevelSummaryStorage* scratch) const { +const char *VersionSet::LevelSummary(LevelSummaryStorage *scratch) const { // Update code if kNumLevels changes assert(config::kNumLevels == 7); snprintf(scratch->buffer, sizeof(scratch->buffer), - "files[ %d %d %d %d %d %d %d ]", - int(current_->files_[0].size()), - int(current_->files_[1].size()), - int(current_->files_[2].size()), - int(current_->files_[3].size()), - int(current_->files_[4].size()), - int(current_->files_[5].size()), - int(current_->files_[6].size())); + "files[ %d %d %d %d %d %d %d ]", int(current_->files_[0].size()), + int(current_->files_[1].size()), int(current_->files_[2].size()), + int(current_->files_[3].size()), int(current_->files_[4].size()), + int(current_->files_[5].size()), int(current_->files_[6].size())); return scratch->buffer; } -uint64_t VersionSet::ApproximateOffsetOf(Version* v, const InternalKey& ikey) { +uint64_t VersionSet::ApproximateOffsetOf(Version *v, const InternalKey &ikey) { uint64_t result = 0; for (int level = 0; level < config::kNumLevels; level++) { - const std::vector& files = v->files_[level]; + const std::vector &files = v->files_[level]; for (size_t i = 0; i < files.size(); i++) { if (icmp_.Compare(files[i]->largest, ikey) <= 0) { // Entire file is before "ikey", so just add the file size @@ -1175,8 +1148,8 @@ uint64_t VersionSet::ApproximateOffsetOf(Version* v, const InternalKey& ikey) { } else { // "ikey" falls in the range for this table. Add the // approximate offset of "ikey" within the table. - Table* tableptr; - Iterator* iter = table_cache_->NewIterator( + Table *tableptr; + Iterator *iter = table_cache_->NewIterator( ReadOptions(), files[i]->number, files[i]->file_size, &tableptr); if (tableptr != nullptr) { result += tableptr->ApproximateOffsetOf(ikey.Encode()); @@ -1188,12 +1161,11 @@ uint64_t VersionSet::ApproximateOffsetOf(Version* v, const InternalKey& ikey) { return result; } -void VersionSet::AddLiveFiles(std::set* live) { - for (Version* v = dummy_versions_.next_; - v != &dummy_versions_; +void VersionSet::AddLiveFiles(std::set *live) { + for (Version *v = dummy_versions_.next_; v != &dummy_versions_; v = v->next_) { for (int level = 0; level < config::kNumLevels; level++) { - const std::vector& files = v->files_[level]; + const std::vector &files = v->files_[level]; for (size_t i = 0; i < files.size(); i++) { live->insert(files[i]->number); } @@ -1209,11 +1181,11 @@ int64_t VersionSet::NumLevelBytes(int level) const { int64_t VersionSet::MaxNextLevelOverlappingBytes() { int64_t result = 0; - std::vector overlaps; + std::vector overlaps; for (int level = 1; level < config::kNumLevels - 1; level++) { for (size_t i = 0; i < current_->files_[level].size(); i++) { - const FileMetaData* f = current_->files_[level][i]; - current_->GetOverlappingInputs(level+1, &f->smallest, &f->largest, + const FileMetaData *f = current_->files_[level][i]; + current_->GetOverlappingInputs(level + 1, &f->smallest, &f->largest, &overlaps); const int64_t sum = TotalFileSize(overlaps); if (sum > result) { @@ -1227,14 +1199,13 @@ int64_t VersionSet::MaxNextLevelOverlappingBytes() { // Stores the minimal range that covers all entries in inputs in // *smallest, *largest. // REQUIRES: inputs is not empty -void VersionSet::GetRange(const std::vector& inputs, - InternalKey* smallest, - InternalKey* largest) { +void VersionSet::GetRange(const std::vector &inputs, + InternalKey *smallest, InternalKey *largest) { assert(!inputs.empty()); smallest->Clear(); largest->Clear(); for (size_t i = 0; i < inputs.size(); i++) { - FileMetaData* f = inputs[i]; + FileMetaData *f = inputs[i]; if (i == 0) { *smallest = f->smallest; *largest = f->largest; @@ -1252,16 +1223,15 @@ void VersionSet::GetRange(const std::vector& inputs, // Stores the minimal range that covers all entries in inputs1 and inputs2 // in *smallest, *largest. // REQUIRES: inputs is not empty -void VersionSet::GetRange2(const std::vector& inputs1, - const std::vector& inputs2, - InternalKey* smallest, - InternalKey* largest) { - std::vector all = inputs1; +void VersionSet::GetRange2(const std::vector &inputs1, + const std::vector &inputs2, + InternalKey *smallest, InternalKey *largest) { + std::vector all = inputs1; all.insert(all.end(), inputs2.begin(), inputs2.end()); GetRange(all, smallest, largest); } -Iterator* VersionSet::MakeInputIterator(Compaction* c) { +Iterator *VersionSet::MakeInputIterator(Compaction *c) { ReadOptions options; options.verify_checksums = options_->paranoid_checks; options.fill_cache = false; @@ -1269,19 +1239,27 @@ Iterator* VersionSet::MakeInputIterator(Compaction* c) { // Level-0 files have to be merged together. For other levels, // we will make a concatenating iterator per level. // TODO(opt): use concatenating iterator for level-0 if there is no overlap + /* + level-0文件必须合并在一起。对于其他level,我们将每level创建一个连接迭代器(concatenating + iterator)。 如果是level0,则取level0的文件数量+1,否则取2 + */ const int space = (c->level() == 0 ? c->inputs_[0].size() + 1 : 2); - Iterator** list = new Iterator*[space]; + Iterator **list = new Iterator *[space]; int num = 0; + // 这里的2表示 level 与 level+1 for (int which = 0; which < 2; which++) { if (!c->inputs_[which].empty()) { + //上层level,即level,并且还是level0 if (c->level() + which == 0) { - const std::vector& files = c->inputs_[which]; - for (size_t i = 0; i < files.size(); i++) { - list[num++] = table_cache_->NewIterator( - options, files[i]->number, files[i]->file_size); + // 合并所有level-0文件 + const std::vector &files = c->inputs_[which]; + for (size_t i = 0; i < files.size(); i++) { // 遍历level0文件 + list[num++] = table_cache_->NewIterator(options, files[i]->number, + files[i]->file_size); } - } else { + } else { // 下层level,即level+1 // Create concatenating iterator for the files from this level + // 为这个级别的文件创建连接迭代器 list[num++] = NewTwoLevelIterator( new Version::LevelFileNumIterator(icmp_, &c->inputs_[which]), &GetFileIterator, table_cache_, options); @@ -1289,13 +1267,13 @@ Iterator* VersionSet::MakeInputIterator(Compaction* c) { } } assert(num <= space); - Iterator* result = NewMergingIterator(&icmp_, list, num); + Iterator *result = NewMergingIterator(&icmp_, list, num); delete[] list; return result; } -Compaction* VersionSet::PickCompaction() { - Compaction* c; +Compaction *VersionSet::PickCompaction() { + Compaction *c; int level; // We prefer compactions triggered by too much data in a level over @@ -1305,12 +1283,12 @@ Compaction* VersionSet::PickCompaction() { if (size_compaction) { level = current_->compaction_level_; assert(level >= 0); - assert(level+1 < config::kNumLevels); + assert(level + 1 < config::kNumLevels); c = new Compaction(options_, level); // Pick the first file that comes after compact_pointer_[level] for (size_t i = 0; i < current_->files_[level].size(); i++) { - FileMetaData* f = current_->files_[level][i]; + FileMetaData *f = current_->files_[level][i]; if (compact_pointer_[level].empty() || icmp_.Compare(f->largest.Encode(), compact_pointer_[level]) > 0) { c->inputs_[0].push_back(f); @@ -1350,15 +1328,15 @@ Compaction* VersionSet::PickCompaction() { // Finds the largest key in a vector of files. Returns true if files it not // empty. -bool FindLargestKey(const InternalKeyComparator& icmp, - const std::vector& files, - InternalKey* largest_key) { +bool FindLargestKey(const InternalKeyComparator &icmp, + const std::vector &files, + InternalKey *largest_key) { if (files.empty()) { return false; } *largest_key = files[0]->largest; for (size_t i = 1; i < files.size(); ++i) { - FileMetaData* f = files[i]; + FileMetaData *f = files[i]; if (icmp.Compare(f->largest, *largest_key) > 0) { *largest_key = f->largest; } @@ -1368,14 +1346,14 @@ bool FindLargestKey(const InternalKeyComparator& icmp, // Finds minimum file b2=(l2, u2) in level file for which l2 > u1 and // user_key(l2) = user_key(u1) -FileMetaData* FindSmallestBoundaryFile( - const InternalKeyComparator& icmp, - const std::vector& level_files, - const InternalKey& largest_key) { - const Comparator* user_cmp = icmp.user_comparator(); - FileMetaData* smallest_boundary_file = nullptr; +FileMetaData * +FindSmallestBoundaryFile(const InternalKeyComparator &icmp, + const std::vector &level_files, + const InternalKey &largest_key) { + const Comparator *user_cmp = icmp.user_comparator(); + FileMetaData *smallest_boundary_file = nullptr; for (size_t i = 0; i < level_files.size(); ++i) { - FileMetaData* f = level_files[i]; + FileMetaData *f = level_files[i]; if (icmp.Compare(f->smallest, largest_key) > 0 && user_cmp->Compare(f->smallest.user_key(), largest_key.user_key()) == 0) { @@ -1402,9 +1380,9 @@ FileMetaData* FindSmallestBoundaryFile( // parameters: // in level_files: List of files to search for boundary files. // in/out compaction_files: List of files to extend by adding boundary files. -void AddBoundaryInputs(const InternalKeyComparator& icmp, - const std::vector& level_files, - std::vector* compaction_files) { +void AddBoundaryInputs(const InternalKeyComparator &icmp, + const std::vector &level_files, + std::vector *compaction_files) { InternalKey largest_key; // Quick return if compaction_files is empty. @@ -1414,7 +1392,7 @@ void AddBoundaryInputs(const InternalKeyComparator& icmp, bool continue_searching = true; while (continue_searching) { - FileMetaData* smallest_boundary_file = + FileMetaData *smallest_boundary_file = FindSmallestBoundaryFile(icmp, level_files, largest_key); // If a boundary file was found advance largest_key, otherwise we're done. @@ -1427,14 +1405,15 @@ void AddBoundaryInputs(const InternalKeyComparator& icmp, } } -void VersionSet::SetupOtherInputs(Compaction* c) { +void VersionSet::SetupOtherInputs(Compaction *c) { const int level = c->level(); InternalKey smallest, largest; AddBoundaryInputs(icmp_, current_->files_[level], &c->inputs_[0]); GetRange(c->inputs_[0], &smallest, &largest); - current_->GetOverlappingInputs(level+1, &smallest, &largest, &c->inputs_[1]); + current_->GetOverlappingInputs(level + 1, &smallest, &largest, + &c->inputs_[1]); // Get entire range covered by compaction InternalKey all_start, all_limit; @@ -1443,7 +1422,7 @@ void VersionSet::SetupOtherInputs(Compaction* c) { // See if we can grow the number of inputs in "level" without // changing the number of "level+1" files we pick up. if (!c->inputs_[1].empty()) { - std::vector expanded0; + std::vector expanded0; current_->GetOverlappingInputs(level, &all_start, &all_limit, &expanded0); AddBoundaryInputs(icmp_, current_->files_[level], &expanded0); const int64_t inputs0_size = TotalFileSize(c->inputs_[0]); @@ -1454,19 +1433,15 @@ void VersionSet::SetupOtherInputs(Compaction* c) { ExpandedCompactionByteSizeLimit(options_)) { InternalKey new_start, new_limit; GetRange(expanded0, &new_start, &new_limit); - std::vector expanded1; - current_->GetOverlappingInputs(level+1, &new_start, &new_limit, + std::vector expanded1; + current_->GetOverlappingInputs(level + 1, &new_start, &new_limit, &expanded1); if (expanded1.size() == c->inputs_[1].size()) { Log(options_->info_log, "Expanding@%d %d+%d (%ld+%ld bytes) to %d+%d (%ld+%ld bytes)\n", - level, - int(c->inputs_[0].size()), - int(c->inputs_[1].size()), - long(inputs0_size), long(inputs1_size), - int(expanded0.size()), - int(expanded1.size()), - long(expanded0_size), long(inputs1_size)); + level, int(c->inputs_[0].size()), int(c->inputs_[1].size()), + long(inputs0_size), long(inputs1_size), int(expanded0.size()), + int(expanded1.size()), long(expanded0_size), long(inputs1_size)); smallest = new_start; largest = new_limit; c->inputs_[0] = expanded0; @@ -1491,11 +1466,9 @@ void VersionSet::SetupOtherInputs(Compaction* c) { c->edit_.SetCompactPointer(level, largest); } -Compaction* VersionSet::CompactRange( - int level, - const InternalKey* begin, - const InternalKey* end) { - std::vector inputs; +Compaction *VersionSet::CompactRange(int level, const InternalKey *begin, + const InternalKey *end) { + std::vector inputs; current_->GetOverlappingInputs(level, begin, end, &inputs); if (inputs.empty()) { return nullptr; @@ -1518,7 +1491,7 @@ Compaction* VersionSet::CompactRange( } } - Compaction* c = new Compaction(options_, level); + Compaction *c = new Compaction(options_, level); c->input_version_ = current_; c->input_version_->Ref(); c->inputs_[0] = inputs; @@ -1526,12 +1499,9 @@ Compaction* VersionSet::CompactRange( return c; } -Compaction::Compaction(const Options* options, int level) - : level_(level), - max_output_file_size_(MaxFileSizeForLevel(options, level)), - input_version_(nullptr), - grandparent_index_(0), - seen_key_(false), +Compaction::Compaction(const Options *options, int level) + : level_(level), max_output_file_size_(MaxFileSizeForLevel(options, level)), + input_version_(nullptr), grandparent_index_(0), seen_key_(false), overlapped_bytes_(0) { for (int i = 0; i < config::kNumLevels; i++) { level_ptrs_[i] = 0; @@ -1545,7 +1515,7 @@ Compaction::~Compaction() { } bool Compaction::IsTrivialMove() const { - const VersionSet* vset = input_version_->vset_; + const VersionSet *vset = input_version_->vset_; // Avoid a move if there is lots of overlapping grandparent data. // Otherwise, the move could create a parent file that will require // a very expensive merge later on. @@ -1554,7 +1524,7 @@ bool Compaction::IsTrivialMove() const { MaxGrandParentOverlapBytes(vset->options_)); } -void Compaction::AddInputDeletions(VersionEdit* edit) { +void Compaction::AddInputDeletions(VersionEdit *edit) { for (int which = 0; which < 2; which++) { for (size_t i = 0; i < inputs_[which].size(); i++) { edit->DeleteFile(level_ + which, inputs_[which][i]->number); @@ -1562,13 +1532,13 @@ void Compaction::AddInputDeletions(VersionEdit* edit) { } } -bool Compaction::IsBaseLevelForKey(const Slice& user_key) { +bool Compaction::IsBaseLevelForKey(const Slice &user_key) { // Maybe use binary search to find right entry instead of linear search? - const Comparator* user_cmp = input_version_->vset_->icmp_.user_comparator(); + const Comparator *user_cmp = input_version_->vset_->icmp_.user_comparator(); for (int lvl = level_ + 2; lvl < config::kNumLevels; lvl++) { - const std::vector& files = input_version_->files_[lvl]; - for (; level_ptrs_[lvl] < files.size(); ) { - FileMetaData* f = files[level_ptrs_[lvl]]; + const std::vector &files = input_version_->files_[lvl]; + for (; level_ptrs_[lvl] < files.size();) { + FileMetaData *f = files[level_ptrs_[lvl]]; if (user_cmp->Compare(user_key, f->largest.user_key()) <= 0) { // We've advanced far enough if (user_cmp->Compare(user_key, f->smallest.user_key()) >= 0) { @@ -1583,13 +1553,15 @@ bool Compaction::IsBaseLevelForKey(const Slice& user_key) { return true; } -bool Compaction::ShouldStopBefore(const Slice& internal_key) { - const VersionSet* vset = input_version_->vset_; +bool Compaction::ShouldStopBefore(const Slice &internal_key) { + const VersionSet *vset = input_version_->vset_; // Scan to find earliest grandparent file that contains key. - const InternalKeyComparator* icmp = &vset->icmp_; + // 扫描以查找包含密钥的最早的grandparent文件。 + const InternalKeyComparator *icmp = &vset->icmp_; while (grandparent_index_ < grandparents_.size() && - icmp->Compare(internal_key, - grandparents_[grandparent_index_]->largest.Encode()) > 0) { + icmp->Compare(internal_key, + grandparents_[grandparent_index_]->largest.Encode()) > + 0) { if (seen_key_) { overlapped_bytes_ += grandparents_[grandparent_index_]->file_size; } @@ -1613,4 +1585,4 @@ void Compaction::ReleaseInputs() { } } -} // namespace leveldb +} // namespace leveldb diff --git a/db/version_set.h b/db/version_set.h index 7826297..eafbca7 100644 --- a/db/version_set.h +++ b/db/version_set.h @@ -81,7 +81,8 @@ public: // yield the contents of this Version when merged together. // REQUIRES: This version has been saved (see VersionSet::SaveTo) /** - * @brief 向*iters添加一个迭代器序列(所有level0文件,当合并在一起时将产生this version的内容。) + * @brief 向*iters添加一个迭代器序列(所有level0文件,当合并在一起时将产生this + * version的内容。) * * REQUIRES: 此version已被保存(参见VersionSet::SaveTo) */ @@ -515,45 +516,84 @@ private: }; // A Compaction encapsulates information about a compaction. +/** +* @brief 压缩封装了关于压缩的信息。 + */ class Compaction { public: ~Compaction(); // Return the level that is being compacted. Inputs from "level" // and "level+1" will be merged to produce a set of "level+1" files. + /** + * @brief 返回正在压缩的level。 + * + * 来自“level”和“level+1”的输入将被合并以产生一组“level+1”文件。 + */ int level() const { return level_; } // Return the object that holds the edits to the descriptor done // by this compaction. + /** + * @brief 返回一个对象,该对象保存由此compaction完成的对描述符的edits。 + */ VersionEdit *edit() { return &edit_; } // "which" must be either 0 or 1 + + /** + * @brief "which" 必须是0或1 + */ int num_input_files(int which) const { return inputs_[which].size(); } // Return the ith input file at "level()+which" ("which" must be 0 or 1). + /** + * @brief 返回第i个输入文件"level()+which"("which"必须为0或1)。 + */ FileMetaData *input(int which, int i) const { return inputs_[which][i]; } // Maximum size of files to build during this compaction. + /** + * @brief 在此compaction过程中要生成的文件的最大大小。 + */ uint64_t MaxOutputFileSize() const { return max_output_file_size_; } // Is this a trivial compaction that can be implemented by just // moving a single input file to the next level (no merging or splitting) + /** + * @brief 这是一种简单的compaction吗? + * + * 只需将单个输入文件移动到下一个级别(不合并或分割)就可以实现。 + */ bool IsTrivialMove() const; // Add all inputs to this compaction as delete operations to *edit. + /** + * @brief 将此compaction的所有输入作为删除操作添加到*edit。 + */ void AddInputDeletions(VersionEdit *edit); // Returns true if the information we have available guarantees that // the compaction is producing data in "level+1" for which no data exists // in levels greater than "level+1". + /** + * @brief 如果我们可用的信息保证压缩正在生成 “level+1” 中的数据, + * 而对于这些数据,在大于 “level+1” 的level中不存在任何数据,则返回 true。 + */ bool IsBaseLevelForKey(const Slice &user_key); // Returns true iff we should stop building the current output // before processing "internal_key". + /** + * @brief 如果我们应该在处理"internal_key"之前停止构建当前输出,则返回true。 + */ bool ShouldStopBefore(const Slice &internal_key); // Release the input version for the compaction, once the compaction // is successful. + /** + * @brief 一旦压缩成功,释放用于压缩的input version。 + */ void ReleaseInputs(); private: @@ -568,15 +608,36 @@ private: VersionEdit edit_; // Each compaction reads inputs from "level_" and "level_+1" + /** + * @brief 每次Compaction从“level_”和“level_+1”读取输入 + */ std::vector inputs_[2]; // The two sets of inputs // State used to check for number of overlapping grandparent files // (parent == level_ + 1, grandparent == level_ + 2) + /** + * @brief 用于检查重叠的祖父文件数量的状态(parent == level_ + 1, grandparent + * == level_ + 2) + */ std::vector grandparents_; - size_t grandparent_index_; // Index in grandparent_starts_ - bool seen_key_; // Some output key has been seen - int64_t overlapped_bytes_; // Bytes of overlap between current output - // and grandparent files + + // Index in grandparent_starts_ + /** + * @brief grandparent_starts_中的索引 + */ + size_t grandparent_index_; + + // Some output key has been seen + /** + * @brief 已经看到了一些output key + */ + bool seen_key_; + + // Bytes of overlap between current output and grandparent files + /** + * @brief 当前输出和父文件之间重叠的字节数 + */ + int64_t overlapped_bytes_; // State for implementing IsBaseLevelForKey @@ -584,6 +645,11 @@ private: // is that we are positioned at one of the file ranges for each // higher level than the ones involved in this compaction (i.e. for // all L >= level_ + 2). + /** + * @brief level_ptrs_保存input_version_->levels_的索引:我们的状态是, + * 对于每个比这个压缩所涉及的更高的level(即对于所有L >= level_ + 2), + * 我们定位在一个文件范围。 + */ size_t level_ptrs_[config::kNumLevels]; }; diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index 9c241e7..f4adf57 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -3,6 +3,15 @@ cmake_minimum_required(VERSION 3.9) project(case VERSION 0.1) set(CMAKE_BUILD_TYPE "Debug") +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) +set(CMAKE_CXX_COMPILER "/usr/bin/clang++") + +set(CMAKE_CXX_FLAGS -Wthread-safety) + +include(${CMAKE_SOURCE_DIR}/util.cmake) add_subdirectory(case) +add_subdirectory(race) diff --git a/demo/case/CMakeLists.txt b/demo/case/CMakeLists.txt index ce7fed2..a569fff 100644 --- a/demo/case/CMakeLists.txt +++ b/demo/case/CMakeLists.txt @@ -1,20 +1,8 @@ -set(include_dir_path /home/lutar/code/c/leveldb/demo/lib/include) -set(lib_dir_path /home/lutar/code/c/leveldb/demo/lib/lib) -set(leveldb_DIR /home/lutar/code/c/leveldb/demo/lib/lib/cmake/leveldb) -# find_package(leveldb REQUIRED PATHS /home/lutar/code/c/leveldb/demo/lib/lib/cmake/leveldb) -find_package(leveldb REQUIRED) +macro(compile_case name) + single_target_cmp(${name} ${CMAKE_SOURCE_DIR}/case) +endmacro(compile_case) -macro(single_target_cmp name src_path) - add_executable(${name} ${src_path}/${name}.cc) - target_include_directories(${name} PUBLIC ${include_dir_path}) - target_link_directories(${name} PUBLIC ${lib_dir_path}) - target_link_libraries(${name} PUBLIC leveldb) -endmacro(single_target_cmp) - -macro(case_cmp name) - single_target_cmp(${name} /home/lutar/code/c/leveldb/demo/case) -endmacro(case_cmp) - -case_cmp(open_close) +compile_case(open_close) +compile_case(compaction) diff --git a/demo/case/compaction.cc b/demo/case/compaction.cc new file mode 100644 index 0000000..eeedc7f --- /dev/null +++ b/demo/case/compaction.cc @@ -0,0 +1,35 @@ +#include +#include +#include + +#include "leveldb/db.h" +#include "leveldb/options.h" +#include "leveldb/slice.h" + +void put(uint64_t size, leveldb::DB *db) { + leveldb::WriteOptions op; + for (uint64_t i = 0; i < size; i++) { + leveldb::Slice key( + std::to_string(i) + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + db->Put(op, key, key); + } +} + +int main() { + std::cout << "hello world" << std::endl; + + leveldb::DB *db; + leveldb::Options options; + options.create_if_missing = true; + leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db); + assert(status.ok()); + + put(4096, db); + db->CompactRange(nullptr, nullptr); + + delete db; // This closes the database + + return 0; +} \ No newline at end of file diff --git a/demo/mutex.h b/demo/mutex.h new file mode 100644 index 0000000..a9749c1 --- /dev/null +++ b/demo/mutex.h @@ -0,0 +1,264 @@ +#ifndef THREAD_SAFETY_ANALYSIS_MUTEX_H +#define THREAD_SAFETY_ANALYSIS_MUTEX_H + +// Enable thread safety attributes only with clang. +// The attributes can be safely erased when compiling with other compilers. +#if defined(__clang__) && (!defined(SWIG)) +#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) +#else +#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op +#endif + +#define CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) + +#define SCOPED_CAPABILITY \ + THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) + +#define GUARDED_BY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) + +#define PT_GUARDED_BY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) + +#define ACQUIRED_BEFORE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) + +#define ACQUIRED_AFTER(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) + +#define REQUIRES(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) + +#define REQUIRES_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__)) + +#define ACQUIRE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__)) + +#define ACQUIRE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__)) + +#define RELEASE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__)) + +#define RELEASE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__)) + +#define RELEASE_GENERIC(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__)) + +#define TRY_ACQUIRE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__)) + +#define TRY_ACQUIRE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__)) + +#define EXCLUDES(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) + +#define ASSERT_CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) + +#define ASSERT_SHARED_CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x)) + +#define RETURN_CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) + +#define NO_THREAD_SAFETY_ANALYSIS \ + THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) + + +// Defines an annotated interface for mutexes. +// These methods can be implemented to use any internal mutex implementation. +class CAPABILITY("mutex") Mutex { +public: + // Acquire/lock this mutex exclusively. Only one thread can have exclusive + // access at any one time. Write operations to guarded data require an + // exclusive lock. + void Lock() ACQUIRE(); + + // Acquire/lock this mutex for read operations, which require only a shared + // lock. This assumes a multiple-reader, single writer semantics. Multiple + // threads may acquire the mutex simultaneously as readers, but a writer + // must wait for all of them to release the mutex before it can acquire it + // exclusively. + void ReaderLock() ACQUIRE_SHARED(); + + // Release/unlock an exclusive mutex. + void Unlock() RELEASE(); + + // Release/unlock a shared mutex. + void ReaderUnlock() RELEASE_SHARED(); + + // Generic unlock, can unlock exclusive and shared mutexes. + void GenericUnlock() RELEASE_GENERIC(); + + // Try to acquire the mutex. Returns true on success, and false on failure. + bool TryLock() TRY_ACQUIRE(true); + + // Try to acquire the mutex for read operations. + bool ReaderTryLock() TRY_ACQUIRE_SHARED(true); + + // Assert that this mutex is currently held by the calling thread. + void AssertHeld() ASSERT_CAPABILITY(this); + + // Assert that is mutex is currently held for read operations. + void AssertReaderHeld() ASSERT_SHARED_CAPABILITY(this); + + // For negative capabilities. + const Mutex& operator!() const { return *this; } +}; + +// Tag types for selecting a constructor. +struct adopt_lock_t {} inline constexpr adopt_lock = {}; +struct defer_lock_t {} inline constexpr defer_lock = {}; +struct shared_lock_t {} inline constexpr shared_lock = {}; + +// MutexLocker is an RAII class that acquires a mutex in its constructor, and +// releases it in its destructor. +class SCOPED_CAPABILITY MutexLocker { +private: + Mutex* mut; + bool locked; + +public: + // Acquire mu, implicitly acquire *this and associate it with mu. + MutexLocker(Mutex *mu) ACQUIRE(mu) : mut(mu), locked(true) { + mu->Lock(); + } + + // Assume mu is held, implicitly acquire *this and associate it with mu. + MutexLocker(Mutex *mu, adopt_lock_t) REQUIRES(mu) : mut(mu), locked(true) {} + + // Acquire mu in shared mode, implicitly acquire *this and associate it with mu. + MutexLocker(Mutex *mu, shared_lock_t) ACQUIRE_SHARED(mu) : mut(mu), locked(true) { + mu->ReaderLock(); + } + + // Assume mu is held in shared mode, implicitly acquire *this and associate it with mu. + MutexLocker(Mutex *mu, adopt_lock_t, shared_lock_t) REQUIRES_SHARED(mu) + : mut(mu), locked(true) {} + + // Assume mu is not held, implicitly acquire *this and associate it with mu. + MutexLocker(Mutex *mu, defer_lock_t) EXCLUDES(mu) : mut(mu), locked(false) {} + + // Same as constructors, but without tag types. (Requires C++17 copy elision.) + static MutexLocker Lock(Mutex *mu) ACQUIRE(mu); + static MutexLocker Adopt(Mutex *mu) REQUIRES(mu); + static MutexLocker ReaderLock(Mutex *mu) ACQUIRE_SHARED(mu); + static MutexLocker AdoptReaderLock(Mutex *mu) REQUIRES_SHARED(mu); + static MutexLocker DeferLock(Mutex *mu) EXCLUDES(mu); + + // Release *this and all associated mutexes, if they are still held. + // There is no warning if the scope was already unlocked before. + ~MutexLocker() RELEASE() { + if (locked) + mut->GenericUnlock(); + } + + // Acquire all associated mutexes exclusively. + void Lock() ACQUIRE() { + mut->Lock(); + locked = true; + } + + // Try to acquire all associated mutexes exclusively. + bool TryLock() TRY_ACQUIRE(true) { + return locked = mut->TryLock(); + } + + // Acquire all associated mutexes in shared mode. + void ReaderLock() ACQUIRE_SHARED() { + mut->ReaderLock(); + locked = true; + } + + // Try to acquire all associated mutexes in shared mode. + bool ReaderTryLock() TRY_ACQUIRE_SHARED(true) { + return locked = mut->ReaderTryLock(); + } + + // Release all associated mutexes. Warn on double unlock. + void Unlock() RELEASE() { + mut->Unlock(); + locked = false; + } + + // Release all associated mutexes. Warn on double unlock. + void ReaderUnlock() RELEASE() { + mut->ReaderUnlock(); + locked = false; + } +}; + + +#ifdef USE_LOCK_STYLE_THREAD_SAFETY_ATTRIBUTES +// The original version of thread safety analysis the following attribute +// definitions. These use a lock-based terminology. They are still in use +// by existing thread safety code, and will continue to be supported. + +// Deprecated. +#define PT_GUARDED_VAR \ + THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_var) + +// Deprecated. +#define GUARDED_VAR \ + THREAD_ANNOTATION_ATTRIBUTE__(guarded_var) + +// Replaced by REQUIRES +#define EXCLUSIVE_LOCKS_REQUIRED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(exclusive_locks_required(__VA_ARGS__)) + +// Replaced by REQUIRES_SHARED +#define SHARED_LOCKS_REQUIRED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(shared_locks_required(__VA_ARGS__)) + +// Replaced by CAPABILITY +#define LOCKABLE \ + THREAD_ANNOTATION_ATTRIBUTE__(lockable) + +// Replaced by SCOPED_CAPABILITY +#define SCOPED_LOCKABLE \ + THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) + +// Replaced by ACQUIRE +#define EXCLUSIVE_LOCK_FUNCTION(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(exclusive_lock_function(__VA_ARGS__)) + +// Replaced by ACQUIRE_SHARED +#define SHARED_LOCK_FUNCTION(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(shared_lock_function(__VA_ARGS__)) + +// Replaced by RELEASE and RELEASE_SHARED +#define UNLOCK_FUNCTION(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(unlock_function(__VA_ARGS__)) + +// Replaced by TRY_ACQUIRE +#define EXCLUSIVE_TRYLOCK_FUNCTION(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(exclusive_trylock_function(__VA_ARGS__)) + +// Replaced by TRY_ACQUIRE_SHARED +#define SHARED_TRYLOCK_FUNCTION(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(shared_trylock_function(__VA_ARGS__)) + +// Replaced by ASSERT_CAPABILITY +#define ASSERT_EXCLUSIVE_LOCK(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(assert_exclusive_lock(__VA_ARGS__)) + +// Replaced by ASSERT_SHARED_CAPABILITY +#define ASSERT_SHARED_LOCK(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_lock(__VA_ARGS__)) + +// Replaced by EXCLUDE_CAPABILITY. +#define LOCKS_EXCLUDED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) + +// Replaced by RETURN_CAPABILITY +#define LOCK_RETURNED(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) + +#endif // USE_LOCK_STYLE_THREAD_SAFETY_ATTRIBUTES + +#endif // THREAD_SAFETY_ANALYSIS_MUTEX_H \ No newline at end of file diff --git a/demo/race/CMakeLists.txt b/demo/race/CMakeLists.txt new file mode 100644 index 0000000..2ec8619 --- /dev/null +++ b/demo/race/CMakeLists.txt @@ -0,0 +1,8 @@ + +macro(compile_race name) + single_target_cmp(${name} ${CMAKE_SOURCE_DIR}/race) +endmacro(compile_race) + +compile_race(race) +compile_race(race_safe) +compile_race(w_thread_safe) diff --git a/demo/race/race.cc b/demo/race/race.cc new file mode 100644 index 0000000..058049d --- /dev/null +++ b/demo/race/race.cc @@ -0,0 +1,28 @@ +#include +#include + +// 全局变量 +int counter = 0; + +void* increment_counter(void *arg) { + for (int i = 0; i < 100000; ++i) { + ++counter; + } + return nullptr; +} + +void hello() { printf("hello\n"); } + +int main() { + // 创建两个线程 + std::thread thread1(increment_counter, &counter); + std::thread thread2(increment_counter, &counter); + + // 等待两个线程完成 + thread1.join(); + thread2.join(); + + printf("%d\n", counter); + + return 0; +} \ No newline at end of file diff --git a/demo/race/race_safe.cc b/demo/race/race_safe.cc new file mode 100644 index 0000000..7e6bd65 --- /dev/null +++ b/demo/race/race_safe.cc @@ -0,0 +1,32 @@ +#include +#include +#include + +// 全局变量 +int counter = 0; +std::mutex mu; + +void* increment_counter(void *arg) { + for (int i = 0; i < 100000; ++i) { + mu.lock(); + ++counter; + mu.unlock(); + } + return nullptr; +} + +void hello() { printf("hello\n"); } + +int main() { + // 创建两个线程 + std::thread thread1(increment_counter, &counter); + std::thread thread2(increment_counter, &counter); + + // 等待两个线程完成 + thread1.join(); + thread2.join(); + + printf("%d\n", counter); + + return 0; +} \ No newline at end of file diff --git a/demo/race/w_thread_safe.cc b/demo/race/w_thread_safe.cc new file mode 100644 index 0000000..7884483 --- /dev/null +++ b/demo/race/w_thread_safe.cc @@ -0,0 +1,33 @@ +#include "mutex.h" + +class BankAccount { +private: + Mutex mu; + int balance GUARDED_BY(mu); + + void depositImpl(int amount) { + balance += amount; // WARNING! Cannot write balance without locking mu. + } + + void withdrawImpl(int amount) REQUIRES(mu) { + balance -= amount; // OK. Caller must have locked mu. + } + +public: + void withdraw(int amount) { + mu.Lock(); + withdrawImpl(amount); // OK. We've locked mu. + } // WARNING! Failed to unlock mu. + + void transferFrom(BankAccount &b, int amount) { + mu.Lock(); + b.withdrawImpl( + amount); // WARNING! Calling withdrawImpl() requires locking b.mu. + depositImpl(amount); // OK. depositImpl() has no requirements. + mu.Unlock(); + } +}; + +int main(){ + return 0; +} \ No newline at end of file diff --git a/demo/util.cmake b/demo/util.cmake new file mode 100644 index 0000000..50b1731 --- /dev/null +++ b/demo/util.cmake @@ -0,0 +1,25 @@ + + +if(util_is_included) + message("include util.cmake: skip ") +else() + message("include util.cmake") + + set(include_dir_path ${CMAKE_SOURCE_DIR}/lib/include) + set(lib_dir_path ${CMAKE_SOURCE_DIR}/lib/lib) + + set(leveldb_DIR ${CMAKE_SOURCE_DIR}/lib/lib/cmake/leveldb) + + # find_package(leveldb REQUIRED PATHS ${CMAKE_SOURCE_DIR}/lib/lib/cmake/leveldb) + find_package(leveldb REQUIRED) + + set(util_is_included ON) +endif() + +macro(single_target_cmp name src_path) + add_executable(${name} ${src_path}/${name}.cc) + target_include_directories(${name} PUBLIC ${include_dir_path}) + target_include_directories(${name} PUBLIC ${CMAKE_SOURCE_DIR}) + target_link_directories(${name} PUBLIC ${lib_dir_path}) + target_link_libraries(${name} PUBLIC leveldb) +endmacro(single_target_cmp) diff --git a/doc/learn/compaction.md b/doc/learn/compaction.md new file mode 100644 index 0000000..e10f5d5 --- /dev/null +++ b/doc/learn/compaction.md @@ -0,0 +1,15 @@ + + +DoCompactionWork + +- 对imm进行压缩:DBImpl.CompactMemTable + - DBImpl.WrteLevel0Table + - BuildTable + - TableCache.NewIterator + - TableCache.FindTable + +- versionset.MakeInputIterator + - TableCache.NewIterator + - TableCache.FindTable + - NewMergingIterator + diff --git a/doc/learn/db.md b/doc/learn/db.md index d5ec086..824f248 100644 --- a/doc/learn/db.md +++ b/doc/learn/db.md @@ -1,3 +1,11 @@ +## 并发接口 + + + + + + + ## DB ``` c++ @@ -348,3 +356,9 @@ entry格式 ## Version + + + + +## Cache + diff --git a/include/leveldb/cache.h b/include/leveldb/cache.h index a238063..48c0ef1 100644 --- a/include/leveldb/cache.h +++ b/include/leveldb/cache.h @@ -15,6 +15,17 @@ // they want something more sophisticated (like scan-resistance, a // custom eviction policy, variable cache sizing, etc.) +/** + * 缓存是一个将键映射到值的接口。它具有内部同步,可以从多个线程安全地并发访问。 + * 它可以自动退出条目,为新条目腾出空间。 + * Values对缓存容量有指定的花费。 + * 例如,一个值是可变长度字符串的缓存,可以使用字符串的长度作为字符串的花费。 + * + * 提供了一个内置缓存实现,其中包含最近最少使用(least-recently-used)的退出策略。 + * 如果客户端想要更复杂的东西(如防扫描、自定义退出策略、可变缓存大小等),他们可以使用自己的实现。 + * + */ + #ifndef STORAGE_LEVELDB_INCLUDE_CACHE_H_ #define STORAGE_LEVELDB_INCLUDE_CACHE_H_ @@ -28,6 +39,11 @@ class LEVELDB_EXPORT Cache; // Create a new cache with a fixed size capacity. This implementation // of Cache uses a least-recently-used eviction policy. + +/** + * @brief 创建一个指定大小空间的cache + * 该cache以LRU实现 + */ LEVELDB_EXPORT Cache *NewLRUCache(size_t capacity); class LEVELDB_EXPORT Cache { @@ -39,9 +55,15 @@ public: // Destroys all existing entries by calling the "deleter" // function that was passed to the constructor. + /** + * @brief 通过调用传递给构造函数的“deleter”函数来销毁所有现有条目。 + */ virtual ~Cache(); // Opaque handle to an entry stored in the cache. + /** + * @brief 存储在缓存中的项的不透明句柄。 + */ struct Handle {}; // Insert a mapping from key->value into the cache and assign it diff --git a/include/leveldb/table.h b/include/leveldb/table.h index 5423667..58dbd5b 100644 --- a/include/leveldb/table.h +++ b/include/leveldb/table.h @@ -40,6 +40,7 @@ public: /** * @brief 尝试打开以bytes[0..file_size]存储的“file”表, * 并读取从表中检索数据所需的 metadata entries。 + * 因为footer是放在文件的最后,所以若读取footer失败,则表示通过file_size读取的文件不是一个合法的sstable文件。 * * 如果成功,返回ok并将“*table”设置为新打开的表。 * 当不再需要时,客户端应该删除“*table”。 diff --git a/util/cache.cc b/util/cache.cc index 7cc2cea..a4b40c9 100644 --- a/util/cache.cc +++ b/util/cache.cc @@ -14,8 +14,7 @@ namespace leveldb { -Cache::~Cache() { -} +Cache::~Cache() {} namespace { @@ -38,24 +37,45 @@ namespace { // when they detect an element in the cache acquiring or losing its only // external reference. +/** + * @brief LRU cache implementation + * + * 缓存项有一个“in_cache”布尔值,表示缓存是否对该项有引用。 + * 在不将条目传递给其“deleter”的情况下,该值变为false的唯一方法是通过Erase()、通过Insert()插入具有重复键的元素时或销毁缓存。 + * + * 缓存在缓存中保留两个项的链表。缓存中的所有项都在其中一个列表中,而不是同时在两个列表中。 + * 仍然被客户端引用但从缓存中删除的项不在这两个列表中。 + * The lists are: + * - in-use: 包含客户端当前引用的项,没有特定的顺序。 + * (此列表用于不变检查。如果我们删除了check,那么原本在这个列表上的元素可能会被保留为断开连接的单例列表。) + * + * - LRU: 包含当前未被客户端引用的项,在LRU顺序中, + * 当它们检测到缓存中的元素获取或丢失其唯一的外部引用时,元素通过Ref()和Unref()方法在这些列表之间移动。 + * + */ + // An entry is a variable length heap-allocated structure. Entries // are kept in a circular doubly linked list ordered by access time. +/** + * @brief 表项是可变长度的堆分配结构。条目保存在按访问时间排序的循环双链表中。 + */ struct LRUHandle { - void* value; - void (*deleter)(const Slice&, void* value); - LRUHandle* next_hash; - LRUHandle* next; - LRUHandle* prev; - size_t charge; // TODO(opt): Only allow uint32_t? + void *value; + void (*deleter)(const Slice &, void *value); + LRUHandle *next_hash; + LRUHandle *next; + LRUHandle *prev; + size_t charge; // TODO(opt): Only allow uint32_t? size_t key_length; - bool in_cache; // Whether entry is in the cache. - uint32_t refs; // References, including cache reference, if present. - uint32_t hash; // Hash of key(); used for fast sharding and comparisons - char key_data[1]; // Beginning of key + bool in_cache; // Whether entry is in the cache. + uint32_t refs; // References, including cache reference, if present. + uint32_t hash; // Hash of key(); used for fast sharding and comparisons + char key_data[1]; // Beginning of key Slice key() const { // next_ is only equal to this if the LRU handle is the list head of an // empty list. List heads never have meaningful keys. + // 只有当LRU句柄是空列表的列表头时,next_才等于this。列表头从来没有有意义的键。 assert(next != this); return Slice(key_data, key_length); @@ -67,18 +87,24 @@ struct LRUHandle { // table implementations in some of the compiler/runtime combinations // we have tested. E.g., readrandom speeds up by ~5% over the g++ // 4.4.3's builtin hashtable. + +/** + * @brief 我们提供了自己的简单哈希表, + * 因为它消除了一大堆移植操作,而且在我们测试过的一些编译器/运行时组合中,它比一些内置哈希表实现要快。 + * 例如,readrandom的速度比g++ 4.4.3的内置哈希表提高了约5%。 + */ class HandleTable { - public: +public: HandleTable() : length_(0), elems_(0), list_(nullptr) { Resize(); } ~HandleTable() { delete[] list_; } - LRUHandle* Lookup(const Slice& key, uint32_t hash) { + LRUHandle *Lookup(const Slice &key, uint32_t hash) { return *FindPointer(key, hash); } - LRUHandle* Insert(LRUHandle* h) { - LRUHandle** ptr = FindPointer(h->key(), h->hash); - LRUHandle* old = *ptr; + LRUHandle *Insert(LRUHandle *h) { + LRUHandle **ptr = FindPointer(h->key(), h->hash); + LRUHandle *old = *ptr; h->next_hash = (old == nullptr ? nullptr : old->next_hash); *ptr = h; if (old == nullptr) { @@ -92,9 +118,9 @@ class HandleTable { return old; } - LRUHandle* Remove(const Slice& key, uint32_t hash) { - LRUHandle** ptr = FindPointer(key, hash); - LRUHandle* result = *ptr; + LRUHandle *Remove(const Slice &key, uint32_t hash) { + LRUHandle **ptr = FindPointer(key, hash); + LRUHandle *result = *ptr; if (result != nullptr) { *ptr = result->next_hash; --elems_; @@ -102,20 +128,19 @@ class HandleTable { return result; } - private: +private: // The table consists of an array of buckets where each bucket is // a linked list of cache entries that hash into the bucket. uint32_t length_; uint32_t elems_; - LRUHandle** list_; + LRUHandle **list_; // Return a pointer to slot that points to a cache entry that // matches key/hash. If there is no such cache entry, return a // pointer to the trailing slot in the corresponding linked list. - LRUHandle** FindPointer(const Slice& key, uint32_t hash) { - LRUHandle** ptr = &list_[hash & (length_ - 1)]; - while (*ptr != nullptr && - ((*ptr)->hash != hash || key != (*ptr)->key())) { + LRUHandle **FindPointer(const Slice &key, uint32_t hash) { + LRUHandle **ptr = &list_[hash & (length_ - 1)]; + while (*ptr != nullptr && ((*ptr)->hash != hash || key != (*ptr)->key())) { ptr = &(*ptr)->next_hash; } return ptr; @@ -126,15 +151,15 @@ class HandleTable { while (new_length < elems_) { new_length *= 2; } - LRUHandle** new_list = new LRUHandle*[new_length]; + LRUHandle **new_list = new LRUHandle *[new_length]; memset(new_list, 0, sizeof(new_list[0]) * new_length); uint32_t count = 0; for (uint32_t i = 0; i < length_; i++) { - LRUHandle* h = list_[i]; + LRUHandle *h = list_[i]; while (h != nullptr) { - LRUHandle* next = h->next_hash; + LRUHandle *next = h->next_hash; uint32_t hash = h->hash; - LRUHandle** ptr = &new_list[hash & (new_length - 1)]; + LRUHandle **ptr = &new_list[hash & (new_length - 1)]; h->next_hash = *ptr; *ptr = h; h = next; @@ -149,8 +174,11 @@ class HandleTable { }; // A single shard of sharded cache. +/** + * 分片缓存的单个分片。 + */ class LRUCache { - public: +public: LRUCache(); ~LRUCache(); @@ -158,24 +186,24 @@ class LRUCache { void SetCapacity(size_t capacity) { capacity_ = capacity; } // Like Cache methods, but with an extra "hash" parameter. - Cache::Handle* Insert(const Slice& key, uint32_t hash, - void* value, size_t charge, - void (*deleter)(const Slice& key, void* value)); - Cache::Handle* Lookup(const Slice& key, uint32_t hash); - void Release(Cache::Handle* handle); - void Erase(const Slice& key, uint32_t hash); + Cache::Handle *Insert(const Slice &key, uint32_t hash, void *value, + size_t charge, + void (*deleter)(const Slice &key, void *value)); + Cache::Handle *Lookup(const Slice &key, uint32_t hash); + void Release(Cache::Handle *handle); + void Erase(const Slice &key, uint32_t hash); void Prune(); size_t TotalCharge() const { MutexLock l(&mutex_); return usage_; } - private: - void LRU_Remove(LRUHandle* e); - void LRU_Append(LRUHandle*list, LRUHandle* e); - void Ref(LRUHandle* e); - void Unref(LRUHandle* e); - bool FinishErase(LRUHandle* e) EXCLUSIVE_LOCKS_REQUIRED(mutex_); +private: + void LRU_Remove(LRUHandle *e); + void LRU_Append(LRUHandle *list, LRUHandle *e); + void Ref(LRUHandle *e); + void Unref(LRUHandle *e); + bool FinishErase(LRUHandle *e) EXCLUSIVE_LOCKS_REQUIRED(mutex_); // Initialized before use. size_t capacity_; @@ -196,8 +224,7 @@ class LRUCache { HandleTable table_ GUARDED_BY(mutex_); }; -LRUCache::LRUCache() - : usage_(0) { +LRUCache::LRUCache() : usage_(0) { // Make empty circular linked lists. lru_.next = &lru_; lru_.prev = &lru_; @@ -206,29 +233,29 @@ LRUCache::LRUCache() } LRUCache::~LRUCache() { - assert(in_use_.next == &in_use_); // Error if caller has an unreleased handle - for (LRUHandle* e = lru_.next; e != &lru_; ) { - LRUHandle* next = e->next; + assert(in_use_.next == &in_use_); // Error if caller has an unreleased handle + for (LRUHandle *e = lru_.next; e != &lru_;) { + LRUHandle *next = e->next; assert(e->in_cache); e->in_cache = false; - assert(e->refs == 1); // Invariant of lru_ list. + assert(e->refs == 1); // Invariant of lru_ list. Unref(e); e = next; } } -void LRUCache::Ref(LRUHandle* e) { - if (e->refs == 1 && e->in_cache) { // If on lru_ list, move to in_use_ list. +void LRUCache::Ref(LRUHandle *e) { + if (e->refs == 1 && e->in_cache) { // If on lru_ list, move to in_use_ list. LRU_Remove(e); LRU_Append(&in_use_, e); } e->refs++; } -void LRUCache::Unref(LRUHandle* e) { +void LRUCache::Unref(LRUHandle *e) { assert(e->refs > 0); e->refs--; - if (e->refs == 0) { // Deallocate. + if (e->refs == 0) { // Deallocate. assert(!e->in_cache); (*e->deleter)(e->key(), e->value); free(e); @@ -239,12 +266,12 @@ void LRUCache::Unref(LRUHandle* e) { } } -void LRUCache::LRU_Remove(LRUHandle* e) { +void LRUCache::LRU_Remove(LRUHandle *e) { e->next->prev = e->prev; e->prev->next = e->next; } -void LRUCache::LRU_Append(LRUHandle* list, LRUHandle* e) { +void LRUCache::LRU_Append(LRUHandle *list, LRUHandle *e) { // Make "e" newest entry by inserting just before *list e->next = list; e->prev = list->prev; @@ -252,61 +279,61 @@ void LRUCache::LRU_Append(LRUHandle* list, LRUHandle* e) { e->next->prev = e; } -Cache::Handle* LRUCache::Lookup(const Slice& key, uint32_t hash) { +Cache::Handle *LRUCache::Lookup(const Slice &key, uint32_t hash) { MutexLock l(&mutex_); - LRUHandle* e = table_.Lookup(key, hash); + LRUHandle *e = table_.Lookup(key, hash); if (e != nullptr) { Ref(e); } - return reinterpret_cast(e); + return reinterpret_cast(e); } -void LRUCache::Release(Cache::Handle* handle) { +void LRUCache::Release(Cache::Handle *handle) { MutexLock l(&mutex_); - Unref(reinterpret_cast(handle)); + Unref(reinterpret_cast(handle)); } -Cache::Handle* LRUCache::Insert( - const Slice& key, uint32_t hash, void* value, size_t charge, - void (*deleter)(const Slice& key, void* value)) { +Cache::Handle * +LRUCache::Insert(const Slice &key, uint32_t hash, void *value, size_t charge, + void (*deleter)(const Slice &key, void *value)) { MutexLock l(&mutex_); - LRUHandle* e = reinterpret_cast( - malloc(sizeof(LRUHandle)-1 + key.size())); + LRUHandle *e = + reinterpret_cast(malloc(sizeof(LRUHandle) - 1 + key.size())); e->value = value; e->deleter = deleter; e->charge = charge; e->key_length = key.size(); e->hash = hash; e->in_cache = false; - e->refs = 1; // for the returned handle. + e->refs = 1; // for the returned handle. memcpy(e->key_data, key.data(), key.size()); if (capacity_ > 0) { - e->refs++; // for the cache's reference. + e->refs++; // for the cache's reference. e->in_cache = true; LRU_Append(&in_use_, e); usage_ += charge; FinishErase(table_.Insert(e)); - } else { // don't cache. (capacity_==0 is supported and turns off caching.) + } else { // don't cache. (capacity_==0 is supported and turns off caching.) // next is read by key() in an assert, so it must be initialized e->next = nullptr; } while (usage_ > capacity_ && lru_.next != &lru_) { - LRUHandle* old = lru_.next; + LRUHandle *old = lru_.next; assert(old->refs == 1); bool erased = FinishErase(table_.Remove(old->key(), old->hash)); - if (!erased) { // to avoid unused variable when compiled NDEBUG + if (!erased) { // to avoid unused variable when compiled NDEBUG assert(erased); } } - return reinterpret_cast(e); + return reinterpret_cast(e); } // If e != nullptr, finish removing *e from the cache; it has already been // removed from the hash table. Return whether e != nullptr. -bool LRUCache::FinishErase(LRUHandle* e) { +bool LRUCache::FinishErase(LRUHandle *e) { if (e != nullptr) { assert(e->in_cache); LRU_Remove(e); @@ -317,7 +344,7 @@ bool LRUCache::FinishErase(LRUHandle* e) { return e != nullptr; } -void LRUCache::Erase(const Slice& key, uint32_t hash) { +void LRUCache::Erase(const Slice &key, uint32_t hash) { MutexLock l(&mutex_); FinishErase(table_.Remove(key, hash)); } @@ -325,10 +352,10 @@ void LRUCache::Erase(const Slice& key, uint32_t hash) { void LRUCache::Prune() { MutexLock l(&mutex_); while (lru_.next != &lru_) { - LRUHandle* e = lru_.next; + LRUHandle *e = lru_.next; assert(e->refs == 1); bool erased = FinishErase(table_.Remove(e->key(), e->hash)); - if (!erased) { // to avoid unused variable when compiled NDEBUG + if (!erased) { // to avoid unused variable when compiled NDEBUG assert(erased); } } @@ -338,47 +365,44 @@ static const int kNumShardBits = 4; static const int kNumShards = 1 << kNumShardBits; class ShardedLRUCache : public Cache { - private: +private: LRUCache shard_[kNumShards]; port::Mutex id_mutex_; uint64_t last_id_; - static inline uint32_t HashSlice(const Slice& s) { + static inline uint32_t HashSlice(const Slice &s) { return Hash(s.data(), s.size(), 0); } - static uint32_t Shard(uint32_t hash) { - return hash >> (32 - kNumShardBits); - } + static uint32_t Shard(uint32_t hash) { return hash >> (32 - kNumShardBits); } - public: - explicit ShardedLRUCache(size_t capacity) - : last_id_(0) { +public: + explicit ShardedLRUCache(size_t capacity) : last_id_(0) { const size_t per_shard = (capacity + (kNumShards - 1)) / kNumShards; for (int s = 0; s < kNumShards; s++) { shard_[s].SetCapacity(per_shard); } } - virtual ~ShardedLRUCache() { } - virtual Handle* Insert(const Slice& key, void* value, size_t charge, - void (*deleter)(const Slice& key, void* value)) { + virtual ~ShardedLRUCache() {} + virtual Handle *Insert(const Slice &key, void *value, size_t charge, + void (*deleter)(const Slice &key, void *value)) { const uint32_t hash = HashSlice(key); return shard_[Shard(hash)].Insert(key, hash, value, charge, deleter); } - virtual Handle* Lookup(const Slice& key) { + virtual Handle *Lookup(const Slice &key) { const uint32_t hash = HashSlice(key); return shard_[Shard(hash)].Lookup(key, hash); } - virtual void Release(Handle* handle) { - LRUHandle* h = reinterpret_cast(handle); + virtual void Release(Handle *handle) { + LRUHandle *h = reinterpret_cast(handle); shard_[Shard(h->hash)].Release(handle); } - virtual void Erase(const Slice& key) { + virtual void Erase(const Slice &key) { const uint32_t hash = HashSlice(key); shard_[Shard(hash)].Erase(key, hash); } - virtual void* Value(Handle* handle) { - return reinterpret_cast(handle)->value; + virtual void *Value(Handle *handle) { + return reinterpret_cast(handle)->value; } virtual uint64_t NewId() { MutexLock l(&id_mutex_); @@ -398,10 +422,8 @@ class ShardedLRUCache : public Cache { } }; -} // end anonymous namespace +} // end anonymous namespace -Cache* NewLRUCache(size_t capacity) { - return new ShardedLRUCache(capacity); -} +Cache *NewLRUCache(size_t capacity) { return new ShardedLRUCache(capacity); } -} // namespace leveldb +} // namespace leveldb -- Gitee From 617b57e7cba072be0d7259f2d09119cae4eb135d Mon Sep 17 00:00:00 2001 From: hellolutar Date: Mon, 14 Oct 2024 23:20:41 +0800 Subject: [PATCH 04/22] chore: some demo code --- CMakeLists.txt | 2 +- demo/race/CMakeLists.txt | 3 ++- demo/race/{race_safe.cc => race_safe_var.cc} | 0 demo/race/race_safe_var2.cc | 25 ++++++++++++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) rename demo/race/{race_safe.cc => race_safe_var.cc} (100%) create mode 100644 demo/race/race_safe_var2.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index beec4f7..e180a6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ project(leveldb VERSION 1.21.0 LANGUAGES C CXX) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED OFF) set(CMAKE_C_EXTENSIONS OFF) -set(CMAKE_INSTALL_PREFIX "/home/lutar/code/c/leveldb/demo/lib") +set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/demo/lib") set(CMAKE_BUILD_TYPE "Debug") set(CMAKE_EXPORT_COMPILE_COMMANDS on) diff --git a/demo/race/CMakeLists.txt b/demo/race/CMakeLists.txt index 2ec8619..2562c1c 100644 --- a/demo/race/CMakeLists.txt +++ b/demo/race/CMakeLists.txt @@ -4,5 +4,6 @@ macro(compile_race name) endmacro(compile_race) compile_race(race) -compile_race(race_safe) +compile_race(race_safe_var) +compile_race(race_safe_var2) compile_race(w_thread_safe) diff --git a/demo/race/race_safe.cc b/demo/race/race_safe_var.cc similarity index 100% rename from demo/race/race_safe.cc rename to demo/race/race_safe_var.cc diff --git a/demo/race/race_safe_var2.cc b/demo/race/race_safe_var2.cc new file mode 100644 index 0000000..96c340b --- /dev/null +++ b/demo/race/race_safe_var2.cc @@ -0,0 +1,25 @@ +// #include "demo/mutex.h" +#include "mutex.h" +#include + +Mutex mu; +int *p1 GUARDED_BY(mu); +int *p2 PT_GUARDED_BY(mu); +std::unique_ptr p3 PT_GUARDED_BY(mu); + +void test() { + p1 = 0; // Warning! + *p1 = 1; // Warning! + + mu.Lock(); + *p1 = 2; // OK + mu.Unlock(); + + *p2 = 42; // Warning! + p2 = new int; // OK. + + *p3 = 42; // Warning! + p3.reset(new int); // OK. +} + +int main() { return 0; } -- Gitee From d0570b20b5369fc24dd991030832f48a6bffb19f Mon Sep 17 00:00:00 2001 From: hellolutar Date: Tue, 15 Oct 2024 11:19:51 +0800 Subject: [PATCH 05/22] chore: add some code about Thread Safety Analysis --- demo/CMakeLists.txt | 1 + demo/race/CMakeLists.txt | 5 +++- demo/race/w_thread_safe_method.cc | 29 +++++++++++++++++++ demo/race/w_thread_safe_method2.cc | 28 ++++++++++++++++++ demo/race/w_thread_safe_method3.cc | 16 ++++++++++ ...race_safe_var2.cc => w_thread_safe_var.cc} | 0 6 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 demo/race/w_thread_safe_method.cc create mode 100644 demo/race/w_thread_safe_method2.cc create mode 100644 demo/race/w_thread_safe_method3.cc rename demo/race/{race_safe_var2.cc => w_thread_safe_var.cc} (100%) diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index f4adf57..c8c92d6 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -6,6 +6,7 @@ set(CMAKE_BUILD_TYPE "Debug") set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS on) set(CMAKE_CXX_COMPILER "/usr/bin/clang++") set(CMAKE_CXX_FLAGS -Wthread-safety) diff --git a/demo/race/CMakeLists.txt b/demo/race/CMakeLists.txt index 2562c1c..f10590d 100644 --- a/demo/race/CMakeLists.txt +++ b/demo/race/CMakeLists.txt @@ -5,5 +5,8 @@ endmacro(compile_race) compile_race(race) compile_race(race_safe_var) -compile_race(race_safe_var2) compile_race(w_thread_safe) +compile_race(w_thread_safe_var) +compile_race(w_thread_safe_method) +compile_race(w_thread_safe_method2) +compile_race(w_thread_safe_method3) diff --git a/demo/race/w_thread_safe_method.cc b/demo/race/w_thread_safe_method.cc new file mode 100644 index 0000000..61db192 --- /dev/null +++ b/demo/race/w_thread_safe_method.cc @@ -0,0 +1,29 @@ +#include "mutex.h" +#include + +Mutex mu1, mu2; +int a GUARDED_BY(mu1); +int b GUARDED_BY(mu2); + +void foo() REQUIRES(mu1, mu2) { + a = 0; + b = 0; +} + +void foo2() REQUIRES_SHARED(mu1, mu2) { + printf("a:%d\nb:%d \n", a, b); // OK + a = 2; // Warning! requires holding mutex 'mu1' exclusively +} + +void test_after_hold_lock() { + mu1.Lock(); + foo(); // Warning! Requires mu2. + foo2(); // Warning! Requires mu2. + mu2.Lock(); + foo(); // OK + foo2(); // OK + mu1.Unlock(); + mu2.Unlock(); +} + +int main() { return 0; } \ No newline at end of file diff --git a/demo/race/w_thread_safe_method2.cc b/demo/race/w_thread_safe_method2.cc new file mode 100644 index 0000000..5dc70df --- /dev/null +++ b/demo/race/w_thread_safe_method2.cc @@ -0,0 +1,28 @@ +#include "mutex.h" + +class MyClass { +public: + void doSomething() {} + void init() {} + void cleanup() {} +}; + +Mutex mu; +MyClass myObject GUARDED_BY(mu); + +void lockAndInit() ACQUIRE(mu) { + mu.Lock(); + myObject.init(); +} + +void cleanupAndUnlock() RELEASE(mu) { + myObject.cleanup(); + mu.Unlock(); +} + +void test() { + lockAndInit(); + myObject.doSomething(); + cleanupAndUnlock(); + myObject.doSomething(); // Warning, requires holding mutex 'mu' +} diff --git a/demo/race/w_thread_safe_method3.cc b/demo/race/w_thread_safe_method3.cc new file mode 100644 index 0000000..6859d6c --- /dev/null +++ b/demo/race/w_thread_safe_method3.cc @@ -0,0 +1,16 @@ +#include "mutex.h" + +Mutex mu; +int a GUARDED_BY(mu); + +void clear() EXCLUDES(mu) { + mu.Lock(); + a = 0; + mu.Unlock(); +} + +void reset() { + mu.Lock(); + clear(); // Warning! Caller cannot hold 'mu'. + mu.Unlock(); +} \ No newline at end of file diff --git a/demo/race/race_safe_var2.cc b/demo/race/w_thread_safe_var.cc similarity index 100% rename from demo/race/race_safe_var2.cc rename to demo/race/w_thread_safe_var.cc -- Gitee From d649c90bd4bd31863c6b2e77e643a168e73cdacd Mon Sep 17 00:00:00 2001 From: hellolutar Date: Thu, 17 Oct 2024 17:26:30 +0800 Subject: [PATCH 06/22] chore: some comments --- db/db_impl.cc | 7 +-- db/version_set.cc | 15 +++++- doc/learn/db.md | 44 +++++++++++++++ doc/learn/images/datablock.jpeg | Bin 0 -> 51857 bytes doc/learn/images/entry_format.jpeg | Bin 0 -> 63605 bytes doc/learn/images/filterblock_format.jpeg | Bin 0 -> 135801 bytes doc/learn/images/footer_format.jpeg | Bin 0 -> 28169 bytes doc/learn/images/indexblock_format.jpeg | Bin 0 -> 93602 bytes doc/learn/images/sstable_logic.jpeg | Bin 0 -> 124193 bytes include/leveldb/filter_policy.h | 64 ++++++++++++++++++---- table/format.h | 66 +++++++++++------------ util/arena.cc | 1 - util/cache.cc | 23 +++++++- 13 files changed, 170 insertions(+), 50 deletions(-) create mode 100644 doc/learn/images/datablock.jpeg create mode 100644 doc/learn/images/entry_format.jpeg create mode 100644 doc/learn/images/filterblock_format.jpeg create mode 100644 doc/learn/images/footer_format.jpeg create mode 100644 doc/learn/images/indexblock_format.jpeg create mode 100644 doc/learn/images/sstable_logic.jpeg diff --git a/db/db_impl.cc b/db/db_impl.cc index 05daedd..d80b81b 100644 --- a/db/db_impl.cc +++ b/db/db_impl.cc @@ -502,6 +502,7 @@ Status DBImpl::WriteLevel0Table(MemTable *mem, VersionEdit *edit, Log(options_.info_log, "Level-0 table #%llu: started", (unsigned long long)meta.number); + // 将mem内容以迭代器遍历的方式写到sstable中 Status s; { mutex_.Unlock(); @@ -543,7 +544,7 @@ void DBImpl::CompactMemTable() { VersionEdit edit; Version *base = versions_->current(); base->Ref(); - Status s = WriteLevel0Table(imm_, &edit, base); + Status s = WriteLevel0Table(imm_, &edit, base); // 写sstable,信息设置到edit base->Unref(); if (s.ok() && shutting_down_.load(std::memory_order_acquire)) { @@ -553,8 +554,8 @@ void DBImpl::CompactMemTable() { // Replace immutable memtable with the generated Table if (s.ok()) { edit.SetPrevLogNumber(0); - edit.SetLogNumber(logfile_number_); // Earlier logs no longer needed - s = versions_->LogAndApply(&edit, &mutex_); + edit.SetLogNumber(logfile_number_); // Earlier logs no longer needed + s = versions_->LogAndApply(&edit, &mutex_); // 将versionedit写到log } if (s.ok()) { diff --git a/db/version_set.cc b/db/version_set.cc index 378a7e9..4459917 100644 --- a/db/version_set.cc +++ b/db/version_set.cc @@ -809,6 +809,7 @@ Status VersionSet::LogAndApply(VersionEdit *edit, port::Mutex *mu) { edit->SetNextFile(next_file_number_); edit->SetLastSequence(last_sequence_); + // 将versionset state设置到version中 Version *v = new Version(this); { Builder builder(this, current_); @@ -819,6 +820,7 @@ Status VersionSet::LogAndApply(VersionEdit *edit, port::Mutex *mu) { // Initialize new descriptor log file if necessary by creating // a temporary file that contains a snapshot of the current version. + // 如果需要,通过创建包含当前版本快照的临时文件来初始化新的描述符日志文件。 std::string new_manifest_file; Status s; if (descriptor_log_ == nullptr) { @@ -827,10 +829,11 @@ Status VersionSet::LogAndApply(VersionEdit *edit, port::Mutex *mu) { assert(descriptor_file_ == nullptr); new_manifest_file = DescriptorFileName(dbname_, manifest_file_number_); edit->SetNextFile(next_file_number_); - s = env_->NewWritableFile(new_manifest_file, &descriptor_file_); + s = env_->NewWritableFile(new_manifest_file, + &descriptor_file_); // 写manifest if (s.ok()) { descriptor_log_ = new log::Writer(descriptor_file_); - s = WriteSnapshot(descriptor_log_); + s = WriteSnapshot(descriptor_log_); // 写versionedit } } @@ -839,6 +842,7 @@ Status VersionSet::LogAndApply(VersionEdit *edit, port::Mutex *mu) { mu->Unlock(); // Write new record to MANIFEST log + // 写versionedit if (s.ok()) { std::string record; edit->EncodeTo(&record); @@ -889,6 +893,7 @@ Status VersionSet::Recover(bool *save_manifest) { }; // Read "CURRENT" file, which contains a pointer to the current manifest file + // 读取CURRENT std::string current; Status s = ReadFileToString(env_, CurrentFileName(dbname_), ¤t); if (!s.ok()) { @@ -899,6 +904,7 @@ Status VersionSet::Recover(bool *save_manifest) { } current.resize(current.size() - 1); + // 创建读log文件的对象 std::string dscname = dbname_ + "/" + current; SequentialFile *file; s = env_->NewSequentialFile(dscname, &file); @@ -921,6 +927,7 @@ Status VersionSet::Recover(bool *save_manifest) { Builder builder(this, current_); { + // 读取log LogReporter reporter; reporter.status = &s; log::Reader reader(file, &reporter, true /*checksum*/, @@ -928,6 +935,7 @@ Status VersionSet::Recover(bool *save_manifest) { Slice record; std::string scratch; while (reader.ReadRecord(&record, &scratch) && s.ok()) { + // 读取并解析为VersionEdit VersionEdit edit; s = edit.DecodeFrom(record); if (s.ok()) { @@ -939,10 +947,12 @@ Status VersionSet::Recover(bool *save_manifest) { } } + // 将VersionEdit应用到VersionSet if (s.ok()) { builder.Apply(&edit); } + // 根据VersionEdit信息,设置VersionSet信息 if (edit.has_log_number_) { log_number = edit.log_number_; have_log_number = true; @@ -984,6 +994,7 @@ Status VersionSet::Recover(bool *save_manifest) { MarkFileNumberUsed(log_number); } + // 将versionset 生成为version if (s.ok()) { Version *v = new Version(this); builder.SaveTo(v); diff --git a/doc/learn/db.md b/doc/learn/db.md index 824f248..919550c 100644 --- a/doc/learn/db.md +++ b/doc/learn/db.md @@ -350,6 +350,50 @@ entry格式 ## Table +### 格式 + +#### sstable + + + + + +#### data block + + + + + +#### data block entry + + + + + + + +#### filter block + + + + + +#### index block + + + + + +#### footer + + + + + + + + + ### Table::Iterator diff --git a/doc/learn/images/datablock.jpeg b/doc/learn/images/datablock.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2cd67a0384b242050232b49327775fc32fb5bc8d GIT binary patch literal 51857 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

a;XXeeBBcPs#}x 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 literal 0 HcmV?d00001 diff --git a/doc/learn/images/filterblock_format.jpeg b/doc/learn/images/filterblock_format.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..0274d2aa4a5bd2fa996d8f3cc314e4491276b611 GIT binary patch literal 135801 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

d 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�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 A<^TWy literal 0 HcmV?d00001 diff --git a/doc/learn/images/footer_format.jpeg b/doc/learn/images/footer_format.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d56ae8cd062eca172ed79a2af95787a9379c391c GIT binary patch literal 28169 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

8UUpb3`{B}3-KO!QPhmU|8 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 literal 0 HcmV?d00001 diff --git a/doc/learn/images/indexblock_format.jpeg b/doc/learn/images/indexblock_format.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..e356717931b492d25ab655e2f6a1520c71b22068 GIT binary patch literal 93602 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

g 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 T85bpR%iBHXME>8u`8EANW8uq8 literal 0 HcmV?d00001 diff --git a/include/leveldb/filter_policy.h b/include/leveldb/filter_policy.h index ba02720..61ac2f3 100644 --- a/include/leveldb/filter_policy.h +++ b/include/leveldb/filter_policy.h @@ -13,25 +13,41 @@ // Most people will want to use the builtin bloom filter support (see // NewBloomFilterPolicy() below). +/* + * 可以使用自定义FilterPolicy对象配置数据库。 + * 该对象负责从一组键创建一个小过滤器。 + * 这些过滤器存储在leveldb中,并由leveldb自动查询,以决定是否从磁盘读取某些信息。 + * 在许多情况下,过滤器可以将每个DB::Get()调用的磁盘查找次数从少量减少到单个磁盘查找。 + * + * 大多数人都希望使用内置的bloom过滤器支持(参见下面的NewBloomFilterPolicy())。 + */ + #ifndef STORAGE_LEVELDB_INCLUDE_FILTER_POLICY_H_ #define STORAGE_LEVELDB_INCLUDE_FILTER_POLICY_H_ -#include #include "leveldb/export.h" +#include namespace leveldb { class Slice; class LEVELDB_EXPORT FilterPolicy { - public: +public: virtual ~FilterPolicy(); // Return the name of this policy. Note that if the filter encoding // changes in an incompatible way, the name returned by this method // must be changed. Otherwise, old incompatible filters may be // passed to methods of this type. - virtual const char* Name() const = 0; + + /** + * @brief 返回此策略的名称。 + * + * 请注意,如果过滤器编码以不兼容的方式更改,则必须更改此方法返回的名称。 + * 否则,可能会将旧的不兼容过滤器传递给此类型的方法。 + */ + virtual const char *Name() const = 0; // keys[0,n-1] contains a list of keys (potentially with duplicates) // that are ordered according to the user supplied comparator. @@ -39,15 +55,29 @@ class LEVELDB_EXPORT FilterPolicy { // // Warning: do not change the initial contents of *dst. Instead, // append the newly constructed filter to *dst. - virtual void CreateFilter(const Slice* keys, int n, std::string* dst) - const = 0; + + /** + * @brief keys[0, + * n-1]包含一个键列表(可能有重复项),这些键是根据用户提供的比较器排序的。 + * 向*dst添加一个汇总键[0, n-1]的过滤器。 + * + * Warning:不要更改*dst的初始内容。相反,将新构造的过滤器附加到*dst。 + */ + virtual void CreateFilter(const Slice *keys, int n, + std::string *dst) const = 0; // "filter" contains the data appended by a preceding call to // CreateFilter() on this class. This method must return true if // the key was in the list of keys passed to CreateFilter(). // This method may return true or false if the key was not on the // list, but it should aim to return false with a high probability. - virtual bool KeyMayMatch(const Slice& key, const Slice& filter) const = 0; + + /** + * @brief “filter”包含之前调用该类上的CreateFilter()所附加的数据。 + * 如果该键在传递给CreateFilter()的键列表中,则此方法必须返回true。 + * 如果键不在列表中,此方法可能返回true或false,但它应该以高概率返回false为目标。 + */ + virtual bool KeyMayMatch(const Slice &key, const Slice &filter) const = 0; }; // Return a new filter policy that uses a bloom filter with approximately @@ -64,8 +94,24 @@ class LEVELDB_EXPORT FilterPolicy { // ignores trailing spaces, it would be incorrect to use a // FilterPolicy (like NewBloomFilterPolicy) that does not ignore // trailing spaces in keys. -LEVELDB_EXPORT const FilterPolicy* NewBloomFilterPolicy(int bits_per_key); -} // namespace leveldb +/** + * @brief + * 返回一个新的过滤器策略,该策略使用每个密钥大约具有指定位数的布隆过滤器。 + * + * bits_per_key的一个较好的值是10,它产生一个假阳性(false + * positive)率约为1%的过滤器。 + * + * 调用者必须在使用该结果的任何数据库关闭后删除该结果。 + * + * 注意:如果您使用的自定义比较器会忽略被比较的键的某些部分, + * 那么您一定不能使用NewBloomFilterPolicy(), + * 并且必须提供您自己的FilterPolicy,它也会忽略键的相应部分。 + * 例如,如果比较器忽略尾随空格, + * 那么使用不忽略键中的尾随空格的FilterPolicy(如NewBloomFilterPolicy)是不正确的。 + */ +LEVELDB_EXPORT const FilterPolicy *NewBloomFilterPolicy(int bits_per_key); + +} // namespace leveldb -#endif // STORAGE_LEVELDB_INCLUDE_FILTER_POLICY_H_ +#endif // STORAGE_LEVELDB_INCLUDE_FILTER_POLICY_H_ diff --git a/table/format.h b/table/format.h index 144ff55..bede0fa 100644 --- a/table/format.h +++ b/table/format.h @@ -5,11 +5,11 @@ #ifndef STORAGE_LEVELDB_TABLE_FORMAT_H_ #define STORAGE_LEVELDB_TABLE_FORMAT_H_ -#include -#include #include "leveldb/slice.h" #include "leveldb/status.h" #include "leveldb/table_builder.h" +#include +#include namespace leveldb { @@ -19,8 +19,12 @@ struct ReadOptions; // BlockHandle is a pointer to the extent of a file that stores a data // block or a meta block. + +/** + * @brief BlockHandle是一个指针,指向存储数据块或元块的文件的范围。 + */ class BlockHandle { - public: +public: BlockHandle(); // The offset of the block in the file. @@ -31,46 +35,44 @@ class BlockHandle { uint64_t size() const { return size_; } void set_size(uint64_t size) { size_ = size; } - void EncodeTo(std::string* dst) const; - Status DecodeFrom(Slice* input); + void EncodeTo(std::string *dst) const; + Status DecodeFrom(Slice *input); // Maximum encoding length of a BlockHandle enum { kMaxEncodedLength = 10 + 10 }; - private: +private: uint64_t offset_; uint64_t size_; }; // Footer encapsulates the fixed information stored at the tail // end of every table file. + +/** + * @brief 页脚封装了存储在每个表文件尾部的固定信息。 + */ class Footer { - public: - Footer() { } +public: + Footer() {} // The block handle for the metaindex block of the table - const BlockHandle& metaindex_handle() const { return metaindex_handle_; } - void set_metaindex_handle(const BlockHandle& h) { metaindex_handle_ = h; } + const BlockHandle &metaindex_handle() const { return metaindex_handle_; } + void set_metaindex_handle(const BlockHandle &h) { metaindex_handle_ = h; } // The block handle for the index block of the table - const BlockHandle& index_handle() const { - return index_handle_; - } - void set_index_handle(const BlockHandle& h) { - index_handle_ = h; - } + const BlockHandle &index_handle() const { return index_handle_; } + void set_index_handle(const BlockHandle &h) { index_handle_ = h; } - void EncodeTo(std::string* dst) const; - Status DecodeFrom(Slice* input); + void EncodeTo(std::string *dst) const; + Status DecodeFrom(Slice *input); // Encoded length of a Footer. Note that the serialization of a // Footer will always occupy exactly this many bytes. It consists // of two block handles and a magic number. - enum { - kEncodedLength = 2*BlockHandle::kMaxEncodedLength + 8 - }; + enum { kEncodedLength = 2 * BlockHandle::kMaxEncodedLength + 8 }; - private: +private: BlockHandle metaindex_handle_; BlockHandle index_handle_; }; @@ -84,25 +86,21 @@ static const uint64_t kTableMagicNumber = 0xdb4775248b80fb57ull; static const size_t kBlockTrailerSize = 5; struct BlockContents { - Slice data; // Actual contents of data - bool cachable; // True iff data can be cached - bool heap_allocated; // True iff caller should delete[] data.data() + Slice data; // Actual contents of data + bool cachable; // True iff data can be cached + bool heap_allocated; // True iff caller should delete[] data.data() }; // Read the block identified by "handle" from "file". On failure // return non-OK. On success fill *result and return OK. -Status ReadBlock(RandomAccessFile* file, - const ReadOptions& options, - const BlockHandle& handle, - BlockContents* result); +Status ReadBlock(RandomAccessFile *file, const ReadOptions &options, + const BlockHandle &handle, BlockContents *result); // Implementation details follow. Clients should ignore, inline BlockHandle::BlockHandle() - : offset_(~static_cast(0)), - size_(~static_cast(0)) { -} + : offset_(~static_cast(0)), size_(~static_cast(0)) {} -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_TABLE_FORMAT_H_ +#endif // STORAGE_LEVELDB_TABLE_FORMAT_H_ diff --git a/util/arena.cc b/util/arena.cc index a496ad0..0242ff2 100644 --- a/util/arena.cc +++ b/util/arena.cc @@ -55,7 +55,6 @@ char* Arena::AllocateAligned(size_t bytes) { assert((reinterpret_cast(result) & (align-1)) == 0); return result; } - char* Arena::AllocateNewBlock(size_t block_bytes) { char* result = new char[block_bytes]; blocks_.push_back(result); diff --git a/util/cache.cc b/util/cache.cc index a4b40c9..ee39d35 100644 --- a/util/cache.cc +++ b/util/cache.cc @@ -175,7 +175,7 @@ private: // A single shard of sharded cache. /** - * 分片缓存的单个分片。 + * @brief 分片缓存的单个分片。 */ class LRUCache { public: @@ -186,12 +186,28 @@ public: void SetCapacity(size_t capacity) { capacity_ = capacity; } // Like Cache methods, but with an extra "hash" parameter. + /** + * @brief 类似于Cache方法,但有一个额外的“hash”参数。 + * + * 当空间不够用时,循环针对lru.next调用FinishErase,直到有足够的空间可以使用 + */ Cache::Handle *Insert(const Slice &key, uint32_t hash, void *value, size_t charge, void (*deleter)(const Slice &key, void *value)); Cache::Handle *Lookup(const Slice &key, uint32_t hash); + + /** + * @brief 回收Insert、Lookup返回的对象 + */ void Release(Cache::Handle *handle); + + /** + * @brief 删除条目,当ref为0时才会真正删除 + */ void Erase(const Slice &key, uint32_t hash); + /** + * @brief 清理lru_中所有条目 + */ void Prune(); size_t TotalCharge() const { MutexLock l(&mutex_); @@ -333,6 +349,11 @@ LRUCache::Insert(const Slice &key, uint32_t hash, void *value, size_t charge, // If e != nullptr, finish removing *e from the cache; it has already been // removed from the hash table. Return whether e != nullptr. +/** + * @brief 如果e != nullptr,完成从缓存中删除*e; + * 它已经从哈希表中删除了。 + * 返回是否e != nullptr。 + */ bool LRUCache::FinishErase(LRUHandle *e) { if (e != nullptr) { assert(e->in_cache); -- Gitee From 22447c8a46b411bc616347e4642e57da905286a2 Mon Sep 17 00:00:00 2001 From: hellolutar Date: Fri, 18 Oct 2024 17:59:40 +0800 Subject: [PATCH 07/22] chore: add some comments --- db/table_cache.cc | 77 +++++++++++------------ db/version_edit.h | 6 +- db/version_set.cc | 69 ++++++++++++++++++++- db/version_set.h | 8 ++- doc/learn/compaction.md | 115 ++++++++++++++++++++++++++++++++++ include/leveldb/table.h | 4 +- table/table.cc | 2 + table/two_level_iterator.cc | 120 +++++++++++++++++++++--------------- table/two_level_iterator.h | 35 +++++++---- 9 files changed, 331 insertions(+), 105 deletions(-) diff --git a/db/table_cache.cc b/db/table_cache.cc index 7226d3b..5643398 100644 --- a/db/table_cache.cc +++ b/db/table_cache.cc @@ -12,49 +12,51 @@ namespace leveldb { struct TableAndFile { - RandomAccessFile* file; - Table* table; + RandomAccessFile *file; + Table *table; }; -static void DeleteEntry(const Slice& key, void* value) { - TableAndFile* tf = reinterpret_cast(value); +static void DeleteEntry(const Slice &key, void *value) { + TableAndFile *tf = reinterpret_cast(value); delete tf->table; delete tf->file; delete tf; } -static void UnrefEntry(void* arg1, void* arg2) { - Cache* cache = reinterpret_cast(arg1); - Cache::Handle* h = reinterpret_cast(arg2); +static void UnrefEntry(void *arg1, void *arg2) { + Cache *cache = reinterpret_cast(arg1); + Cache::Handle *h = reinterpret_cast(arg2); cache->Release(h); } -TableCache::TableCache(const std::string& dbname, - const Options& options, +TableCache::TableCache(const std::string &dbname, const Options &options, int entries) - : env_(options.env), - dbname_(dbname), - options_(options), - cache_(NewLRUCache(entries)) { -} + : env_(options.env), dbname_(dbname), options_(options), + cache_(NewLRUCache(entries)) {} -TableCache::~TableCache() { - delete cache_; -} +TableCache::~TableCache() { delete cache_; } +/** + * @brief + * 在cache中查找table,若没有table则加载Table,先尝试加载ldb,加载失败再尝试加载sst。 + * table加载成功后,放入cache: + * + */ Status TableCache::FindTable(uint64_t file_number, uint64_t file_size, - Cache::Handle** handle) { + Cache::Handle **handle) { Status s; char buf[sizeof(file_number)]; EncodeFixed64(buf, file_number); Slice key(buf, sizeof(buf)); *handle = cache_->Lookup(key); if (*handle == nullptr) { + // 访问ldb std::string fname = TableFileName(dbname_, file_number); - RandomAccessFile* file = nullptr; - Table* table = nullptr; + RandomAccessFile *file = nullptr; + Table *table = nullptr; s = env_->NewRandomAccessFile(fname, &file); if (!s.ok()) { + // 访问sst std::string old_fname = SSTTableFileName(dbname_, file_number); if (env_->NewRandomAccessFile(old_fname, &file).ok()) { s = Status::OK(); @@ -70,31 +72,33 @@ Status TableCache::FindTable(uint64_t file_number, uint64_t file_size, // We do not cache error results so that if the error is transient, // or somebody repairs the file, we recover automatically. } else { - TableAndFile* tf = new TableAndFile; + TableAndFile *tf = new TableAndFile; tf->file = file; tf->table = table; + // 将table放入cache *handle = cache_->Insert(key, tf, 1, &DeleteEntry); } } return s; } -Iterator* TableCache::NewIterator(const ReadOptions& options, - uint64_t file_number, - uint64_t file_size, - Table** tableptr) { +Iterator *TableCache::NewIterator(const ReadOptions &options, + uint64_t file_number, uint64_t file_size, + Table **tableptr) { if (tableptr != nullptr) { *tableptr = nullptr; } - Cache::Handle* handle = nullptr; + Cache::Handle *handle = nullptr; + // 从cache或磁盘上加载table(ldb,sst),并放入cache; 并得到handle Status s = FindTable(file_number, file_size, &handle); if (!s.ok()) { return NewErrorIterator(s); } - - Table* table = reinterpret_cast(cache_->Value(handle))->table; - Iterator* result = table->NewIterator(options); + // 通过handle拿到table + Table *table = reinterpret_cast(cache_->Value(handle))->table; + // + Iterator *result = table->NewIterator(options); result->RegisterCleanup(&UnrefEntry, cache_, handle); if (tableptr != nullptr) { *tableptr = table; @@ -102,16 +106,13 @@ Iterator* TableCache::NewIterator(const ReadOptions& options, return result; } -Status TableCache::Get(const ReadOptions& options, - uint64_t file_number, - uint64_t file_size, - const Slice& k, - void* arg, - void (*saver)(void*, const Slice&, const Slice&)) { - Cache::Handle* handle = nullptr; +Status TableCache::Get(const ReadOptions &options, uint64_t file_number, + uint64_t file_size, const Slice &k, void *arg, + void (*saver)(void *, const Slice &, const Slice &)) { + Cache::Handle *handle = nullptr; Status s = FindTable(file_number, file_size, &handle); if (s.ok()) { - Table* t = reinterpret_cast(cache_->Value(handle))->table; + Table *t = reinterpret_cast(cache_->Value(handle))->table; s = t->InternalGet(options, k, arg, saver); cache_->Release(handle); } @@ -124,4 +125,4 @@ void TableCache::Evict(uint64_t file_number) { cache_->Erase(Slice(buf, sizeof(buf))); } -} // namespace leveldb +} // namespace leveldb diff --git a/db/version_edit.h b/db/version_edit.h index 52e696b..a2abe60 100644 --- a/db/version_edit.h +++ b/db/version_edit.h @@ -108,9 +108,9 @@ private: bool has_next_file_number_; bool has_last_sequence_; - std::vector> compact_pointers_; - DeletedFileSet deleted_files_; - std::vector> new_files_; + std::vector> compact_pointers_; // + DeletedFileSet deleted_files_; // + std::vector> new_files_; // }; } // namespace leveldb diff --git a/db/version_set.cc b/db/version_set.cc index 4459917..72ad749 100644 --- a/db/version_set.cc +++ b/db/version_set.cc @@ -159,6 +159,14 @@ bool SomeFileOverlapsRange(const InternalKeyComparator &icmp, // is the largest key that occurs in the file, and value() is an // 16-byte value containing the file number and file size, both // encoded using EncodeFixed64. + +/** + * @brief 内部迭代器。 + * 对于给定的版本/级别对,生成有关该级别中文件的信息。 + * + * - 对于给定的条目,key()是文件中出现的最大键, + * - value()是一个16字节的值,包含文件号和文件大小,两者都使用EncodeFixed64编码。 + */ class Version::LevelFileNumIterator : public Iterator { public: LevelFileNumIterator(const InternalKeyComparator &icmp, @@ -166,6 +174,10 @@ public: : icmp_(icmp), flist_(flist), index_(flist->size()) { // Marks as invalid } virtual bool Valid() const { return index_ < flist_->size(); } + + /** + * @brief 定位到满足target<=flist_[i]的最靠前的文件 + */ virtual void Seek(const Slice &target) { index_ = FindFile(icmp_, *flist_, target); } @@ -213,6 +225,7 @@ static Iterator *GetFileIterator(void *arg, const ReadOptions &options, return NewErrorIterator( Status::Corruption("FileReader invoked with unexpected value")); } else { + // 这里调用的是TableCache,实际返回的是TwoLevelIterator return cache->NewIterator(options, DecodeFixed64(file_value.data()), DecodeFixed64(file_value.data() + 8)); } @@ -833,7 +846,7 @@ Status VersionSet::LogAndApply(VersionEdit *edit, port::Mutex *mu) { &descriptor_file_); // 写manifest if (s.ok()) { descriptor_log_ = new log::Writer(descriptor_file_); - s = WriteSnapshot(descriptor_log_); // 写versionedit + s = WriteSnapshot(descriptor_log_); // 写versionedit } } @@ -1074,6 +1087,14 @@ void VersionSet::Finalize(Version *v) { // file size is small (perhaps because of a small write-buffer // setting, or very high compression ratios, or lots of // overwrites/deletions). + + /* + 我们通过限定文件数而不是字节数来特殊地处理0级,原因有二 + (1) 对于较大的写缓冲区大小,最好不要做太多的0级压缩。 + (2) 0级的文件在每次读取时都会合并, + 因此我们希望在单个文件大小很小的情况下避免过多的文件(可能是因为写缓冲区设置很小, + 或者压缩比非常高,或者大量的覆盖/删除)。 + */ score = v->files_[level].size() / static_cast(config::kL0_CompactionTrigger); } else { @@ -1147,9 +1168,11 @@ uint64_t VersionSet::ApproximateOffsetOf(Version *v, const InternalKey &ikey) { for (size_t i = 0; i < files.size(); i++) { if (icmp_.Compare(files[i]->largest, ikey) <= 0) { // Entire file is before "ikey", so just add the file size + // 整个文件在“ikey”之前,所以只需添加文件大小 result += files[i]->file_size; } else if (icmp_.Compare(files[i]->smallest, ikey) > 0) { // Entire file is after "ikey", so ignore + // 整个文件在“ikey”之后,所以忽略 if (level > 0) { // Files other than level 0 are sorted by meta->smallest, so // no further files in this level will contain data for @@ -1159,6 +1182,7 @@ uint64_t VersionSet::ApproximateOffsetOf(Version *v, const InternalKey &ikey) { } else { // "ikey" falls in the range for this table. Add the // approximate offset of "ikey" within the table. + // “ikey”在这张表的范围内。在表中添加“ikey”的近似偏移量。 Table *tableptr; Iterator *iter = table_cache_->NewIterator( ReadOptions(), files[i]->number, files[i]->file_size, &tableptr); @@ -1210,6 +1234,10 @@ int64_t VersionSet::MaxNextLevelOverlappingBytes() { // Stores the minimal range that covers all entries in inputs in // *smallest, *largest. // REQUIRES: inputs is not empty +/** + * @brief 以*smallest, *largest的形式存储包含所有输入项的最小范围。 + * REQUIRES: inputs不为空 + */ void VersionSet::GetRange(const std::vector &inputs, InternalKey *smallest, InternalKey *largest) { assert(!inputs.empty()); @@ -1234,6 +1262,9 @@ void VersionSet::GetRange(const std::vector &inputs, // Stores the minimal range that covers all entries in inputs1 and inputs2 // in *smallest, *largest. // REQUIRES: inputs is not empty +/** + * @brief 最小的范围涵盖了所有条目存储在inputs1 inputs2 *最小,最大。 + */ void VersionSet::GetRange2(const std::vector &inputs1, const std::vector &inputs2, InternalKey *smallest, InternalKey *largest) { @@ -1322,12 +1353,14 @@ Compaction *VersionSet::PickCompaction() { c->input_version_->Ref(); // Files in level 0 may overlap each other, so pick up all overlapping ones + // level-0中的文件可能相互重叠,所以要选择所有重叠的文件 if (level == 0) { InternalKey smallest, largest; GetRange(c->inputs_[0], &smallest, &largest); // Note that the next call will discard the file we placed in // c->inputs_[0] earlier and replace it with an overlapping set // which will include the picked file. + // 请注意,下一次调用将丢弃我们之前放置在c->inputs_[0]中的文件,并将其替换为包含所选文件的重叠集合。 current_->GetOverlappingInputs(0, &smallest, &largest, &c->inputs_[0]); assert(!c->inputs_[0].empty()); } @@ -1339,6 +1372,9 @@ Compaction *VersionSet::PickCompaction() { // Finds the largest key in a vector of files. Returns true if files it not // empty. +/** + * @brief 查找文件vector中的最大键。如果文件不为空则返回true。 + */ bool FindLargestKey(const InternalKeyComparator &icmp, const std::vector &files, InternalKey *largest_key) { @@ -1357,6 +1393,10 @@ bool FindLargestKey(const InternalKeyComparator &icmp, // Finds minimum file b2=(l2, u2) in level file for which l2 > u1 and // user_key(l2) = user_key(u1) +/** + * @brief 在l2 > u1且user_key(l2) = user_key(u1)的级别文件中查找最小文件b2=(l2, + * u2) + */ FileMetaData * FindSmallestBoundaryFile(const InternalKeyComparator &icmp, const std::vector &level_files, @@ -1391,6 +1431,21 @@ FindSmallestBoundaryFile(const InternalKeyComparator &icmp, // parameters: // in level_files: List of files to search for boundary files. // in/out compaction_files: List of files to extend by adding boundary files. + +/** + * @brief 从|compaction_files|中提取最大的文件b1, + * 然后在|level_files|中搜索user_key(u1) = user_key(l2)的b2。 + * 如果它找到这样的文件b2(称为boundary file),它将其添加到|compaction_files|, + * 然后使用这个新的上界(upper bound)再次搜索。 + * + * 如果有两个块,b1=(l1, u1)和b2=(l2, u2)和user_key(u1) = user_key(l2), + * 并且如果我们压缩b1但不压缩b2,那么后续的get操作将产生错误的结果, + * 因为它将返回第i级b2的记录,而不是b1的记录,因为它逐级搜索与提供的用户键匹配的记录。 + * + * parameters: + * in level_files: List of files to search for boundary files. + * in/out compaction_files: List of files to extend by adding boundary files. + */ void AddBoundaryInputs(const InternalKeyComparator &icmp, const std::vector &level_files, std::vector *compaction_files) { @@ -1407,6 +1462,7 @@ void AddBoundaryInputs(const InternalKeyComparator &icmp, FindSmallestBoundaryFile(icmp, level_files, largest_key); // If a boundary file was found advance largest_key, otherwise we're done. + // 如果在largest_key之前找到了边界文件,否则就完成了。 if (smallest_boundary_file != NULL) { compaction_files->push_back(smallest_boundary_file); largest_key = smallest_boundary_file->largest; @@ -1427,11 +1483,13 @@ void VersionSet::SetupOtherInputs(Compaction *c) { &c->inputs_[1]); // Get entire range covered by compaction + // 把整个范围都压实 InternalKey all_start, all_limit; GetRange2(c->inputs_[0], c->inputs_[1], &all_start, &all_limit); // See if we can grow the number of inputs in "level" without // changing the number of "level+1" files we pick up. + // 看看我们是否可以在不改变我们拾取的“level+1”文件数量的情况下增加“level”中的输入数量。 if (!c->inputs_[1].empty()) { std::vector expanded0; current_->GetOverlappingInputs(level, &all_start, &all_limit, &expanded0); @@ -1464,6 +1522,8 @@ void VersionSet::SetupOtherInputs(Compaction *c) { // Compute the set of grandparent files that overlap this compaction // (parent == level+1; grandparent == level+2) + // 计算与此压缩重叠的grandparent文件集(parent == level+1; grandparent == + // level+2) if (level + 2 < config::kNumLevels) { current_->GetOverlappingInputs(level + 2, &all_start, &all_limit, &c->grandparents_); @@ -1473,6 +1533,8 @@ void VersionSet::SetupOtherInputs(Compaction *c) { // We update this immediately instead of waiting for the VersionEdit // to be applied so that if the compaction fails, we will try a different // key range next time. + // 更新我们将对该级别进行下一次压缩的位置。 + // 我们立即更新这个,而不是等待VersionEdit被应用,这样如果压缩失败,我们将在下次尝试不同的键范围。 compact_pointer_[level] = largest.Encode().ToString(); c->edit_.SetCompactPointer(level, largest); } @@ -1489,6 +1551,9 @@ Compaction *VersionSet::CompactRange(int level, const InternalKey *begin, // But we cannot do this for level-0 since level-0 files can overlap // and we must not pick one file and drop another older file if the // two files overlap. + // 避免一次压实太多,以免范围太大。 + // 但是我们不能对0级文件这样做,因为0级文件可以重叠, + // 我们不能选择一个文件并删除另一个旧文件,如果两个文件重叠。 if (level > 0) { const uint64_t limit = MaxFileSizeForLevel(options_, level); uint64_t total = 0; @@ -1530,6 +1595,8 @@ bool Compaction::IsTrivialMove() const { // Avoid a move if there is lots of overlapping grandparent data. // Otherwise, the move could create a parent file that will require // a very expensive merge later on. + // 如果有很多重叠的grandparent数据,避免移动。 + // 否则,移动可能会创建一个父文件,这将需要稍后进行非常昂贵的合并。 return (num_input_files(0) == 1 && num_input_files(1) == 0 && TotalFileSize(grandparents_) <= MaxGrandParentOverlapBytes(vset->options_)); diff --git a/db/version_set.h b/db/version_set.h index eafbca7..7a41642 100644 --- a/db/version_set.h +++ b/db/version_set.h @@ -50,6 +50,12 @@ class WritableFile; // Return the smallest index i such that files[i]->largest >= key. // Return files.size() if there is no such file. // REQUIRES: "files" contains a sorted list of non-overlapping files. + +/** + * @brief 返回最小的索引i,使得files[i]->largest >= key。 + * 如果没有这样的文件,返回files.size() + * REQUIRES: “files”包含一个有序的非重叠文件列表。 + */ int FindFile(const InternalKeyComparator &icmp, const std::vector &files, const Slice &key); @@ -517,7 +523,7 @@ private: // A Compaction encapsulates information about a compaction. /** -* @brief 压缩封装了关于压缩的信息。 + * @brief 压缩封装了关于压缩的信息。 */ class Compaction { public: diff --git a/doc/learn/compaction.md b/doc/learn/compaction.md index e10f5d5..2a84ef4 100644 --- a/doc/learn/compaction.md +++ b/doc/learn/compaction.md @@ -13,3 +13,118 @@ DoCompactionWork - TableCache.FindTable - NewMergingIterator + + + + +## VersionSet + +``` c++ +class VersionSet { +public: + Status LogAndApply(VersionEdit *edit, port::Mutex *mu) + EXCLUSIVE_LOCKS_REQUIRED(mu); + + Status Recover(bool *save_manifest); + + Version *current() const { return current_; } + + uint64_t ManifestFileNumber() const { return manifest_file_number_; } + + uint64_t NewFileNumber() { return next_file_number_++; } + + void ReuseFileNumber(uint64_t file_number) { + if (next_file_number_ == file_number + 1) { + next_file_number_ = file_number; + } + } + + int NumLevelFiles(int level) const; + + int64_t NumLevelBytes(int level) const; + + uint64_t LastSequence() const { return last_sequence_; } + + void SetLastSequence(uint64_t s) { + assert(s >= last_sequence_); + last_sequence_ = s; + } + + void MarkFileNumberUsed(uint64_t number); + + uint64_t LogNumber() const { return log_number_; } + + uint64_t PrevLogNumber() const { return prev_log_number_; } + + Compaction *PickCompaction(); + + Compaction *CompactRange(int level, const InternalKey *begin, + const InternalKey *end); + + int64_t MaxNextLevelOverlappingBytes(); + + Iterator *MakeInputIterator(Compaction *c); + + bool NeedsCompaction() const { + Version *v = current_; + return (v->compaction_score_ >= 1) || (v->file_to_compact_ != nullptr); + } + void AddLiveFiles(std::set *live); + + uint64_t ApproximateOffsetOf(Version *v, const InternalKey &key); + + const char *LevelSummary(LevelSummaryStorage *scratch) const; + +private: + bool ReuseManifest(const std::string &dscname, const std::string &dscbase); + + void Finalize(Version *v); + + void GetRange(const std::vector &inputs, + InternalKey *smallest, InternalKey *largest); + + void GetRange2(const std::vector &inputs1, + const std::vector &inputs2, + InternalKey *smallest, InternalKey *largest); + + void SetupOtherInputs(Compaction *c); + + Status WriteSnapshot(log::Writer *log); + + void AppendVersion(Version *v); + + Env *const env_; + const std::string dbname_; + const Options *const options_; + TableCache *const table_cache_; + const InternalKeyComparator icmp_; + uint64_t next_file_number_; + uint64_t manifest_file_number_; + uint64_t last_sequence_; + uint64_t log_number_; + // 0 或正在压缩的 memtable 的后备存储 + uint64_t prev_log_number_; // 0 or backing store for memtable being compacted + + // Opened lazily + WritableFile *descriptor_file_; + log::Writer *descriptor_log_; + Version dummy_versions_; // Head of circular doubly-linked list of versions. + Version *current_; // == dummy_versions_.prev_ + std::string compact_pointer_[config::kNumLevels]; +} +``` + + + +## VersionEdit + +- new_files_: 新增了哪些文件:level、number、size、smallest、largest +- deleted_files_: 删除了哪些文件:level、number +- **compact_pointer_:不清楚作用** +- **log_number_:不清楚作用** +- **prev_log_number_:不清楚作用** +- **next_file_number_:不清楚作用** +- **last_sequence_:不清楚作用** +- EncodeTo() 编码为字符串 +- DecodeFrom() 从字符串中解码 + diff --git a/include/leveldb/table.h b/include/leveldb/table.h index 58dbd5b..6e65d68 100644 --- a/include/leveldb/table.h +++ b/include/leveldb/table.h @@ -61,8 +61,10 @@ public: // call one of the Seek methods on the iterator before using it). /** - * @brief 返回表内容上的新迭代器。 + * @brief 返回表内容上的新迭代器。 * NewIterator()的结果最初是无效的(调用者在使用迭代器之前必须调用其中一个Seek方法)。 + * + * 迭代的内容是 */ Iterator *NewIterator(const ReadOptions &) const; diff --git a/table/table.cc b/table/table.cc index 8e737e1..35fef2d 100644 --- a/table/table.cc +++ b/table/table.cc @@ -164,6 +164,7 @@ Iterator* Table::BlockReader(void* arg, Block* block = nullptr; Cache::Handle* cache_handle = nullptr; + // 解析index_value(即index block),得到blockHanlde BlockHandle handle; Slice input = index_value; Status s = handle.DecodeFrom(&input); @@ -212,6 +213,7 @@ Iterator* Table::BlockReader(void* arg, return iter; } + Iterator* Table::NewIterator(const ReadOptions& options) const { return NewTwoLevelIterator( rep_->index_block->NewIterator(rep_->options.comparator), diff --git a/table/two_level_iterator.cc b/table/two_level_iterator.cc index 4e6f420..a94ef22 100644 --- a/table/two_level_iterator.cc +++ b/table/two_level_iterator.cc @@ -13,27 +13,22 @@ namespace leveldb { namespace { -typedef Iterator* (*BlockFunction)(void*, const ReadOptions&, const Slice&); +typedef Iterator *(*BlockFunction)(void *, const ReadOptions &, const Slice &); -class TwoLevelIterator: public Iterator { - public: - TwoLevelIterator( - Iterator* index_iter, - BlockFunction block_function, - void* arg, - const ReadOptions& options); +class TwoLevelIterator : public Iterator { +public: + TwoLevelIterator(Iterator *index_iter, BlockFunction block_function, + void *arg, const ReadOptions &options); virtual ~TwoLevelIterator(); - virtual void Seek(const Slice& target); + virtual void Seek(const Slice &target); virtual void SeekToFirst(); virtual void SeekToLast(); virtual void Next(); virtual void Prev(); - virtual bool Valid() const { - return data_iter_.Valid(); - } + virtual bool Valid() const { return data_iter_.Valid(); } virtual Slice key() const { assert(Valid()); return data_iter_.key(); @@ -53,59 +48,80 @@ class TwoLevelIterator: public Iterator { } } - private: - void SaveError(const Status& s) { - if (status_.ok() && !s.ok()) status_ = s; +private: + void SaveError(const Status &s) { + if (status_.ok() && !s.ok()) + status_ = s; } void SkipEmptyDataBlocksForward(); void SkipEmptyDataBlocksBackward(); - void SetDataIterator(Iterator* data_iter); + void SetDataIterator(Iterator *data_iter); void InitDataBlock(); + /** + * @brief 即 Table::BlockReader 或者 GetFileIterator + */ BlockFunction block_function_; - void* arg_; + void *arg_; const ReadOptions options_; Status status_; + /** + * @brief index block 迭代器 + * + * index block 格式: 指向data block + * - | Max key 1 | Offset | Length | + * - | Max key 2 | Offset | Length | + * - | Max key 3 | Offset | Length | + */ IteratorWrapper index_iter_; - IteratorWrapper data_iter_; // May be nullptr + + /** + * @brief data block迭代器。 May be nullptr + * + * data block 格式 + * - | entry1 | + * - | entry2 | + * - | ... | + * - | restart point1 | + * - | restart point2 | + * - | ... | + * - | restart point length | + */ + IteratorWrapper data_iter_; // If data_iter_ is non-null, then "data_block_handle_" holds the // "index_value" passed to block_function_ to create the data_iter_. std::string data_block_handle_; }; -TwoLevelIterator::TwoLevelIterator( - Iterator* index_iter, - BlockFunction block_function, - void* arg, - const ReadOptions& options) - : block_function_(block_function), - arg_(arg), - options_(options), - index_iter_(index_iter), - data_iter_(nullptr) { -} +TwoLevelIterator::TwoLevelIterator(Iterator *index_iter, + BlockFunction block_function, void *arg, + const ReadOptions &options) + : block_function_(block_function), arg_(arg), options_(options), + index_iter_(index_iter), data_iter_(nullptr) {} -TwoLevelIterator::~TwoLevelIterator() { -} +TwoLevelIterator::~TwoLevelIterator() {} -void TwoLevelIterator::Seek(const Slice& target) { +void TwoLevelIterator::Seek(const Slice &target) { index_iter_.Seek(target); InitDataBlock(); - if (data_iter_.iter() != nullptr) data_iter_.Seek(target); + if (data_iter_.iter() != nullptr) + data_iter_.Seek(target); SkipEmptyDataBlocksForward(); } void TwoLevelIterator::SeekToFirst() { index_iter_.SeekToFirst(); InitDataBlock(); - if (data_iter_.iter() != nullptr) data_iter_.SeekToFirst(); + if (data_iter_.iter() != nullptr) + data_iter_.SeekToFirst(); SkipEmptyDataBlocksForward(); } void TwoLevelIterator::SeekToLast() { index_iter_.SeekToLast(); InitDataBlock(); - if (data_iter_.iter() != nullptr) data_iter_.SeekToLast(); + if (data_iter_.iter() != nullptr) + data_iter_.SeekToLast(); SkipEmptyDataBlocksBackward(); } @@ -121,7 +137,6 @@ void TwoLevelIterator::Prev() { SkipEmptyDataBlocksBackward(); } - void TwoLevelIterator::SkipEmptyDataBlocksForward() { while (data_iter_.iter() == nullptr || !data_iter_.Valid()) { // Move to next block @@ -131,7 +146,8 @@ void TwoLevelIterator::SkipEmptyDataBlocksForward() { } index_iter_.Next(); InitDataBlock(); - if (data_iter_.iter() != nullptr) data_iter_.SeekToFirst(); + if (data_iter_.iter() != nullptr) + data_iter_.SeekToFirst(); } } @@ -144,12 +160,14 @@ void TwoLevelIterator::SkipEmptyDataBlocksBackward() { } index_iter_.Prev(); InitDataBlock(); - if (data_iter_.iter() != nullptr) data_iter_.SeekToLast(); + if (data_iter_.iter() != nullptr) + data_iter_.SeekToLast(); } } -void TwoLevelIterator::SetDataIterator(Iterator* data_iter) { - if (data_iter_.iter() != nullptr) SaveError(data_iter_.status()); +void TwoLevelIterator::SetDataIterator(Iterator *data_iter) { + if (data_iter_.iter() != nullptr) + SaveError(data_iter_.status()); data_iter_.Set(data_iter); } @@ -158,25 +176,29 @@ void TwoLevelIterator::InitDataBlock() { SetDataIterator(nullptr); } else { Slice handle = index_iter_.value(); - if (data_iter_.iter() != nullptr && handle.compare(data_block_handle_) == 0) { + if (data_iter_.iter() != nullptr && + handle.compare(data_block_handle_) == 0) { // data_iter_ is already constructed with this iterator, so // no need to change anything + // data_iter_已经用这个迭代器构造了,所以不需要改变任何东西 } else { - Iterator* iter = (*block_function_)(arg_, options_, handle); + /* + 得到table的block迭代器 @see Table::BlockReader + 或者得到tablecache 的cache迭代器 @see GetFileIterator + */ + Iterator *iter = (*block_function_)(arg_, options_, handle); data_block_handle_.assign(handle.data(), handle.size()); SetDataIterator(iter); } } } -} // namespace +} // namespace -Iterator* NewTwoLevelIterator( - Iterator* index_iter, - BlockFunction block_function, - void* arg, - const ReadOptions& options) { +Iterator *NewTwoLevelIterator(Iterator *index_iter, + BlockFunction block_function, void *arg, + const ReadOptions &options) { return new TwoLevelIterator(index_iter, block_function, arg, options); } -} // namespace leveldb +} // namespace leveldb diff --git a/table/two_level_iterator.h b/table/two_level_iterator.h index a93ba89..58136b6 100644 --- a/table/two_level_iterator.h +++ b/table/two_level_iterator.h @@ -20,15 +20,26 @@ struct ReadOptions; // // Uses a supplied function to convert an index_iter value into // an iterator over the contents of the corresponding block. -Iterator* NewTwoLevelIterator( - Iterator* index_iter, - Iterator* (*block_function)( - void* arg, - const ReadOptions& options, - const Slice& index_value), - void* arg, - const ReadOptions& options); - -} // namespace leveldb - -#endif // STORAGE_LEVELDB_TABLE_TWO_LEVEL_ITERATOR_H_ + +/** + * @brief 返回一个新的two level迭代器。 + * + * two-level迭代器包含一个index迭代器,其值指向一个blocks序列, + * 其中每个块本身是一个键值对序列。 + * + * 返回的two-level迭代器产生block序列中所有键/值对的连接。 + * + * NOTE: 获取“index_iter”的所有权,并在不再需要时将其删除。 + * + * @param index_iter 即index block 的迭代器 + * @param arg 即Table或TableCache + */ +Iterator *NewTwoLevelIterator( + Iterator *index_iter, + Iterator *(*block_function)(void *arg, const ReadOptions &options, + const Slice &index_value), + void *arg, const ReadOptions &options); + +} // namespace leveldb + +#endif // STORAGE_LEVELDB_TABLE_TWO_LEVEL_ITERATOR_H_ -- Gitee From 76c079da48166d5afe8afe7450dfa90440c3e32d Mon Sep 17 00:00:00 2001 From: hellolutar Date: Mon, 21 Oct 2024 18:04:25 +0800 Subject: [PATCH 08/22] chore: add some comments --- db/version_edit.h | 6 +++++ db/version_set.cc | 62 +++++++++++++++++++++++++++++++++++++---------- db/version_set.h | 16 +++++++++++- doc/learn/db.md | 47 +++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 14 deletions(-) diff --git a/db/version_edit.h b/db/version_edit.h index a2abe60..8c98e25 100644 --- a/db/version_edit.h +++ b/db/version_edit.h @@ -87,7 +87,13 @@ public: deleted_files_.insert(std::make_pair(level, file)); } + /** + * @brief 编码到dst + */ void EncodeTo(std::string *dst) const; + /** + * @brief 根据src解码 + */ Status DecodeFrom(const Slice &src); std::string DebugString() const; diff --git a/db/version_set.cc b/db/version_set.cc index 72ad749..b78bd45 100644 --- a/db/version_set.cc +++ b/db/version_set.cc @@ -37,6 +37,11 @@ static int64_t ExpandedCompactionByteSizeLimit(const Options *options) { return 25 * TargetFileSize(options); } +/** + * @brief 返回每个level的最大Bytes限制: 1048576 * 10^level + * + * 注意:level-0的结果并没有真正使用,因为我们根据文件数量设置了级别0的压缩阈值。 + */ static double MaxBytesForLevel(const Options *options, int level) { // Note: the result for level zero is not really used since we set // the level-0 compaction threshold based on number of files. @@ -163,9 +168,10 @@ bool SomeFileOverlapsRange(const InternalKeyComparator &icmp, /** * @brief 内部迭代器。 * 对于给定的版本/级别对,生成有关该级别中文件的信息。 - * - * - 对于给定的条目,key()是文件中出现的最大键, - * - value()是一个16字节的值,包含文件号和文件大小,两者都使用EncodeFixed64编码。 + * + * - 对于给定的条目,key()是文件中出现的最大键, + * - + * value()是一个16字节的值,包含文件号和文件大小,两者都使用EncodeFixed64编码。 */ class Version::LevelFileNumIterator : public Iterator { public: @@ -698,6 +704,19 @@ public: // same as the compaction of 40KB of data. We are a little // conservative and allow approximately one seek for every 16KB // of data before triggering a compaction. + + /* + 我们安排在经过一定次数的seeks后自动压缩该文件。我们假设: + (1) 一次seek花费10毫秒 + (2) 写入或读取1MB需要10ms (100MB/s) + (3) 1MB的压缩会产生25MB的IO: + 从这个level读取1MB + 从下一个level读取10-12MB(边界可能不对齐) + 10-12MB写入下一level + 这意味着25次seeks的成本与1MB数据的压缩成本相同。 + 也就是说,一次seek的开销与压缩40KB数据的开销大致相同。 + 我们有点保守,在触发压缩之前,大约允许对每16KB的数据进行一次seek。 + */ f->allowed_seeks = static_cast((f->file_size / 16384U)); if (f->allowed_seeks < 100) f->allowed_seeks = 100; @@ -708,31 +727,40 @@ public: } // Save the current state in *v. + /** + * @brief + */ void SaveTo(Version *v) { BySmallestKey cmp; cmp.internal_comparator = &vset_->icmp_; + // 遍历每层level,将每层level的文件保存到version中; + // 并且将versionset builder的base files添加到version中 for (int level = 0; level < config::kNumLevels; level++) { // Merge the set of added files with the set of pre-existing files. // Drop any deleted files. Store the result in *v. const std::vector &base_files = base_->files_[level]; - std::vector::const_iterator base_iter = - base_files.begin(); - std::vector::const_iterator base_end = base_files.end(); + auto base_iter = base_files.begin(); + auto base_end = base_files.end(); const FileSet *added = levels_[level].added_files; + // 指定vector容量 v->files_[level].reserve(base_files.size() + added->size()); + // 遍历当前level的added_files for (FileSet::const_iterator added_iter = added->begin(); added_iter != added->end(); ++added_iter) { // Add all smaller files listed in base_ + // 1. 遍历base_.files_ 如果level的added_file未在base_.files中,则添加 for (std::vector::const_iterator bpos = std::upper_bound(base_iter, base_end, *added_iter, cmp); base_iter != bpos; ++base_iter) { MaybeAddFile(v, level, *base_iter); } + // 2. 之前是添加了>added_iter的文件,现在添加added_iter的文件 MaybeAddFile(v, level, *added_iter); } // Add remaining base files + // 3. 现在添加base_files for (; base_iter != base_end; ++base_iter) { MaybeAddFile(v, level, *base_iter); } @@ -755,6 +783,10 @@ public: } } + /** + * @brief 将*f添加到*v中,前提条件是*f没有deleted,没有overlap + * + */ void MaybeAddFile(Version *v, int level, FileMetaData *f) { if (levels_[level].deleted_files.count(f->number) > 0) { // File is deleted: do nothing @@ -960,7 +992,7 @@ Status VersionSet::Recover(bool *save_manifest) { } } - // 将VersionEdit应用到VersionSet + // 将VersionEdit应用到VersionSet::Builder if (s.ok()) { builder.Apply(&edit); } @@ -1009,11 +1041,12 @@ Status VersionSet::Recover(bool *save_manifest) { // 将versionset 生成为version if (s.ok()) { + // 根据version set创建version对象 Version *v = new Version(this); - builder.SaveTo(v); + builder.SaveTo(v); // 主要是将新增的文件同步到version中 // Install recovered version - Finalize(v); - AppendVersion(v); + Finalize(v); // 计算compaction score and level + AppendVersion(v); // 将version添加到version set中 manifest_file_number_ = next_file; next_file_number_ = next_file + 1; last_sequence_ = last_sequence; @@ -1117,11 +1150,11 @@ void VersionSet::Finalize(Version *v) { Status VersionSet::WriteSnapshot(log::Writer *log) { // TODO: Break up into multiple records to reduce memory usage on recovery? - // Save metadata + // 1. Save metadata VersionEdit edit; edit.SetComparatorName(icmp_.user_comparator()->Name()); - // Save compaction pointers + // 2. Save compaction pointers for (int level = 0; level < config::kNumLevels; level++) { if (!compact_pointer_[level].empty()) { InternalKey key; @@ -1130,7 +1163,7 @@ Status VersionSet::WriteSnapshot(log::Writer *log) { } } - // Save files + // 3. Save files for (int level = 0; level < config::kNumLevels; level++) { const std::vector &files = current_->files_[level]; for (size_t i = 0; i < files.size(); i++) { @@ -1139,8 +1172,11 @@ Status VersionSet::WriteSnapshot(log::Writer *log) { } } + // 4. 编码 std::string record; edit.EncodeTo(&record); + + // 5. 持久化 return log->AddRecord(record); } diff --git a/db/version_set.h b/db/version_set.h index 7a41642..cfcbf08 100644 --- a/db/version_set.h +++ b/db/version_set.h @@ -274,11 +274,16 @@ public: // current version. Will release *mu while actually writing to the file. // REQUIRES: *mu is held on entry. // REQUIRES: no other thread concurrently calls LogAndApply() + /** * @brief - * 对当前版本应用*edit以形成一个新的描述符,该描述符既保存为持久状态,又作为新的当前版本安装。 + * 对current version应用*edit以形成一个新的descriptor, + * 该descriptor既保存为持久状态,又作为新的current version安装。 * 将在实际写入文件时释放*mu。 * + * 细节:会设置log_number, prev_log_number, next_file, last_sequence; + * 如果MANIFEST不存在,则创建。 将edit内容写入MANIFEST。 + * * 要求: *mu在输入时被保留。 * 要求: 没有其他线程并发调用LogAndApply() */ @@ -288,6 +293,10 @@ public: // Recover the last saved descriptor from persistent storage. /** * @brief 从持久存储恢复最后保存的描述符。 + * - 读取current指向的log + * - 解码得到version edit + * - 通过versionset builder得到version, + * - 将version添加到version set中 */ Status Recover(bool *save_manifest); @@ -478,6 +487,10 @@ private: // Save current contents to *log /** * @brief 将当前内容保存到log + * - user comparator name + * - compaction pointers + * - new files + * - 其他属性 */ Status WriteSnapshot(log::Writer *log); @@ -513,6 +526,7 @@ private: /* 每个级别的键,该级别的下一个压缩应该从该键开始。 一个空字符串或一个有效的InternalKey。 + */ std::string compact_pointer_[config::kNumLevels]; diff --git a/doc/learn/db.md b/doc/learn/db.md index 919550c..927e094 100644 --- a/doc/learn/db.md +++ b/doc/learn/db.md @@ -400,9 +400,56 @@ entry格式 ## Version +### LogAndApply() + +调用方 + +- `DBImpl::CompactMemTable()` +- `BackgroundCompaction()` +- `InstallCompactionResults()` +- `DB:Open()` + + + +### WriteSnapshot() + +- 保存比较器名字 +- 保存compaction pointers +- 保存当前状态(current)的的files +- 编码以上内容及其他属性字段 +- 持久化 + + + +## VersionSet + +### VersionSet::Builder + +#### Apply() + +从current读取指定log后,解析得到VersionEdit,然后调用此方法 + +- 更新versionset的`compact_pointer_` +- 更新versionset的`deleted_files` +- 更新versionset的`added_files_` + + + +#### SaveTo() + +将versionset::builder的base_files添加到version中 + +将versionset::builder的每个level的added_files添加到version中 + ## Cache + + + + + + -- Gitee From 20ab3f3b22dd8ea4e86426999eb33bc04886ef53 Mon Sep 17 00:00:00 2001 From: hellolutar Date: Tue, 22 Oct 2024 18:11:45 +0800 Subject: [PATCH 09/22] chore: add some comments --- db/version_set.cc | 35 ++++++++++++++++++++++++++++++++--- db/version_set.h | 8 +++++--- doc/learn/db.md | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/db/version_set.cc b/db/version_set.cc index b78bd45..36683da 100644 --- a/db/version_set.cc +++ b/db/version_set.cc @@ -108,6 +108,9 @@ int FindFile(const InternalKeyComparator &icmp, return right; } +/** + * @brief user_key 大于 f.largest, 即user_key较新 + */ static bool AfterFile(const Comparator *ucmp, const Slice *user_key, const FileMetaData *f) { // null user_key occurs before all keys and is therefore never after *f @@ -115,6 +118,9 @@ static bool AfterFile(const Comparator *ucmp, const Slice *user_key, ucmp->Compare(*user_key, f->largest.user_key()) > 0); } +/** + * @brief user_key小于f.smallest, 即user_key较旧 + */ static bool BeforeFile(const Comparator *ucmp, const Slice *user_key, const FileMetaData *f) { // null user_key occurs after all keys and is therefore never before *f @@ -132,6 +138,7 @@ bool SomeFileOverlapsRange(const InternalKeyComparator &icmp, // Need to check against all files for (size_t i = 0; i < files.size(); i++) { const FileMetaData *f = files[i]; + // user key 不在 file 的范围内 if (AfterFile(ucmp, smallest_user_key, f) || BeforeFile(ucmp, largest_user_key, f)) { // No overlap @@ -156,7 +163,10 @@ bool SomeFileOverlapsRange(const InternalKeyComparator &icmp, return false; } - return !BeforeFile(ucmp, largest_user_key, files[index]); + // 此时表明,smallest_user_key 在file 范围内 + + bool overlap = !BeforeFile(ucmp, largest_user_key, files[index]); + return overlap; } // An internal iterator. For a given version/level pair, yields @@ -357,6 +367,9 @@ Status Version::Get(const ReadOptions &options, const LookupKey &k, // We can search level-by-level since entries never hop across // levels. Therefore we are guaranteed that if we find data // in a smaller level, later levels are irrelevant. + /* + 我们可以逐level搜索,因为条目不会跨层跳跃。因此,我们可以保证,如果我们在较小的level中找到数据,则后面的level无关紧要。 + */ std::vector tmp; FileMetaData *tmp2; for (int level = 0; level < config::kNumLevels; level++) { @@ -369,6 +382,9 @@ Status Version::Get(const ReadOptions &options, const LookupKey &k, if (level == 0) { // Level-0 files may overlap each other. Find all files that // overlap user_key and process them in order from newest to oldest. + /* + level-0文件可能相互重叠。找到与user_key重叠的所有文件,并按从最新到最旧的顺序处理它们。 + */ tmp.reserve(num_files); for (uint32_t i = 0; i < num_files; i++) { FileMetaData *f = files[i]; @@ -386,12 +402,13 @@ Status Version::Get(const ReadOptions &options, const LookupKey &k, } else { // Binary search to find earliest index whose largest key >= ikey. uint32_t index = FindFile(vset_->icmp_, files_[level], ikey); - if (index >= num_files) { + if (index >= num_files) { //应该是没有找到的意思 files = nullptr; num_files = 0; } else { tmp2 = files[index]; if (ucmp->Compare(user_key, tmp2->smallest.user_key()) < 0) { + // key不在file范围内 // All of "tmp2" is past any data for user_key files = nullptr; num_files = 0; @@ -413,13 +430,17 @@ Status Version::Get(const ReadOptions &options, const LookupKey &k, last_file_read = f; last_file_read_level = level; + // 用于临时保存值的对象 Saver saver; saver.state = kNotFound; saver.ucmp = ucmp; saver.user_key = user_key; - saver.value = value; + saver.value = value; // 注意这里是指针,给saver赋值时,value即赋值 + + // 从cache中查找,找不到则从file中查找,找到了则调用SaveValue() s = vset_->table_cache_->Get(options, f->number, f->file_size, ikey, &saver, SaveValue); + if (!s.ok()) { return s; } @@ -473,18 +494,26 @@ bool Version::RecordReadSample(Slice internal_key) { state->stats.seek_file_level = level; } // We can stop iterating once we have a second match. + // 一旦有了第二个匹配,我们就可以停止迭代了。 return state->matches < 2; } }; State state; state.matches = 0; + // 设置state的值。 需要联系下面的UpdateStats()一起来看。 + // 即当出现至少两个匹配项时,表明我们需要跨文件合并。 + // UpdateStats更新compact_file, compact_level ForEachOverlapping(ikey.user_key, internal_key, &state, &State::Match); // Must have at least two matches since we want to merge across // files. But what if we have a single file that contains many // overwrites and deletions? Should we have another mechanism for // finding such files? + /* + 必须至少有两个匹配项,因为我们要跨文件合并。 + 但是,如果我们有一个包含许多覆盖和删除的文件呢?我们是否应该有另一种机制来查找此类文件? + */ if (state.matches >= 2) { // 1MB cost is about 1 seek (see comment in Builder::Apply). return UpdateStats(state.stats); diff --git a/db/version_set.h b/db/version_set.h index cfcbf08..fe27f7c 100644 --- a/db/version_set.h +++ b/db/version_set.h @@ -116,7 +116,8 @@ public: // REQUIRES: lock is held /** * @brief - * 将“stats”添加到当前状态。如果可能需要触发新的压缩,则返回true,否则返回false。 + * 将“stats”添加到当前状态(seek_file, + * seek_file_level)。如果可能需要触发新的压缩,则返回true,否则返回false。 * * 要求:锁不被持有 */ @@ -127,7 +128,8 @@ public: // bytes. Returns true if a new compaction may need to be triggered. // REQUIRES: lock is held /** - * @brief 记录在指定的internal key上读取的字节样本。 + * @brief 采样,以判断指定key是否需要压缩。 + * 记录在指定的internal key上读取的字节样本。 * * 大约每config::kReadBytesPeriod字节采集一次样本。如果可能需要触发新的压缩,则返回true。 * @@ -199,7 +201,7 @@ private: * @brief 对每个与user_key重叠的文件按从最新到最旧的顺序调用func(arg, level, * f)。 如果调用函数返回false,则不再调用。 * - * REQUIRES:internal_key == user_key的用户部分。 + * REQUIRES: internal_key == user_key的用户部分。 */ void ForEachOverlapping(Slice user_key, Slice internal_key, void *arg, bool (*func)(void *, int, FileMetaData *)); diff --git a/doc/learn/db.md b/doc/learn/db.md index 927e094..4f1c236 100644 --- a/doc/learn/db.md +++ b/doc/learn/db.md @@ -400,6 +400,14 @@ entry格式 ## Version +### Version::LevelFileNumIterator + +#### Seek + +调用`FindFile()`,找到target位置,设置`index_` + + + ### LogAndApply() 调用方 @@ -421,6 +429,38 @@ entry格式 + + +### Get() + +从0开始遍历level + +- 找key所在文件 + - level0:可能相互重叠,收集找到所有与user_key重叠的文件,按number大到小排序 + - 其他level:调用FindFile找到文件 +- 所有文件按大到小排序,然后遍历 + - 调用`versionset.tableCache.Get()`:尝试从cache中找key,没找到则从文件中找 + - 找到、已删除、上面步骤出错等情况,直接返回 + - 没找到则继续遍历 + + + +### RecordReadSample + +采样,以判断指定key是否需要压缩。 从level0开始向上统计key出现的次数。 当key出现两次及以上,表示需要进行压缩,将会调用Version.UpdateStats记录要压缩的file和level。 + + + +### SomeFileOverlapsRange + +指定smallest_user_key, largest_user_key 是否在file范围内。 + + + + + + + ## VersionSet ### VersionSet::Builder -- Gitee From ab6a648c6e05df47963d685e2b118b5caaa0d852 Mon Sep 17 00:00:00 2001 From: hellolutar Date: Wed, 23 Oct 2024 17:05:56 +0800 Subject: [PATCH 10/22] chore: add some comments --- db/version_set.cc | 28 +++++++++++++--- db/version_set.h | 20 +++++++++++- doc/learn/db.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 5 deletions(-) diff --git a/db/version_set.cc b/db/version_set.cc index 36683da..bd5e118 100644 --- a/db/version_set.cc +++ b/db/version_set.cc @@ -565,7 +565,6 @@ int Version::PickLevelForMemTableOutput(const Slice &smallest_user_key, return level; } -// Store in "*inputs" all files in "level" that overlap [begin,end] void Version::GetOverlappingInputs(int level, const InternalKey *begin, const InternalKey *end, std::vector *inputs) { @@ -589,10 +588,14 @@ void Version::GetOverlappingInputs(int level, const InternalKey *begin, } else if (end != nullptr && user_cmp->Compare(file_start, user_end) > 0) { // "f" is completely after specified range; skip it } else { + // f在指定的begin,end范围内 inputs->push_back(f); if (level == 0) { // Level-0 files may overlap each other. So check if the newly // added file has expanded the range. If so, restart search. + /* + level-0文件可能相互重叠。所以检查新添加的文件是否扩展了范围。如果是,重新开始搜索。 + */ if (begin != nullptr && user_cmp->Compare(file_start, user_begin) < 0) { user_begin = file_start; inputs->clear(); @@ -1285,6 +1288,7 @@ int64_t VersionSet::MaxNextLevelOverlappingBytes() { for (int level = 1; level < config::kNumLevels - 1; level++) { for (size_t i = 0; i < current_->files_[level].size(); i++) { const FileMetaData *f = current_->files_[level][i]; + // 统计上层level中重叠的文件,存储到overlaps中 current_->GetOverlappingInputs(level + 1, &f->smallest, &f->largest, &overlaps); const int64_t sum = TotalFileSize(overlaps); @@ -1360,7 +1364,7 @@ Iterator *VersionSet::MakeInputIterator(Compaction *c) { if (c->level() + which == 0) { // 合并所有level-0文件 const std::vector &files = c->inputs_[which]; - for (size_t i = 0; i < files.size(); i++) { // 遍历level0文件 + for (size_t i = 0; i < files.size(); i++) { // 遍历level0文件,读取文件内容 list[num++] = table_cache_->NewIterator(options, files[i]->number, files[i]->file_size); } @@ -1385,9 +1389,14 @@ Compaction *VersionSet::PickCompaction() { // We prefer compactions triggered by too much data in a level over // the compactions triggered by seeks. + /* + 我们更喜欢在一个级别中由过多数据触发的压缩,而不是由seeks触发的压缩。 + */ const bool size_compaction = (current_->compaction_score_ >= 1); const bool seek_compaction = (current_->file_to_compact_ != nullptr); - if (size_compaction) { + if (size_compaction) { //数据过多触发的压缩 + + // 收集要压缩的level的FileMetaData 作为c的inputs level = current_->compaction_level_; assert(level >= 0); assert(level + 1 < config::kNumLevels); @@ -1404,9 +1413,12 @@ Compaction *VersionSet::PickCompaction() { } if (c->inputs_[0].empty()) { // Wrap-around to the beginning of the key space + // 环绕到键空间的开头 c->inputs_[0].push_back(current_->files_[level][0]); } - } else if (seek_compaction) { + // 收集结束 + + } else if (seek_compaction) { // seek触发的压缩 level = current_->file_to_compact_level_; c = new Compaction(options_, level); c->inputs_[0].push_back(current_->file_to_compact_); @@ -1500,6 +1512,7 @@ FindSmallestBoundaryFile(const InternalKeyComparator &icmp, /** * @brief 从|compaction_files|中提取最大的文件b1, * 然后在|level_files|中搜索user_key(u1) = user_key(l2)的b2。 + * * 如果它找到这样的文件b2(称为boundary file),它将其添加到|compaction_files|, * 然后使用这个新的上界(upper bound)再次搜索。 * @@ -1662,6 +1675,13 @@ bool Compaction::IsTrivialMove() const { // a very expensive merge later on. // 如果有很多重叠的grandparent数据,避免移动。 // 否则,移动可能会创建一个父文件,这将需要稍后进行非常昂贵的合并。 + + /* + num_input_files(0) == 1 表示level的input数量为1 + num_input_files(1) == 0 表示level+1的input数量为0 + 所以以上语句的意思就是重叠文件有且只有一个在level中,level+1中没有 + */ + return (num_input_files(0) == 1 && num_input_files(1) == 0 && TotalFileSize(grandparents_) <= MaxGrandParentOverlapBytes(vset->options_)); diff --git a/db/version_set.h b/db/version_set.h index fe27f7c..35c9872 100644 --- a/db/version_set.h +++ b/db/version_set.h @@ -145,6 +145,17 @@ public: void Ref(); void Unref(); + // Store in "*inputs" all files in "level" that overlap [begin,end] + /** + * @brief + * 将level中所有满足[begin,end]范围的文件添加到inputs中。如果是level-0,则有可能会扩展begin,end的范围。 + * + * 举例:@see VersionSet::MaxNextLevelOverlappingBytes() + * 调用时传入level=level-2, begin=files[level-1][0].smallest, + * end=files[level-1][0].largest, + * 即可知道在level-2中有多少与level-1中指定的文件重复 + * + */ void GetOverlappingInputs( int level, const InternalKey *begin, // nullptr means before all keys @@ -419,7 +430,9 @@ public: // Create an iterator that reads over the compaction inputs for "*c". // The caller should delete the iterator when no longer needed. /** - * @brief 创建一个迭代器,读取“*c”的压缩输入。 + * @brief 创建一个迭代器,读取“*c”的compaction inputs。 + * 即创建一个迭代器,用以遍历compaction inputs的level和level+1。 + * * * @note 当不再需要迭代器时,调用者应该删除该迭代器。 */ @@ -601,6 +614,11 @@ public: /** * @brief 如果我们可用的信息保证压缩正在生成 “level+1” 中的数据, * 而对于这些数据,在大于 “level+1” 的level中不存在任何数据,则返回 true。 + * + * AI解释:用于确定一个给定的 key是否存在于数据库的基础级别(也就是第 0 级)。 + * LevelDB采用多级结构来组织其数据,基础级别是数据存储的最初级别,也是最近写入的数据存放地。 + * 具体来说,这个方法检查某个 key 是否在基础级别存在。如果 key + * 存在于这个级别,说明它还没有被压缩到更低的级别。这对于性能优化和查询处理非常重要,因为基础级别的数据是最新的,读取速度也最快。 */ bool IsBaseLevelForKey(const Slice &user_key); diff --git a/doc/learn/db.md b/doc/learn/db.md index 4f1c236..1c88f2a 100644 --- a/doc/learn/db.md +++ b/doc/learn/db.md @@ -451,6 +451,36 @@ entry格式 +### GetOverlappingInputs() + +将level中所有满足[begin,end]范围的文件添加到inputs中。如果时level-0,则有可能会扩展begin,end的范围。 + + + +如由MaxNextLevelOverlappingBytes()调用。 + +``` c++ +const FileMetaData *f = current_->files_[level][i]; + +// 在此拿到overlaps +current_->GetOverlappingInputs(level + 1, &f->smallest, &f->largest, + &overlaps); +``` + + + + + +### MakeInputIterator() + +由`DBImpl::DoCompactionwork()`调用 + +为Compaction指定的level和level+1创建迭代器,此迭代器可以迭代level和level+1. + + + + + ### SomeFileOverlapsRange 指定smallest_user_key, largest_user_key 是否在file范围内。 @@ -483,6 +513,57 @@ entry格式 +### LogAndApply() + + + + + + + +## Compaction + +封装压缩相关的信息。 将level和level+1合并为level+1。压缩后的结果为`VersionEdit`。 + +压缩的触发有两种情况 + +- 数据过多:用到了`VersionSet`的以下字段 + - `compaction_score_` + - `compaction_level_` +- seek:用到了`VersionSet`的以下字段 + - `file_to_compact_` + - `file_to_compact_level_` + + + + + +### `IsTrivialMove()` + +判断是否是一种简单的compaction。即只需将单个输入文件移动到下一个级别(不合并或分割)就可以实现。 + + + + + +### `AddInputDeletions()` + +将level和level+1重叠的文件都添加到`VersionEdit`的deleted_files中。 + + + + + +### `IsBaseLevelForKey()` + +是 LevelDB 中的一个方法,用于确定一个给定的 key 是否存在于数据库的基础级别(也就是第 0 级)。LevelDB 采用多级结构来组织其数据,基础级别是数据存储的最初级别,也是最近写入的数据存放地。 + +具体来说,这个方法检查某个 key 是否在基础级别存在。如果 key 存在于这个级别,说明它还没有被压缩到更低的级别。这对于性能优化和查询处理非常重要,因为基础级别的数据是最新的,读取速度也最快。 + + + + + ## Cache -- Gitee From 6c3fbffa806c7234e73c3b64cfff0b93449c4924 Mon Sep 17 00:00:00 2001 From: hellolutar Date: Fri, 25 Oct 2024 17:57:48 +0800 Subject: [PATCH 11/22] chore: add some comments --- db/dbformat.cc | 49 +++--- db/filename.cc | 46 +++--- db/filename.h | 75 ++++++--- db/version_set.cc | 32 ++-- db/version_set.h | 18 ++- db/version_set_test.cc | 350 +++++++++++++++++++++++----------------- demo/case/open_close.cc | 4 +- doc/learn/db.md | 86 ++++++++++ 8 files changed, 434 insertions(+), 226 deletions(-) diff --git a/db/dbformat.cc b/db/dbformat.cc index b82fe6a..c2188c7 100644 --- a/db/dbformat.cc +++ b/db/dbformat.cc @@ -2,10 +2,10 @@ // 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 #include "db/dbformat.h" #include "port/port.h" #include "util/coding.h" +#include namespace leveldb { @@ -15,15 +15,14 @@ static uint64_t PackSequenceAndType(uint64_t seq, ValueType t) { return (seq << 8) | t; } -void AppendInternalKey(std::string* result, const ParsedInternalKey& key) { +void AppendInternalKey(std::string *result, const ParsedInternalKey &key) { result->append(key.user_key.data(), key.user_key.size()); PutFixed64(result, PackSequenceAndType(key.sequence, key.type)); } std::string ParsedInternalKey::DebugString() const { char buf[50]; - snprintf(buf, sizeof(buf), "' @ %llu : %d", - (unsigned long long) sequence, + snprintf(buf, sizeof(buf), "' @ %llu : %d", (unsigned long long)sequence, int(type)); std::string result = "'"; result += EscapeString(user_key.ToString()); @@ -43,11 +42,14 @@ std::string InternalKey::DebugString() const { return result; } -const char* InternalKeyComparator::Name() const { +const char *InternalKeyComparator::Name() const { return "leveldb.InternalKeyComparator"; } -int InternalKeyComparator::Compare(const Slice& akey, const Slice& bkey) const { +/** + * @brief user key 升序,sequence number 降序,type 降序 + */ +int InternalKeyComparator::Compare(const Slice &akey, const Slice &bkey) const { // Order by: // increasing user key (according to user-supplied comparator) // decreasing sequence number @@ -65,9 +67,8 @@ int InternalKeyComparator::Compare(const Slice& akey, const Slice& bkey) const { return r; } -void InternalKeyComparator::FindShortestSeparator( - std::string* start, - const Slice& limit) const { +void InternalKeyComparator::FindShortestSeparator(std::string *start, + const Slice &limit) const { // Attempt to shorten the user portion of the key Slice user_start = ExtractUserKey(*start); Slice user_limit = ExtractUserKey(limit); @@ -77,14 +78,15 @@ void InternalKeyComparator::FindShortestSeparator( user_comparator_->Compare(user_start, tmp) < 0) { // User key has become shorter physically, but larger logically. // Tack on the earliest possible number to the shortened user key. - PutFixed64(&tmp, PackSequenceAndType(kMaxSequenceNumber,kValueTypeForSeek)); + PutFixed64(&tmp, + PackSequenceAndType(kMaxSequenceNumber, kValueTypeForSeek)); assert(this->Compare(*start, tmp) < 0); assert(this->Compare(tmp, limit) < 0); start->swap(tmp); } } -void InternalKeyComparator::FindShortSuccessor(std::string* key) const { +void InternalKeyComparator::FindShortSuccessor(std::string *key) const { Slice user_key = ExtractUserKey(*key); std::string tmp(user_key.data(), user_key.size()); user_comparator_->FindShortSuccessor(&tmp); @@ -92,21 +94,20 @@ void InternalKeyComparator::FindShortSuccessor(std::string* key) const { user_comparator_->Compare(user_key, tmp) < 0) { // User key has become shorter physically, but larger logically. // Tack on the earliest possible number to the shortened user key. - PutFixed64(&tmp, PackSequenceAndType(kMaxSequenceNumber,kValueTypeForSeek)); + PutFixed64(&tmp, + PackSequenceAndType(kMaxSequenceNumber, kValueTypeForSeek)); assert(this->Compare(*key, tmp) < 0); key->swap(tmp); } } -const char* InternalFilterPolicy::Name() const { - return user_policy_->Name(); -} +const char *InternalFilterPolicy::Name() const { return user_policy_->Name(); } -void InternalFilterPolicy::CreateFilter(const Slice* keys, int n, - std::string* dst) const { +void InternalFilterPolicy::CreateFilter(const Slice *keys, int n, + std::string *dst) const { // We rely on the fact that the code in table.cc does not mind us // adjusting keys[]. - Slice* mkey = const_cast(keys); + Slice *mkey = const_cast(keys); for (int i = 0; i < n; i++) { mkey[i] = ExtractUserKey(keys[i]); // TODO(sanjay): Suppress dups? @@ -114,20 +115,20 @@ void InternalFilterPolicy::CreateFilter(const Slice* keys, int n, user_policy_->CreateFilter(keys, n, dst); } -bool InternalFilterPolicy::KeyMayMatch(const Slice& key, const Slice& f) const { +bool InternalFilterPolicy::KeyMayMatch(const Slice &key, const Slice &f) const { return user_policy_->KeyMayMatch(ExtractUserKey(key), f); } -LookupKey::LookupKey(const Slice& user_key, SequenceNumber s) { +LookupKey::LookupKey(const Slice &user_key, SequenceNumber s) { size_t usize = user_key.size(); - size_t needed = usize + 13; // A conservative estimate - char* dst; + size_t needed = usize + 13; // A conservative estimate + char *dst; if (needed <= sizeof(space_)) { dst = space_; } else { dst = new char[needed]; } - start_ = dst; // size开始的位置 + start_ = dst; // size开始的位置 // 编码entry大小 dst = EncodeVarint32(dst, usize + 8); kstart_ = dst; // user key 开始的位置 @@ -140,4 +141,4 @@ LookupKey::LookupKey(const Slice& user_key, SequenceNumber s) { end_ = dst; } -} // namespace leveldb +} // namespace leveldb diff --git a/db/filename.cc b/db/filename.cc index 6539bbe..c0908c7 100644 --- a/db/filename.cc +++ b/db/filename.cc @@ -2,44 +2,44 @@ // 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 -#include #include "db/filename.h" #include "db/dbformat.h" #include "leveldb/env.h" #include "util/logging.h" +#include +#include namespace leveldb { // A utility routine: write "data" to the named file and Sync() it. -Status WriteStringToFileSync(Env* env, const Slice& data, - const std::string& fname); +Status WriteStringToFileSync(Env *env, const Slice &data, + const std::string &fname); -static std::string MakeFileName(const std::string& dbname, uint64_t number, - const char* suffix) { +static std::string MakeFileName(const std::string &dbname, uint64_t number, + const char *suffix) { char buf[100]; snprintf(buf, sizeof(buf), "/%06llu.%s", - static_cast(number), - suffix); + static_cast(number), suffix); return dbname + buf; } -std::string LogFileName(const std::string& dbname, uint64_t number) { +std::string LogFileName(const std::string &dbname, uint64_t number) { assert(number > 0); return MakeFileName(dbname, number, "log"); } -std::string TableFileName(const std::string& dbname, uint64_t number) { +std::string TableFileName(const std::string &dbname, uint64_t number) { assert(number > 0); return MakeFileName(dbname, number, "ldb"); } -std::string SSTTableFileName(const std::string& dbname, uint64_t number) { +std::string SSTTableFileName(const std::string &dbname, uint64_t number) { assert(number > 0); return MakeFileName(dbname, number, "sst"); } -std::string DescriptorFileName(const std::string& dbname, uint64_t number) { + +std::string DescriptorFileName(const std::string &dbname, uint64_t number) { assert(number > 0); char buf[100]; snprintf(buf, sizeof(buf), "/MANIFEST-%06llu", @@ -47,29 +47,26 @@ std::string DescriptorFileName(const std::string& dbname, uint64_t number) { return dbname + buf; } -std::string CurrentFileName(const std::string& dbname) { +std::string CurrentFileName(const std::string &dbname) { return dbname + "/CURRENT"; } -std::string LockFileName(const std::string& dbname) { - return dbname + "/LOCK"; -} +std::string LockFileName(const std::string &dbname) { return dbname + "/LOCK"; } -std::string TempFileName(const std::string& dbname, uint64_t number) { +std::string TempFileName(const std::string &dbname, uint64_t number) { assert(number > 0); return MakeFileName(dbname, number, "dbtmp"); } -std::string InfoLogFileName(const std::string& dbname) { +std::string InfoLogFileName(const std::string &dbname) { return dbname + "/LOG"; } // Return the name of the old info log file for "dbname". -std::string OldInfoLogFileName(const std::string& dbname) { +std::string OldInfoLogFileName(const std::string &dbname) { return dbname + "/LOG.old"; } - // Owned filenames have the form: // dbname/CURRENT // dbname/LOCK @@ -77,9 +74,8 @@ std::string OldInfoLogFileName(const std::string& dbname) { // dbname/LOG.old // dbname/MANIFEST-[0-9]+ // dbname/[0-9]+.(log|sst|ldb) -bool ParseFileName(const std::string& filename, - uint64_t* number, - FileType* type) { +bool ParseFileName(const std::string &filename, uint64_t *number, + FileType *type) { Slice rest(filename); if (rest == "CURRENT") { *number = 0; @@ -123,7 +119,7 @@ bool ParseFileName(const std::string& filename, return true; } -Status SetCurrentFile(Env* env, const std::string& dbname, +Status SetCurrentFile(Env *env, const std::string &dbname, uint64_t descriptor_number) { // Remove leading "dbname/" and add newline to manifest file name std::string manifest = DescriptorFileName(dbname, descriptor_number); @@ -141,4 +137,4 @@ Status SetCurrentFile(Env* env, const std::string& dbname, return s; } -} // namespace leveldb +} // namespace leveldb diff --git a/db/filename.h b/db/filename.h index 62cb3ef..8143ef0 100644 --- a/db/filename.h +++ b/db/filename.h @@ -7,11 +7,11 @@ #ifndef STORAGE_LEVELDB_DB_FILENAME_H_ #define STORAGE_LEVELDB_DB_FILENAME_H_ -#include -#include #include "leveldb/slice.h" #include "leveldb/status.h" #include "port/port.h" +#include +#include namespace leveldb { @@ -24,60 +24,99 @@ enum FileType { kDescriptorFile, kCurrentFile, kTempFile, - kInfoLogFile // Either the current one, or an old one + kInfoLogFile // Either the current one, or an old one }; // Return the name of the log file with the specified number // in the db named by "dbname". The result will be prefixed with // "dbname". -std::string LogFileName(const std::string& dbname, uint64_t number); + +/** + * @brief 返回log文件名:${dbname}/${number}.log + */ +std::string LogFileName(const std::string &dbname, uint64_t number); // Return the name of the sstable with the specified number // in the db named by "dbname". The result will be prefixed with // "dbname". -std::string TableFileName(const std::string& dbname, uint64_t number); + +/** + * @brief 返回sstable名: ${dbname}/${number}.ldb + */ +std::string TableFileName(const std::string &dbname, uint64_t number); // Return the legacy file name for an sstable with the specified number // in the db named by "dbname". The result will be prefixed with // "dbname". -std::string SSTTableFileName(const std::string& dbname, uint64_t number); + +/** + * @brief 返回sstable名: ${dbname}/${number}.sst + * + * NOTE: 这是旧的文件名 + */ +std::string SSTTableFileName(const std::string &dbname, uint64_t number); // Return the name of the descriptor file for the db named by // "dbname" and the specified incarnation number. The result will be // prefixed with "dbname". -std::string DescriptorFileName(const std::string& dbname, uint64_t number); + +/** + * @brief 返回 ${dbname}/MANIFEST-${number} + */ +std::string DescriptorFileName(const std::string &dbname, uint64_t number); // Return the name of the current file. This file contains the name // of the current manifest file. The result will be prefixed with // "dbname". -std::string CurrentFileName(const std::string& dbname); + +/** + * @brief 返回 ${dbname}/CURRENT + * + * 这个文件包含当前manifest的文件名。 + */ +std::string CurrentFileName(const std::string &dbname); // Return the name of the lock file for the db named by // "dbname". The result will be prefixed with "dbname". -std::string LockFileName(const std::string& dbname); + +/** + *@brief 返回lock文件名:${dbname}/LOCK + */ +std::string LockFileName(const std::string &dbname); // Return the name of a temporary file owned by the db named "dbname". // The result will be prefixed with "dbname". -std::string TempFileName(const std::string& dbname, uint64_t number); + +/** + *@brief 返回临时文件名:${dbname}/${number}.dbtmp + */ +std::string TempFileName(const std::string &dbname, uint64_t number); // Return the name of the info log file for "dbname". -std::string InfoLogFileName(const std::string& dbname); + +/** + * @brief 返回info日志文件名:${dbname}/LOG + */ +std::string InfoLogFileName(const std::string &dbname); // Return the name of the old info log file for "dbname". -std::string OldInfoLogFileName(const std::string& dbname); + +/** + * @brief 返回旧info日志文件名${dbname}/LOG.old + */ +std::string OldInfoLogFileName(const std::string &dbname); // If filename is a leveldb file, store the type of the file in *type. // The number encoded in the filename is stored in *number. If the // filename was successfully parsed, returns true. Else return false. -bool ParseFileName(const std::string& filename, - uint64_t* number, - FileType* type); +bool ParseFileName(const std::string &filename, uint64_t *number, + FileType *type); // Make the CURRENT file point to the descriptor file with the // specified number. -Status SetCurrentFile(Env* env, const std::string& dbname, +Status SetCurrentFile(Env *env, const std::string &dbname, uint64_t descriptor_number); -} // namespace leveldb +} // namespace leveldb -#endif // STORAGE_LEVELDB_DB_FILENAME_H_ +#endif // STORAGE_LEVELDB_DB_FILENAME_H_ diff --git a/db/version_set.cc b/db/version_set.cc index bd5e118..ec891d7 100644 --- a/db/version_set.cc +++ b/db/version_set.cc @@ -981,7 +981,7 @@ Status VersionSet::Recover(bool *save_manifest) { } current.resize(current.size() - 1); - // 创建读log文件的对象 + // manifest 文件名 std::string dscname = dbname_ + "/" + current; SequentialFile *file; s = env_->NewSequentialFile(dscname, &file); @@ -1004,11 +1004,12 @@ Status VersionSet::Recover(bool *save_manifest) { Builder builder(this, current_); { - // 读取log LogReporter reporter; reporter.status = &s; log::Reader reader(file, &reporter, true /*checksum*/, 0 /*initial_offset*/); + + // 读取manifest内容,即version edit内容 Slice record; std::string scratch; while (reader.ReadRecord(&record, &scratch) && s.ok()) { @@ -1029,7 +1030,8 @@ Status VersionSet::Recover(bool *save_manifest) { builder.Apply(&edit); } - // 根据VersionEdit信息,设置VersionSet信息 + // 读取verionedit状态,并保存到临时变量中, + // 在代码的后面,将这些状态变量同时应用到version set的状态 if (edit.has_log_number_) { log_number = edit.log_number_; have_log_number = true; @@ -1071,7 +1073,8 @@ Status VersionSet::Recover(bool *save_manifest) { MarkFileNumberUsed(log_number); } - // 将versionset 生成为version + // 将versionset::builder 生成为version,并将version添加到versionset链表中 + // 将之前读取出来的version edit状态同步到当前状态(version set的状态) if (s.ok()) { // 根据version set创建version对象 Version *v = new Version(this); @@ -1218,6 +1221,11 @@ int VersionSet::NumLevelFiles(int level) const { return current_->files_[level].size(); } +/** + * @brief 返回各level的文件数量,格式如下 + * + * files [ level-0 level-1 level-2 level-3 level-4 level-5 level-6 ] + */ const char *VersionSet::LevelSummary(LevelSummaryStorage *scratch) const { // Update code if kNumLevels changes assert(config::kNumLevels == 7); @@ -1364,7 +1372,8 @@ Iterator *VersionSet::MakeInputIterator(Compaction *c) { if (c->level() + which == 0) { // 合并所有level-0文件 const std::vector &files = c->inputs_[which]; - for (size_t i = 0; i < files.size(); i++) { // 遍历level0文件,读取文件内容 + for (size_t i = 0; i < files.size(); + i++) { // 遍历level0文件,读取文件内容 list[num++] = table_cache_->NewIterator(options, files[i]->number, files[i]->file_size); } @@ -1470,9 +1479,9 @@ bool FindLargestKey(const InternalKeyComparator &icmp, // Finds minimum file b2=(l2, u2) in level file for which l2 > u1 and // user_key(l2) = user_key(u1) + /** - * @brief 在l2 > u1且user_key(l2) = user_key(u1)的级别文件中查找最小文件b2=(l2, - * u2) + * @brief 找min(f.user_key) then max(f.largest.seqno) */ FileMetaData * FindSmallestBoundaryFile(const InternalKeyComparator &icmp, @@ -1485,6 +1494,9 @@ FindSmallestBoundaryFile(const InternalKeyComparator &icmp, if (icmp.Compare(f->smallest, largest_key) > 0 && user_cmp->Compare(f->smallest.user_key(), largest_key.user_key()) == 0) { + // 从低level遍历到高level + // smallest_boundary_file.user_key <= f->smallest.user_key + // && f->smallest.seq > smallest_boundary_file.seq if (smallest_boundary_file == nullptr || icmp.Compare(f->smallest, smallest_boundary_file->smallest) < 0) { smallest_boundary_file = f; @@ -1520,9 +1532,8 @@ FindSmallestBoundaryFile(const InternalKeyComparator &icmp, * 并且如果我们压缩b1但不压缩b2,那么后续的get操作将产生错误的结果, * 因为它将返回第i级b2的记录,而不是b1的记录,因为它逐级搜索与提供的用户键匹配的记录。 * - * parameters: - * in level_files: List of files to search for boundary files. - * in/out compaction_files: List of files to extend by adding boundary files. + * @param level_files 同属一个level的files + * */ void AddBoundaryInputs(const InternalKeyComparator &icmp, const std::vector &level_files, @@ -1534,6 +1545,7 @@ void AddBoundaryInputs(const InternalKeyComparator &icmp, return; } + // largest key 以前的旧版本file放入compaction_files bool continue_searching = true; while (continue_searching) { FileMetaData *smallest_boundary_file = diff --git a/db/version_set.h b/db/version_set.h index 35c9872..fb25eda 100644 --- a/db/version_set.h +++ b/db/version_set.h @@ -290,9 +290,9 @@ public: /** * @brief - * 对current version应用*edit以形成一个新的descriptor, - * 该descriptor既保存为持久状态,又作为新的current version安装。 - * 将在实际写入文件时释放*mu。 + * 由VersionSet::Builder根据edit和current version + * 生成一个新的version,将此version添加到versionset链表中。 + * 并持久化edit。 更新manifest内容。 * * 细节:会设置log_number, prev_log_number, next_file, last_sequence; * 如果MANIFEST不存在,则创建。 将edit内容写入MANIFEST。 @@ -458,6 +458,14 @@ public: // "key" as of version "v". /** * @brief 返回版本“v”的“key”数据在数据库中的近似偏移量。 + * + * 遍历v的每个level中的files。 + * - f.largest < key, 则表明整个文件在key之前,即部分偏移量 + * - f.smallest > key, 则表明整个文件在key之后,略过 + * - 读取f,seek(key),拿到偏移量 + * + * 以上循环,即可得到key在数据库中的完整的偏移量 + * */ uint64_t ApproximateOffsetOf(Version *v, const InternalKey &key); @@ -522,6 +530,10 @@ private: TableCache *const table_cache_; const InternalKeyComparator icmp_; uint64_t next_file_number_; + /** + * @brief 指向current manifest文件名的数字, + * 即 ${dbname}/MANIFEST-${number}中的number + */ uint64_t manifest_file_number_; uint64_t last_sequence_; uint64_t log_number_; diff --git a/db/version_set_test.cc b/db/version_set_test.cc index b32e2e5..35b05c4 100644 --- a/db/version_set_test.cc +++ b/db/version_set_test.cc @@ -10,11 +10,11 @@ namespace leveldb { class FindFileTest { - public: - std::vector files_; +public: + std::vector files_; bool disjoint_sorted_files_; - FindFileTest() : disjoint_sorted_files_(true) { } + FindFileTest() : disjoint_sorted_files_(true) {} ~FindFileTest() { for (int i = 0; i < files_.size(); i++) { @@ -22,23 +22,23 @@ class FindFileTest { } } - void Add(const char* smallest, const char* largest, + void Add(const char *smallest, const char *largest, SequenceNumber smallest_seq = 100, SequenceNumber largest_seq = 100) { - FileMetaData* f = new FileMetaData; + FileMetaData *f = new FileMetaData; f->number = files_.size() + 1; f->smallest = InternalKey(smallest, smallest_seq, kTypeValue); f->largest = InternalKey(largest, largest_seq, kTypeValue); files_.push_back(f); } - int Find(const char* key) { + int Find(const char *key) { InternalKey target(key, 100, kTypeValue); InternalKeyComparator cmp(BytewiseComparator()); return FindFile(cmp, files_, target.Encode()); } - bool Overlaps(const char* smallest, const char* largest) { + bool Overlaps(const char *smallest, const char *largest) { InternalKeyComparator cmp(BytewiseComparator()); Slice s(smallest != nullptr ? smallest : ""); Slice l(largest != nullptr ? largest : ""); @@ -50,10 +50,10 @@ class FindFileTest { TEST(FindFileTest, Empty) { ASSERT_EQ(0, Find("foo")); - ASSERT_TRUE(! Overlaps("a", "z")); - ASSERT_TRUE(! Overlaps(nullptr, "z")); - ASSERT_TRUE(! Overlaps("a", nullptr)); - ASSERT_TRUE(! Overlaps(nullptr, nullptr)); + ASSERT_TRUE(!Overlaps("a", "z")); + ASSERT_TRUE(!Overlaps(nullptr, "z")); + ASSERT_TRUE(!Overlaps("a", nullptr)); + ASSERT_TRUE(!Overlaps(nullptr, nullptr)); } TEST(FindFileTest, Single) { @@ -65,8 +65,8 @@ TEST(FindFileTest, Single) { ASSERT_EQ(1, Find("q1")); ASSERT_EQ(1, Find("z")); - ASSERT_TRUE(! Overlaps("a", "b")); - ASSERT_TRUE(! Overlaps("z1", "z2")); + ASSERT_TRUE(!Overlaps("a", "b")); + ASSERT_TRUE(!Overlaps("z1", "z2")); ASSERT_TRUE(Overlaps("a", "p")); ASSERT_TRUE(Overlaps("a", "q")); ASSERT_TRUE(Overlaps("a", "z")); @@ -78,15 +78,14 @@ TEST(FindFileTest, Single) { ASSERT_TRUE(Overlaps("q", "q")); ASSERT_TRUE(Overlaps("q", "q1")); - ASSERT_TRUE(! Overlaps(nullptr, "j")); - ASSERT_TRUE(! Overlaps("r", nullptr)); + ASSERT_TRUE(!Overlaps(nullptr, "j")); + ASSERT_TRUE(!Overlaps("r", nullptr)); ASSERT_TRUE(Overlaps(nullptr, "p")); ASSERT_TRUE(Overlaps(nullptr, "p1")); ASSERT_TRUE(Overlaps("q", nullptr)); ASSERT_TRUE(Overlaps(nullptr, nullptr)); } - TEST(FindFileTest, Multiple) { Add("150", "200"); Add("200", "250"); @@ -110,10 +109,10 @@ TEST(FindFileTest, Multiple) { ASSERT_EQ(3, Find("450")); ASSERT_EQ(4, Find("451")); - ASSERT_TRUE(! Overlaps("100", "149")); - ASSERT_TRUE(! Overlaps("251", "299")); - ASSERT_TRUE(! Overlaps("451", "500")); - ASSERT_TRUE(! Overlaps("351", "399")); + ASSERT_TRUE(!Overlaps("100", "149")); + ASSERT_TRUE(!Overlaps("251", "299")); + ASSERT_TRUE(!Overlaps("451", "500")); + ASSERT_TRUE(!Overlaps("351", "399")); ASSERT_TRUE(Overlaps("100", "150")); ASSERT_TRUE(Overlaps("100", "200")); @@ -130,8 +129,8 @@ TEST(FindFileTest, MultipleNullBoundaries) { Add("200", "250"); Add("300", "350"); Add("400", "450"); - ASSERT_TRUE(! Overlaps(nullptr, "149")); - ASSERT_TRUE(! Overlaps("451", nullptr)); + ASSERT_TRUE(!Overlaps(nullptr, "149")); + ASSERT_TRUE(!Overlaps("451", nullptr)); ASSERT_TRUE(Overlaps(nullptr, nullptr)); ASSERT_TRUE(Overlaps(nullptr, "150")); ASSERT_TRUE(Overlaps(nullptr, "199")); @@ -147,8 +146,8 @@ TEST(FindFileTest, MultipleNullBoundaries) { TEST(FindFileTest, OverlapSequenceChecks) { Add("200", "200", 5000, 3000); - ASSERT_TRUE(! Overlaps("199", "199")); - ASSERT_TRUE(! Overlaps("201", "300")); + ASSERT_TRUE(!Overlaps("199", "199")); + ASSERT_TRUE(!Overlaps("201", "300")); ASSERT_TRUE(Overlaps("200", "200")); ASSERT_TRUE(Overlaps("190", "200")); ASSERT_TRUE(Overlaps("200", "210")); @@ -158,8 +157,8 @@ TEST(FindFileTest, OverlappingFiles) { Add("150", "600"); Add("400", "500"); disjoint_sorted_files_ = false; - ASSERT_TRUE(! Overlaps("100", "149")); - ASSERT_TRUE(! Overlaps("601", "700")); + ASSERT_TRUE(!Overlaps("100", "149")); + ASSERT_TRUE(!Overlaps("601", "700")); ASSERT_TRUE(Overlaps("100", "150")); ASSERT_TRUE(Overlaps("100", "200")); ASSERT_TRUE(Overlaps("100", "300")); @@ -172,15 +171,15 @@ TEST(FindFileTest, OverlappingFiles) { ASSERT_TRUE(Overlaps("600", "700")); } -void AddBoundaryInputs(const InternalKeyComparator& icmp, - const std::vector& level_files, - std::vector* compaction_files); +void AddBoundaryInputs(const InternalKeyComparator &icmp, + const std::vector &level_files, + std::vector *compaction_files); class AddBoundaryInputsTest { - public: - std::vector level_files_; - std::vector compaction_files_; - std::vector all_files_; +public: + std::vector level_files_; + std::vector compaction_files_; + std::vector all_files_; InternalKeyComparator icmp_; AddBoundaryInputsTest() : icmp_(BytewiseComparator()){}; @@ -192,9 +191,9 @@ class AddBoundaryInputsTest { all_files_.clear(); }; - FileMetaData* CreateFileMetaData(uint64_t number, InternalKey smallest, + FileMetaData *CreateFileMetaData(uint64_t number, InternalKey smallest, InternalKey largest) { - FileMetaData* f = new FileMetaData(); + FileMetaData *f = new FileMetaData(); f->number = number; f->smallest = smallest; f->largest = largest; @@ -203,129 +202,192 @@ class AddBoundaryInputsTest { } }; -TEST(AddBoundaryInputsTest, TestEmptyFileSets) { - AddBoundaryInputs(icmp_, level_files_, &compaction_files_); - ASSERT_TRUE(compaction_files_.empty()); - ASSERT_TRUE(level_files_.empty()); -} - -TEST(AddBoundaryInputsTest, TestEmptyLevelFiles) { - FileMetaData* f1 = - CreateFileMetaData(1, InternalKey("100", 2, kTypeValue), - InternalKey(InternalKey("100", 1, kTypeValue))); - compaction_files_.push_back(f1); - - AddBoundaryInputs(icmp_, level_files_, &compaction_files_); - ASSERT_EQ(1, compaction_files_.size()); - ASSERT_EQ(f1, compaction_files_[0]); - ASSERT_TRUE(level_files_.empty()); -} - -TEST(AddBoundaryInputsTest, TestEmptyCompactionFiles) { - FileMetaData* f1 = - CreateFileMetaData(1, InternalKey("100", 2, kTypeValue), - InternalKey(InternalKey("100", 1, kTypeValue))); - level_files_.push_back(f1); - - AddBoundaryInputs(icmp_, level_files_, &compaction_files_); - ASSERT_TRUE(compaction_files_.empty()); - ASSERT_EQ(1, level_files_.size()); - ASSERT_EQ(f1, level_files_[0]); -} - -TEST(AddBoundaryInputsTest, TestNoBoundaryFiles) { - FileMetaData* f1 = - CreateFileMetaData(1, InternalKey("100", 2, kTypeValue), - InternalKey(InternalKey("100", 1, kTypeValue))); - FileMetaData* f2 = - CreateFileMetaData(1, InternalKey("200", 2, kTypeValue), - InternalKey(InternalKey("200", 1, kTypeValue))); - FileMetaData* f3 = - CreateFileMetaData(1, InternalKey("300", 2, kTypeValue), - InternalKey(InternalKey("300", 1, kTypeValue))); - +// TEST(AddBoundaryInputsTest, TestEmptyFileSets) { +// AddBoundaryInputs(icmp_, level_files_, &compaction_files_); +// ASSERT_TRUE(compaction_files_.empty()); +// ASSERT_TRUE(level_files_.empty()); +// } + +// TEST(AddBoundaryInputsTest, TestEmptyLevelFiles) { +// FileMetaData *f1 = +// CreateFileMetaData(1, InternalKey("100", 2, kTypeValue), +// InternalKey(InternalKey("100", 1, kTypeValue))); +// compaction_files_.push_back(f1); + +// AddBoundaryInputs(icmp_, level_files_, &compaction_files_); +// ASSERT_EQ(1, compaction_files_.size()); +// ASSERT_EQ(f1, compaction_files_[0]); +// ASSERT_TRUE(level_files_.empty()); +// } + +// TEST(AddBoundaryInputsTest, TestEmptyCompactionFiles) { +// FileMetaData *f1 = +// CreateFileMetaData(1, InternalKey("100", 2, kTypeValue), +// InternalKey(InternalKey("100", 1, kTypeValue))); +// level_files_.push_back(f1); + +// AddBoundaryInputs(icmp_, level_files_, &compaction_files_); +// ASSERT_TRUE(compaction_files_.empty()); +// ASSERT_EQ(1, level_files_.size()); +// ASSERT_EQ(f1, level_files_[0]); +// } + +// TEST(AddBoundaryInputsTest, TestNoBoundaryFiles) { +// FileMetaData *f1 = +// CreateFileMetaData(1, InternalKey("100", 2, kTypeValue), +// InternalKey(InternalKey("100", 1, kTypeValue))); +// FileMetaData *f2 = +// CreateFileMetaData(1, InternalKey("200", 2, kTypeValue), +// InternalKey(InternalKey("200", 1, kTypeValue))); +// FileMetaData *f3 = +// CreateFileMetaData(1, InternalKey("300", 2, kTypeValue), +// InternalKey(InternalKey("300", 1, kTypeValue))); + +// level_files_.push_back(f3); +// level_files_.push_back(f2); +// level_files_.push_back(f1); +// compaction_files_.push_back(f2); +// compaction_files_.push_back(f3); + +// AddBoundaryInputs(icmp_, level_files_, &compaction_files_); +// ASSERT_EQ(2, compaction_files_.size()); +// } + +// TEST(AddBoundaryInputsTest, TestOneBoundaryFiles) { +// FileMetaData *f1 = +// CreateFileMetaData(1, InternalKey("100", 3, kTypeValue), +// InternalKey(InternalKey("100", 2, kTypeValue))); +// FileMetaData *f2 = +// CreateFileMetaData(1, InternalKey("100", 1, kTypeValue), +// InternalKey(InternalKey("200", 3, kTypeValue))); +// FileMetaData *f3 = +// CreateFileMetaData(1, InternalKey("300", 2, kTypeValue), +// InternalKey(InternalKey("300", 1, kTypeValue))); + +// level_files_.push_back(f3); +// level_files_.push_back(f2); +// level_files_.push_back(f1); +// compaction_files_.push_back(f1); + +// AddBoundaryInputs(icmp_, level_files_, &compaction_files_); +// ASSERT_EQ(2, compaction_files_.size()); +// ASSERT_EQ(f1, compaction_files_[0]); +// ASSERT_EQ(f2, compaction_files_[1]); +// } + +// TEST(AddBoundaryInputsTest, TestTwoBoundaryFiles) { +// FileMetaData *f1 = +// CreateFileMetaData(1, InternalKey("100", 6, kTypeValue), +// InternalKey(InternalKey("100", 5, kTypeValue))); +// FileMetaData *f2 = +// CreateFileMetaData(1, InternalKey("100", 2, kTypeValue), +// InternalKey(InternalKey("300", 1, kTypeValue))); +// FileMetaData *f3 = +// CreateFileMetaData(1, InternalKey("100", 4, kTypeValue), +// InternalKey(InternalKey("100", 3, kTypeValue))); + +// level_files_.push_back(f2); +// level_files_.push_back(f3); +// level_files_.push_back(f1); +// compaction_files_.push_back(f1); + +// AddBoundaryInputs(icmp_, level_files_, &compaction_files_); +// ASSERT_EQ(3, compaction_files_.size()); +// ASSERT_EQ(f1, compaction_files_[0]); +// ASSERT_EQ(f3, compaction_files_[1]); +// ASSERT_EQ(f2, compaction_files_[2]); +// } + +// TEST(AddBoundaryInputsTest, TestDisjoinFilePointers) { +// FileMetaData *f1 = +// CreateFileMetaData(1, InternalKey("100", 6, kTypeValue), +// InternalKey(InternalKey("100", 5, kTypeValue))); +// FileMetaData *f2 = +// CreateFileMetaData(1, InternalKey("100", 6, kTypeValue), +// InternalKey(InternalKey("100", 5, kTypeValue))); +// FileMetaData *f3 = +// CreateFileMetaData(1, InternalKey("100", 2, kTypeValue), +// InternalKey(InternalKey("300", 1, kTypeValue))); +// FileMetaData *f4 = +// CreateFileMetaData(1, InternalKey("100", 4, kTypeValue), +// InternalKey(InternalKey("100", 3, kTypeValue))); + +// level_files_.push_back(f2); +// level_files_.push_back(f3); +// level_files_.push_back(f4); + +// compaction_files_.push_back(f1); + +// AddBoundaryInputs(icmp_, level_files_, &compaction_files_); +// ASSERT_EQ(3, compaction_files_.size()); +// ASSERT_EQ(f1, compaction_files_[0]); +// ASSERT_EQ(f4, compaction_files_[1]); +// ASSERT_EQ(f3, compaction_files_[2]); +// } + +FileMetaData * +FindSmallestBoundaryFile(const InternalKeyComparator &icmp, + const std::vector &level_files, + const InternalKey &largest_key); + +TEST(AddBoundaryInputsTest, TestFindSmallestBoundaryFile) { + FileMetaData *f5 = + CreateFileMetaData(1, InternalKey("5", 5, kTypeValue), + InternalKey(InternalKey("10", 5, kTypeValue))); + FileMetaData *f4 = + CreateFileMetaData(1, InternalKey("5", 4, kTypeValue), + InternalKey(InternalKey("10", 4, kTypeValue))); + FileMetaData *f3 = + CreateFileMetaData(1, InternalKey("5", 3, kTypeValue), + InternalKey(InternalKey("10", 3, kTypeValue))); + FileMetaData *f2 = + CreateFileMetaData(1, InternalKey("5", 2, kTypeValue), + InternalKey(InternalKey("10", 2, kTypeValue))); + FileMetaData *f1 = + CreateFileMetaData(1, InternalKey("5", 1, kTypeValue), + InternalKey(InternalKey("10", 1, kTypeValue))); + + level_files_.push_back(f5); + level_files_.push_back(f4); level_files_.push_back(f3); level_files_.push_back(f2); level_files_.push_back(f1); - compaction_files_.push_back(f2); - compaction_files_.push_back(f3); - AddBoundaryInputs(icmp_, level_files_, &compaction_files_); - ASSERT_EQ(2, compaction_files_.size()); -} - -TEST(AddBoundaryInputsTest, TestOneBoundaryFiles) { - FileMetaData* f1 = - CreateFileMetaData(1, InternalKey("100", 3, kTypeValue), - InternalKey(InternalKey("100", 2, kTypeValue))); - FileMetaData* f2 = - CreateFileMetaData(1, InternalKey("100", 1, kTypeValue), - InternalKey(InternalKey("200", 3, kTypeValue))); - FileMetaData* f3 = - CreateFileMetaData(1, InternalKey("300", 2, kTypeValue), - InternalKey(InternalKey("300", 1, kTypeValue))); - - level_files_.push_back(f3); - level_files_.push_back(f2); - level_files_.push_back(f1); - compaction_files_.push_back(f1); + auto f = FindSmallestBoundaryFile(icmp_, level_files_, + InternalKey("5", 6, kTypeValue)); - AddBoundaryInputs(icmp_, level_files_, &compaction_files_); - ASSERT_EQ(2, compaction_files_.size()); - ASSERT_EQ(f1, compaction_files_[0]); - ASSERT_EQ(f2, compaction_files_[1]); + ASSERT_EQ(f5, f); } -TEST(AddBoundaryInputsTest, TestTwoBoundaryFiles) { - FileMetaData* f1 = - CreateFileMetaData(1, InternalKey("100", 6, kTypeValue), - InternalKey(InternalKey("100", 5, kTypeValue))); - FileMetaData* f2 = - CreateFileMetaData(1, InternalKey("100", 2, kTypeValue), - InternalKey(InternalKey("300", 1, kTypeValue))); - FileMetaData* f3 = - CreateFileMetaData(1, InternalKey("100", 4, kTypeValue), - InternalKey(InternalKey("100", 3, kTypeValue))); +TEST(AddBoundaryInputsTest, TestFindSmallestBoundaryFile2) { + FileMetaData *f5 = + CreateFileMetaData(1, InternalKey("5", 5, kTypeValue), + InternalKey(InternalKey("10", 5, kTypeValue))); + FileMetaData *f4 = + CreateFileMetaData(1, InternalKey("5", 4, kTypeValue), + InternalKey(InternalKey("10", 4, kTypeValue))); + FileMetaData *f3 = + CreateFileMetaData(1, InternalKey("5", 3, kTypeValue), + InternalKey(InternalKey("10", 3, kTypeValue))); + FileMetaData *f2 = + CreateFileMetaData(1, InternalKey("5", 2, kTypeValue), + InternalKey(InternalKey("10", 2, kTypeValue))); + FileMetaData *f1 = + CreateFileMetaData(1, InternalKey("5", 1, kTypeValue), + InternalKey(InternalKey("10", 1, kTypeValue))); - level_files_.push_back(f2); - level_files_.push_back(f3); level_files_.push_back(f1); - compaction_files_.push_back(f1); - - AddBoundaryInputs(icmp_, level_files_, &compaction_files_); - ASSERT_EQ(3, compaction_files_.size()); - ASSERT_EQ(f1, compaction_files_[0]); - ASSERT_EQ(f3, compaction_files_[1]); - ASSERT_EQ(f2, compaction_files_[2]); -} - -TEST(AddBoundaryInputsTest, TestDisjoinFilePointers) { - FileMetaData* f1 = - CreateFileMetaData(1, InternalKey("100", 6, kTypeValue), - InternalKey(InternalKey("100", 5, kTypeValue))); - FileMetaData* f2 = - CreateFileMetaData(1, InternalKey("100", 6, kTypeValue), - InternalKey(InternalKey("100", 5, kTypeValue))); - FileMetaData* f3 = - CreateFileMetaData(1, InternalKey("100", 2, kTypeValue), - InternalKey(InternalKey("300", 1, kTypeValue))); - FileMetaData* f4 = - CreateFileMetaData(1, InternalKey("100", 4, kTypeValue), - InternalKey(InternalKey("100", 3, kTypeValue))); - level_files_.push_back(f2); level_files_.push_back(f3); level_files_.push_back(f4); + level_files_.push_back(f5); - compaction_files_.push_back(f1); + auto f = FindSmallestBoundaryFile(icmp_, level_files_, + InternalKey("5", 6, kTypeValue)); - AddBoundaryInputs(icmp_, level_files_, &compaction_files_); - ASSERT_EQ(3, compaction_files_.size()); - ASSERT_EQ(f1, compaction_files_[0]); - ASSERT_EQ(f4, compaction_files_[1]); - ASSERT_EQ(f3, compaction_files_[2]); + ASSERT_EQ(f5, f); } -} // namespace leveldb +} // namespace leveldb -int main(int argc, char** argv) { return leveldb::test::RunAllTests(); } +int main(int argc, char **argv) { return leveldb::test::RunAllTests(); } diff --git a/demo/case/open_close.cc b/demo/case/open_close.cc index 8b2aaca..34926b6 100644 --- a/demo/case/open_close.cc +++ b/demo/case/open_close.cc @@ -13,8 +13,8 @@ int main() { delete db; // This closes the database - status = leveldb::DestroyDB("/tmp/testdb", options); - assert(status.ok()); + // status = leveldb::DestroyDB("/tmp/testdb", options); + // assert(status.ok()); return 0; } \ No newline at end of file diff --git a/doc/learn/db.md b/doc/learn/db.md index 1c88f2a..cc071b3 100644 --- a/doc/learn/db.md +++ b/doc/learn/db.md @@ -562,6 +562,92 @@ current_->GetOverlappingInputs(level + 1, &f->smallest, &f->largest, +### LogAndApply() + +由VersionSet::Builder根据edit和current version 生成一个新的version,将此version添加到versionset链表中。 并持久化edit。 更新manifest内容。 + + + +### Recover() + +读取CURRENT,从而读取MANIFEST,从而得到version edit。 + +通过versionset::builder应用version edit得到version。 + +将version添加到versionset链表中。 + +将versionedit状态同步到versionset中。 + + + + + +### NumLevelFiles() + +current version的files[level]的数量 + + + +### PickCompaction() + +负责选择需要进行压缩的一组文件。压缩是一个减少文件数量和数据量的过程,有助于优化数据库性能。 + +简化版解释如下: + +1. **评估压缩需求**:计算每一层的“压缩得分”,根据文件大小和文件数量等因素评估哪些层和文件需要压缩。 +2. **选择文件进行压缩**:选择压缩得分最高的层和文件,即那些最需要压缩的文件。 +3. **返回压缩计划**:函数返回一个包含待压缩文件和涉及层信息的 `Compaction*` 对象。 + +压缩有助于通过合并较小的文件为较大的文件并移除过时数据来保持数据库的高效运行。 + + + +### CompactRange() + + + + + + + +### `FindSmallestBoundaryFile()` + +找`min(f.user_key) `并且` max(f.largest.seqno)` + +用于查找文件中最小的边界键。这个函数通常在压缩过程或键范围查询中使用,目的是确保边界键范围内的数据处理是完整和一致的。 + +具体来说,它会: + +1. **遍历文件**:检查当前文件列表中的每一个文件。 +2. **比较键值**:对于每个文件,它会比较文件的边界键值,找到范围内的最小键。 +3. **返回结果**:函数最终会返回包含这个最小边界键的文件。 + +通过这种方式,`FindSmallestBoundaryFile` 确保在压缩或查询操作中,不会遗漏任何在指定键范围内的重要数据。 + + + +### AddBoundaryInputs() + +`AddBoundaryInputs` 是在 LevelDB 的压缩过程中用来确定哪些额外的文件需要加入当前压缩任务的函数。它的主要目的是确保压缩过程中所处理的键范围是完整的,避免遗漏任何相关的数据。 + +以下是其大致步骤: + +1. **确定边界范围**:函数会首先确认当前压缩任务所涉及的键范围(key range)。 +2. **添加边界输入**:根据确定的键范围,函数会检查并添加所有可能会影响该范围的文件。这些文件的键范围可能与当前压缩任务的范围有重叠。 +3. **确保一致性**:通过添加这些边界文件,函数确保压缩过程中不会遗漏任何数据,保持数据库的一致性和完整性。 + +这一过程帮助数据库在压缩时不会丢失任何重要数据,确保数据的完整性和高效性。 + + + + + + + +### TODO + + + -- Gitee From 0fb194f53f5e4105a1bfcfd946a7a6f2cfcddc8e Mon Sep 17 00:00:00 2001 From: hellolutar Date: Wed, 30 Oct 2024 17:48:13 +0800 Subject: [PATCH 12/22] chore: part of comments --- db/db_impl.cc | 88 +++++++++++++++++++++++++++++++++++++---- db/db_impl.h | 17 ++++++++ db/dbformat.h | 4 +- db/filename.cc | 3 +- db/filename.h | 6 +++ db/version_set.cc | 24 ++++++++++- db/version_set.h | 8 ++-- doc/learn/compaction.md | 33 ++++++++++++++-- doc/learn/db.md | 86 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 247 insertions(+), 22 deletions(-) diff --git a/db/db_impl.cc b/db/db_impl.cc index d80b81b..5bd603f 100644 --- a/db/db_impl.cc +++ b/db/db_impl.cc @@ -75,13 +75,23 @@ struct DBImpl::CompactionState { uint64_t file_size; InternalKey smallest, largest; }; + + /** + * @brief 输出的文件,即要添加到version edit的new_files_ + */ std::vector outputs; // State kept for output being generated + // 为生成输出而保留的状态 + /** - * @brief 为生成输出而保留的状态 + * @brief 输出压缩后的table的文件描述符 */ WritableFile *outfile; + + /** + * @brief 压缩后的table + */ TableBuilder *builder; uint64_t total_bytes; @@ -239,24 +249,30 @@ void DBImpl::DeleteObsoleteFiles() { uint64_t number; FileType type; for (size_t i = 0; i < filenames.size(); i++) { + // 遍历${dbname_}下的所有文件,根据文件类型执行不同的逻辑 if (ParseFileName(filenames[i], &number, &type)) { bool keep = true; switch (type) { case kLogFile: + // 只保留当前log number和前一个log number keep = ((number >= versions_->LogNumber()) || (number == versions_->PrevLogNumber())); break; case kDescriptorFile: // Keep my manifest file, and any newer incarnations' // (in case there is a race that allows other incarnations) + + // 保留my manifest file,以及任何更新的 keep = (number >= versions_->ManifestFileNumber()); break; case kTableFile: + // 保留version set中的version引用的文件(level) keep = (live.find(number) != live.end()); break; case kTempFile: // Any temp files that are currently being written to must // be recorded in pending_outputs_, which is inserted into "live" + // 当前要写入的任何临时文件都必须记录在pending_outputs_中,该文件插入到“live”中。 keep = (live.find(number) != live.end()); break; case kCurrentFile: @@ -267,11 +283,13 @@ void DBImpl::DeleteObsoleteFiles() { } if (!keep) { + // 从table cache中移除条目 if (type == kTableFile) { table_cache_->Evict(number); } Log(options_.info_log, "Delete type=%d #%lld\n", static_cast(type), static_cast(number)); + // 删除文件 env_->DeleteFile(dbname_ + "/" + filenames[i]); } } @@ -322,6 +340,13 @@ Status DBImpl::Recover(VersionEdit *edit, bool *save_manifest) { // Note that PrevLogNumber() is no longer used, but we pay // attention to it in case we are recovering a database // produced by an older version of leveldb. + + /* + 从所有比描述符中命名的日志文件更新的日志文件中恢复(新日志文件可能是由前一个版本添加的,但没有在描述符中注册) + 请注意,PrevLogNumber()不再使用,但我们要注意它,以防我们正在恢复由旧版本的leveldb生成的数据库。 + + 我的理解:此时已恢复了version set。 遍历${dbname_}下所有文件,并 + */ const uint64_t min_log = versions_->LogNumber(); const uint64_t prev_log = versions_->PrevLogNumber(); std::vector filenames; @@ -333,7 +358,7 @@ Status DBImpl::Recover(VersionEdit *edit, bool *save_manifest) { versions_->AddLiveFiles(&expected); uint64_t number; FileType type; - std::vector logs; + std::vector logs; // version set引用的所有日志文件 for (size_t i = 0; i < filenames.size(); i++) { if (ParseFileName(filenames[i], &number, &type)) { expected.erase(number); @@ -341,6 +366,7 @@ Status DBImpl::Recover(VersionEdit *edit, bool *save_manifest) { logs.push_back(number); } } + // 确认version set涉及的文件都存在 if (!expected.empty()) { char buf[50]; snprintf(buf, sizeof(buf), "%d missing files; e.g.", @@ -349,6 +375,7 @@ Status DBImpl::Recover(VersionEdit *edit, bool *save_manifest) { } // Recover in the order in which the logs were generated + // 按照日志生成的顺序进行恢复 std::sort(logs.begin(), logs.end()); for (size_t i = 0; i < logs.size(); i++) { s = RecoverLogFile(logs[i], (i == logs.size() - 1), save_manifest, edit, @@ -408,6 +435,10 @@ Status DBImpl::RecoverLogFile(uint64_t log_number, bool last_log, // paranoid_checks==false so that corruptions cause entire commits // to be skipped instead of propagating bad information (like overly // large sequence numbers). + /* + 我们故意让log::Reader做校验和,即使是在paranoid==false的情况下, + 这样损坏会导致整个提交被跳过,而不是传播坏的信息(比如过大的序列号)。 + */ log::Reader reader(file, &reporter, true /*checksum*/, 0 /*initial_offset*/); Log(options_.info_log, "Recovering log #%llu", (unsigned long long)log_number); @@ -424,27 +455,34 @@ Status DBImpl::RecoverLogFile(uint64_t log_number, bool last_log, Status::Corruption("log record too small")); continue; } + // 将内容放到batch中 WriteBatchInternal::SetContents(&batch, record); + // 构建MemTable if (mem == nullptr) { mem = new MemTable(internal_comparator_); mem->Ref(); } + // 迭代器内部迭代batch,将内容放入MemTable status = WriteBatchInternal::InsertInto(&batch, mem); MaybeIgnoreError(&status); if (!status.ok()) { break; } + // 得到last sequence const SequenceNumber last_seq = WriteBatchInternal::Sequence(&batch) + WriteBatchInternal::Count(&batch) - 1; if (last_seq > *max_sequence) { *max_sequence = last_seq; } + // 将memtable持久化到磁盘,其文件添加到version edit的new_files_中 if (mem->ApproximateMemoryUsage() > options_.write_buffer_size) { compactions++; - *save_manifest = true; + *save_manifest = + true; // 告诉caller,需要更新manifest,将其指向并持久化version edit status = WriteLevel0Table(mem, edit, nullptr); + // 将MemTable置nullptr mem->Unref(); mem = nullptr; if (!status.ok()) { @@ -454,9 +492,11 @@ Status DBImpl::RecoverLogFile(uint64_t log_number, bool last_log, } } } + // 至此,形成了memTable、versionEdit、sst文件、save_manifest delete file; + // TODO(lutar),由于options_.reuse_logs默认为false,所以此if语句可以不关注 // See if we should keep reusing the last log file. if (status.ok() && options_.reuse_logs && last_log && compactions == 0) { assert(logfile_ == nullptr); @@ -711,6 +751,7 @@ void DBImpl::BackgroundCompaction() { c = versions_->CompactRange(m->level, m->begin, m->end); m->done = (c == nullptr); if (c != nullptr) { + // 获取level的最后一个file manual_end = c->input(0, c->num_input_files(0) - 1)->largest; } Log(options_.info_log, @@ -719,7 +760,7 @@ void DBImpl::BackgroundCompaction() { (m->end ? m->end->DebugString().c_str() : "(end)"), (m->done ? "(end)" : manual_end.DebugString().c_str())); } else { - // 压缩逻辑 + // 拿到需要压缩的文件集合,即compaction.inputs c = versions_->PickCompaction(); } @@ -728,7 +769,9 @@ void DBImpl::BackgroundCompaction() { // Nothing to do } else if (!is_manual && c->IsTrivialMove()) { // Move file to next level - assert(c->num_input_files(0) == 1); + assert(c->num_input_files(0) == 1); // IsTrivialMove()为true, + // 则此断言一定成立 + // 获取level的第一个file FileMetaData *f = c->input(0, 0); c->edit()->DeleteFile(c->level(), f->number); c->edit()->AddFile(c->level() + 1, f->number, f->file_size, f->smallest, @@ -744,6 +787,7 @@ void DBImpl::BackgroundCompaction() { status.ToString().c_str(), versions_->LevelSummary(&tmp)); } else { CompactionState *compact = new CompactionState(c); + // 压缩逻辑 status = DoCompactionWork(compact); if (!status.ok()) { RecordBackgroundError(status); @@ -853,6 +897,7 @@ Status DBImpl::FinishCompactionOutputFile(CompactionState *compact, delete compact->outfile; compact->outfile = nullptr; + // 写入完成后,加载到cache中 if (s.ok() && current_entries > 0) { // Verify that the table is usable // 验证table是否可用:sstable将footer放置在文件的最后,current_bytes即表示文件的最后, @@ -879,13 +924,16 @@ Status DBImpl::InstallCompactionResults(CompactionState *compact) { static_cast(compact->total_bytes)); // Add compaction outputs + // 将compaction inputs文件全部放入versionedit的deleted_files_中 compact->compaction->AddInputDeletions(compact->compaction->edit()); const int level = compact->compaction->level(); + // 将compaction outputs文件添加到versionedit的new_files_中 for (size_t i = 0; i < compact->outputs.size(); i++) { const CompactionState::Output &out = compact->outputs[i]; compact->compaction->edit()->AddFile(level + 1, out.number, out.file_size, out.smallest, out.largest); } + // 持久化versionedit,更新manifest return versions_->LogAndApply(compact->compaction->edit(), &mutex_); } @@ -926,7 +974,7 @@ Status DBImpl::DoCompactionWork(CompactionState *compact) { bool has_current_user_key = false; SequenceNumber last_sequence_for_key = kMaxSequenceNumber; - // input 迭代 + // input 迭代所有条目,将需要保留的条目构建为新的table并持久化。 for (; input->Valid() && !shutting_down_.load(std::memory_order_acquire);) { // Prioritize immutable compaction work @@ -935,7 +983,7 @@ Status DBImpl::DoCompactionWork(CompactionState *compact) { const uint64_t imm_start = env_->NowMicros(); mutex_.Lock(); if (imm_ != nullptr) { - CompactMemTable(); + CompactMemTable(); // imm_压缩为sst,并持久化versionedit和更新manifest // Wake up MakeRoomForWrite() if necessary. background_work_finished_signal_.SignalAll(); } @@ -1008,17 +1056,22 @@ Status DBImpl::DoCompactionWork(CompactionState *compact) { if (!drop) { // Open output file if necessary if (compact->builder == nullptr) { + // 设置compact.builder compact.outfile compact.outputs status = OpenCompactionOutputFile(compact); if (!status.ok()) { break; } } if (compact->builder->NumEntries() == 0) { + // 在这里设置output.smallest. 关于compact.output @see + // DBImpl::OpenCompactionOutputFile compact->current_output()->smallest.DecodeFrom(key); } + // 在这里设置output.largest. 关于compact.output @see + // DBImpl::OpenCompactionOutputFile compact->current_output()->largest.DecodeFrom(key); - // 将需要压缩时需要保留的记录添加到Compaction的输出文件中 + // 将需要压缩时需要保留的记录添加到Compaction的table builder中 compact->builder->Add(key, input->value()); // Close output file if it is big enough @@ -1038,6 +1091,7 @@ Status DBImpl::DoCompactionWork(CompactionState *compact) { status = Status::IOError("Deleting DB during compaction"); } if (status.ok() && compact->builder != nullptr) { + // 输出table到磁盘 status = FinishCompactionOutputFile(compact, input); } if (status.ok()) { @@ -1046,6 +1100,7 @@ Status DBImpl::DoCompactionWork(CompactionState *compact) { delete input; input = nullptr; + // 统计信息 CompactionStats stats; stats.micros = env_->NowMicros() - start_micros - imm_micros; for (int which = 0; which < 2; which++) { @@ -1059,8 +1114,14 @@ Status DBImpl::DoCompactionWork(CompactionState *compact) { mutex_.Lock(); stats_[compact->compaction->level() + 1].Add(stats); + // end统计信息 if (status.ok()) { + /* + 将compaction inputs文件全部放入versionedit的deleted_files_中 + 将compaction outputs文件添加到versionedit的new_files_中 + 持久化versionedit,更新manifest + */ status = InstallCompactionResults(compact); } if (!status.ok()) { @@ -1547,6 +1608,11 @@ Status DB::Open(const Options &options, const std::string &dbname, DB **dbptr) { VersionEdit edit; // Recover handles create_if_missing, error_if_exists bool save_manifest = false; + /* + 恢复memTable,如果恢复过程中内容大小超过阈值, + 会形成sst文件,以versionEdit.new_files_的形式呈现, + 并且save_manifest为true。@see DBImpl::RecoverLogFile + */ Status s = impl->Recover(&edit, &save_manifest); // 表明第一次创建数据库,需要为数据库做一些初始化操作 if (s.ok() && impl->mem_ == nullptr) { @@ -1568,8 +1634,11 @@ Status DB::Open(const Options &options, const std::string &dbname, DB **dbptr) { if (s.ok() && save_manifest) { edit.SetPrevLogNumber(0); // No older logs needed after recovery. edit.SetLogNumber(impl->logfile_number_); + // 生成新version s = impl->versions_->LogAndApply(&edit, &impl->mutex_); } + // 至此,已完成Recover() + if (s.ok()) { impl->DeleteObsoleteFiles(); impl->MaybeScheduleCompaction(); @@ -1595,12 +1664,14 @@ Status DestroyDB(const std::string &dbname, const Options &options) { return Status::OK(); } + // 1. 上锁 FileLock *lock; const std::string lockname = LockFileName(dbname); result = env->LockFile(lockname, &lock); if (result.ok()) { uint64_t number; FileType type; + // 2. 删除文件 for (size_t i = 0; i < filenames.size(); i++) { if (ParseFileName(filenames[i], &number, &type) && type != kDBLockFile) { // Lock file will be deleted at end @@ -1610,6 +1681,7 @@ Status DestroyDB(const std::string &dbname, const Options &options) { } } } + // 3. 解锁并删除锁文件 env->UnlockFile(lock); // Ignore error since state is already gone env->DeleteFile(lockname); env->DeleteDir(dbname); // Ignore error in case dir contains other files diff --git a/db/db_impl.h b/db/db_impl.h index 7e354e5..49eee20 100644 --- a/db/db_impl.h +++ b/db/db_impl.h @@ -84,6 +84,10 @@ private: /** * @brief 从持久存储恢复描述符。 * 可能需要做大量的工作来恢复最近记录的更新。对描述符所做的任何更改都会添加到*edit中。 + * + * 先恢复VersionSet,然后恢复log + * + * @param save_manifest 告诉caller是否需要更新和保存新的Manifest文件 */ Status Recover(VersionEdit *edit, bool *save_manifest) EXCLUSIVE_LOCKS_REQUIRED(mutex_); @@ -96,8 +100,18 @@ private: // Compact the in-memory write buffer to disk. Switches to a new // log-file/memtable and writes a new descriptor iff successful. // Errors are recorded in bg_error_. + /** + * @brief 将内存中的write buffer压缩到磁盘(level-0)。 + * 完成后,将imm_置为nullptr + * + * 切换到一个新的log-file/memtable,如果成功的话,写一个新的描述符。错误记录在bg_error_中。 + */ void CompactMemTable() EXCLUSIVE_LOCKS_REQUIRED(mutex_); + /** + * + * @param save_manifest 告诉caller是否需要更新和保存新的Manifest文件 + */ Status RecoverLogFile(uint64_t log_number, bool last_log, bool *save_manifest, VersionEdit *edit, SequenceNumber *max_sequence) EXCLUSIVE_LOCKS_REQUIRED(mutex_); @@ -237,6 +251,9 @@ private: // Sanitize db options. The caller should delete result.info_log if // it is not equal to src.info_log. +/** + * @brief 清理db选项。如果result.info_log不等于src.info_log,调用者应该删除它。 + */ Options SanitizeOptions(const std::string &db, const InternalKeyComparator *icmp, const InternalFilterPolicy *ipolicy, diff --git a/db/dbformat.h b/db/dbformat.h index a9366f3..065917f 100644 --- a/db/dbformat.h +++ b/db/dbformat.h @@ -47,8 +47,8 @@ static const int kL0_StopWritesTrigger = 12; /* 如果新的压缩后的memtable不会产生重叠,则将其推至的最高级别。 我们尝试推到级别2,以避免相对昂贵的级别0=>1压缩,并避免一些昂贵的manifest文件操作。 -我们不会一直推到最大级别,因为如果重复覆盖相同的key -space,可能会产生大量浪费的磁盘空间。 +我们不会一直推到最大级别,因为如果重复覆盖相同的key space, +可能会产生大量浪费的磁盘空间。 */ static const int kMaxMemCompactLevel = 2; diff --git a/db/filename.cc b/db/filename.cc index c0908c7..7dcbf83 100644 --- a/db/filename.cc +++ b/db/filename.cc @@ -38,7 +38,6 @@ std::string SSTTableFileName(const std::string &dbname, uint64_t number) { return MakeFileName(dbname, number, "sst"); } - std::string DescriptorFileName(const std::string &dbname, uint64_t number) { assert(number > 0); char buf[100]; @@ -125,7 +124,7 @@ Status SetCurrentFile(Env *env, const std::string &dbname, std::string manifest = DescriptorFileName(dbname, descriptor_number); Slice contents = manifest; assert(contents.starts_with(dbname + "/")); - contents.remove_prefix(dbname.size() + 1); + contents.remove_prefix(dbname.size() + 1); // MANIFEST-${number} std::string tmp = TempFileName(dbname, descriptor_number); Status s = WriteStringToFileSync(env, contents.ToString() + "\n", tmp); if (s.ok()) { diff --git a/db/filename.h b/db/filename.h index 8143ef0..8a38b59 100644 --- a/db/filename.h +++ b/db/filename.h @@ -114,6 +114,12 @@ bool ParseFileName(const std::string &filename, uint64_t *number, // Make the CURRENT file point to the descriptor file with the // specified number. + +/** + * @brief 使CURRENT文件指向具有指定编号的描述符文件。 + * + * CURRENT文件内容为:MANIFEST-${number} + */ Status SetCurrentFile(Env *env, const std::string &dbname, uint64_t descriptor_number); diff --git a/db/version_set.cc b/db/version_set.cc index ec891d7..c025b0f 100644 --- a/db/version_set.cc +++ b/db/version_set.cc @@ -26,6 +26,11 @@ static size_t TargetFileSize(const Options *options) { // Maximum bytes of overlaps in grandparent (i.e., level+2) before we // stop building a single file in a level->level+1 compaction. + +/** + * @brief + * 在我们停止在level->level+1压缩中构建单个文件之前,祖父级(即level+2)中重叠的最大字节数。 + */ static int64_t MaxGrandParentOverlapBytes(const Options *options) { return 10 * TargetFileSize(options); } @@ -33,6 +38,12 @@ static int64_t MaxGrandParentOverlapBytes(const Options *options) { // Maximum number of bytes in all compacted files. We avoid expanding // the lower level file set of a compaction if it would make the // total compaction cover more than this many bytes. + +/** + * @brief 所有压缩文件中的最大字节数。 + * + * 如果压缩的低级别文件集会使总压缩覆盖的字节数超过这么多,我们就避免扩展它。 + */ static int64_t ExpandedCompactionByteSizeLimit(const Options *options) { return 25 * TargetFileSize(options); } @@ -541,6 +552,7 @@ bool Version::OverlapInLevel(int level, const Slice *smallest_user_key, int Version::PickLevelForMemTableOutput(const Slice &smallest_user_key, const Slice &largest_user_key) { int level = 0; + // 若level-0存在重叠,则OverlapInLevel返回true,则不进入if,而是直接返回了0。 if (!OverlapInLevel(0, &smallest_user_key, &largest_user_key)) { // Push to next level if there is no overlap in next level, // and the #bytes overlapping in the level after that are limited. @@ -548,6 +560,7 @@ int Version::PickLevelForMemTableOutput(const Slice &smallest_user_key, InternalKey limit(largest_user_key, 0, static_cast(0)); std::vector overlaps; while (level < config::kMaxMemCompactLevel) { + // 若level+1存在重叠,则OverlapInLevel返回true, 则跳出while, 返回level if (OverlapInLevel(level + 1, &smallest_user_key, &largest_user_key)) { break; } @@ -555,6 +568,7 @@ int Version::PickLevelForMemTableOutput(const Slice &smallest_user_key, // Check that file does not overlap too many grandparent bytes. GetOverlappingInputs(level + 2, &start, &limit, &overlaps); const int64_t sum = TotalFileSize(overlaps); + // grandparent level的重叠大小字节数已经超出了限制, 跳出while循环, 返回level if (sum > MaxGrandParentOverlapBytes(vset_->options_)) { break; } @@ -1565,22 +1579,25 @@ void AddBoundaryInputs(const InternalKeyComparator &icmp, void VersionSet::SetupOtherInputs(Compaction *c) { const int level = c->level(); InternalKey smallest, largest; - + // 未扩展前 获取的 inputs的数量,这里是获取inputs_[0] AddBoundaryInputs(icmp_, current_->files_[level], &c->inputs_[0]); GetRange(c->inputs_[0], &smallest, &largest); + // 未扩展前 获取的 inputs的数量,这里是获取inputs_[1] current_->GetOverlappingInputs(level + 1, &smallest, &largest, &c->inputs_[1]); // Get entire range covered by compaction - // 把整个范围都压实 + // 这里是求取inputs[0]和inputs[1]中的start和limit InternalKey all_start, all_limit; GetRange2(c->inputs_[0], c->inputs_[1], &all_start, &all_limit); // See if we can grow the number of inputs in "level" without // changing the number of "level+1" files we pick up. + // 想要扩展compaction中inputs数量,以便于更高效地合并和排序数据。 // 看看我们是否可以在不改变我们拾取的“level+1”文件数量的情况下增加“level”中的输入数量。 if (!c->inputs_[1].empty()) { + // 扩展inputs_[0] std::vector expanded0; current_->GetOverlappingInputs(level, &all_start, &all_limit, &expanded0); AddBoundaryInputs(icmp_, current_->files_[level], &expanded0); @@ -1590,8 +1607,11 @@ void VersionSet::SetupOtherInputs(Compaction *c) { if (expanded0.size() > c->inputs_[0].size() && inputs1_size + expanded0_size < ExpandedCompactionByteSizeLimit(options_)) { + // 扩展了压缩 InternalKey new_start, new_limit; GetRange(expanded0, &new_start, &new_limit); + + // 扩展inputs_[1] std::vector expanded1; current_->GetOverlappingInputs(level + 1, &new_start, &new_limit, &expanded1); diff --git a/db/version_set.h b/db/version_set.h index fb25eda..3012f78 100644 --- a/db/version_set.h +++ b/db/version_set.h @@ -183,9 +183,7 @@ public: // Return the level at which we should place a new memtable compaction // result that covers the range [smallest_user_key,largest_user_key]. /** - * 返回需要压缩的级别: level+1存在重叠, level+2重叠字节数太多 - * 返回应该放置新的memtable compaction result的级别, - * 该级别覆盖范围[smallst_user_key,largest_user_key]。 + * @brief 用于确定在将MemTable写入磁盘时,应该将数据写入到哪个层级(level)。 */ int PickLevelForMemTableOutput(const Slice &smallest_user_key, const Slice &largest_user_key); @@ -310,6 +308,8 @@ public: * - 解码得到version edit * - 通过versionset builder得到version, * - 将version添加到version set中 + * + * @param save_manifest 告诉caller是否需要更新和保存新的Manifest文件 */ Status Recover(bool *save_manifest); @@ -661,7 +661,7 @@ private: // Each compaction reads inputs from "level_" and "level_+1" /** - * @brief 每次Compaction从“level_”和“level_+1”读取输入 + * @brief 需要压缩的文件,每次Compaction从“level_”和“level_+1”读取输入 */ std::vector inputs_[2]; // The two sets of inputs diff --git a/doc/learn/compaction.md b/doc/learn/compaction.md index 2a84ef4..5909633 100644 --- a/doc/learn/compaction.md +++ b/doc/learn/compaction.md @@ -120,11 +120,36 @@ private: - new_files_: 新增了哪些文件:level、number、size、smallest、largest - deleted_files_: 删除了哪些文件:level、number -- **compact_pointer_:不清楚作用** -- **log_number_:不清楚作用** -- **prev_log_number_:不清楚作用** -- **next_file_number_:不清楚作用** +- compact_pointer_:压缩位置 +- log_number_:当前的log number +- prev_log_number_:前一个log number +- next_file_number_:下一个log number - **last_sequence_:不清楚作用** - EncodeTo() 编码为字符串 - DecodeFrom() 从字符串中解码 + + + + +DBImpl::BackgroundCompaction()压缩 + +- CompactMemTable() 压缩imm, 较为简单 +- 其他情况 + - 拿到Compaction + - 客户调用:从manual_compaction_获取压缩的范围、level,调用versionset.CompactRange()得到Compaction + - 非客户调用:调用versionset.PickCompaction()得到Compaction + - Compaction是简单压缩 + - 是:获取当前level的第一个文件(有且只有一个文件)。更新versionedit:在level中删除,在level+1中添加。 然后将versionedit持久化,并更新manifest。 + - 否:通过Compaction得到CompactionState。 调用DBImpl.DoCompactionWork()。 + + + +常规压缩原理 + +通过PickCompaction()拿到Compaction,此时compaction.inputs代表level和level+1中要压缩的文件。 + +迭代compaction.inputs将需要留下的文件,合并为一个table,将table持久化。 + +然后将compaction.inputs作为version edit的deletedfiles。 + diff --git a/doc/learn/db.md b/doc/learn/db.md index cc071b3..76928f9 100644 --- a/doc/learn/db.md +++ b/doc/learn/db.md @@ -451,6 +451,55 @@ entry格式 + + +### PickLevelForMemTableOutput() + +> [LevelDB设计与实现 - Compaction - 知乎](https://zhuanlan.zhihu.com/p/51573929) + +用于确定在将内存表(MemTable)写入磁盘时,应该将数据写入到哪个层级(level)。 + + + +``` mermaid +flowchart TD + +c1{New sstable的key与level0的文件是否有重叠} + +c2{New sstable的key与level1的文件是否有重叠} + +c3{New sstable的key与level2重叠文件太多,这些文件size之和超过阈值} + +c4{New sstable的key与level2的文件是否有重叠} + +c5{New sstable的key与level3重叠文件太多,这些文件size之和超过阈值} + +c1 --Yes--> level0 +c1 --No--> c2 +c2 --Yes--> level0 +c2 --No--> c3 +c3 --Yes--> level0 +c3 --No--> c4 +c4 --Yes--> level1 +c4 --No--> c5 +c5 --Yes--> level1 +c5 --No--> level2 + + +``` + + + + + + + + + + + + + ### GetOverlappingInputs() 将level中所有满足[begin,end]范围的文件添加到inputs中。如果时level-0,则有可能会扩展begin,end的范围。 @@ -519,6 +568,30 @@ current_->GetOverlappingInputs(level + 1, &f->smallest, &f->largest, +### SetupOtherInputs() + +- 调用`AddBoundaryInputs()` 设置`compaction.inputs[0]` +- 调用`GetRange()`设置smallest和largest。 +- 调用`GetOverlappingInputs()`设置`compaction.inputs[0]`和可能设置smallest、largest。 +- 扩展`compaction.inputs[0]`, `compaction.inputs[1]` +- 调用`GetOverlappingInputs()`设置`compaction.grandparents` +- 设置version set的`compact_pointer[level]`为largest. +- 设置`compaction.edit.compact_pointer[level]`为largest + + + + + +### GetRange2() + +将两个vector合并,然后调用GetRange(),得到smallest,largest + + + + + + + ## Compaction @@ -538,6 +611,19 @@ current_->GetOverlappingInputs(level + 1, &f->smallest, &f->largest, +`inputs`字段是一个包含要参与Compaction的SSTable文件列表。 + +- `inputs[0]`:通常指当前级别需要进行Compaction的SSTable文件集合。这些文件是从一个级别中的所有文件中筛选出来的,包含了可能需要合并的数据。 +- `inputs[1]`:指向下一级别中与`inputs[0]`中数据范围重叠的SSTable文件集合。这些文件会与`inputs[0]`中的文件一起进行合并,以确保数据的有序性和完整性。 + +通过合并`inputs[0]`和`inputs[1]`中的文件,LevelDB能够有效地减少数据冗余,优化存储,并保持数据的有序排列。这个过程有助于提升数据库的读写性能。 + + + + + + + ### `IsTrivialMove()` 判断是否是一种简单的compaction。即只需将单个输入文件移动到下一个级别(不合并或分割)就可以实现。 -- Gitee From 301ffd2d4303c99a6a03b29141548578d5e54340 Mon Sep 17 00:00:00 2001 From: hellolutar Date: Thu, 31 Oct 2024 17:55:34 +0800 Subject: [PATCH 13/22] doc: add Doxyfile --- .gitignore | 4 +- Doxyfile | 2658 +++++++++++++++++++++++++++++++++++++ db/db_impl.cc | 12 +- db/write_batch_internal.h | 3 + doc/learn/db.md | 10 + 5 files changed, 2685 insertions(+), 2 deletions(-) create mode 100644 Doxyfile diff --git a/.gitignore b/.gitignore index f36d979..2273a42 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ build/ out/ -.cache \ No newline at end of file +.cache +html/ +latex/ \ No newline at end of file diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..b06c8df --- /dev/null +++ b/Doxyfile @@ -0,0 +1,2658 @@ +# Doxyfile 1.9.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "My Project" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = YES + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = YES + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = YES + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = build + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to +# YES then doxygen will add the directory of each input to the include path. +# The default value is: YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2 + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /