leveldb源码分析--SSTable之Compaction SetupOtherInputs

2023-11-04

leveldb源码分析--SSTable之Compaction

对于compaction是leveldb中体量最大的一部分,也应该是最为复杂的部分,为了便于理解我们首先从一些基本的概念开始。下面是一些从doc/impl.html中翻译和整理的内容:

Level 0

当日志文件超过一定大小的阈值是 (默认为 1MB):

  • 建立一个新的memtable和日志文件,以后的操作都是用新的memtable和日志文件
  • 后台进行如下操作:
    • 将旧的 memtable写到SSTable中(过程为先转为immtable_table,然后遍历写入)
    • 废弃旧的 memtable
    • 删除旧的 memtable和日志文件
    • 将新的SSTable加到level 0中.

这是doc/impl.html中的说明,但是在源代码中我们可以看到在MakeRoomForWrite函数中有逻辑,当满足一些其他条件之后(这里的其他条件不涉及到这个阈值大小)(mem_->ApproximateMemoryUsage() > options_.write_buffer_size) 就会有

log_ = new log::Writer(lfile);
    imm_ = mem_;
    has_imm_.Release_Store(imm_);
    mem_ = new MemTable(internal_comparator_);

  这些操作,及上面描述的操作。而再查找write_buffer_size在Options::Options()  中进行了如下初始化write_buffer_size(4<<20),所以这里不知道是不是文档过久未更新的原因,所以从代码来看应该是阈值达到4MB时。而且这里的计算也不是已日志文件为依据的,而是以memtable的内存使用量为依据,当然这里两个数据应该是相差不大的,只是直观上来说应该是memtable的内存使用量。

我们再来看看compaction涉及到的一些因素:

过程

当level L中的文件的大小超过阈值时,我们在后台对其进行compact。compaction过程是先在level L+1中查找和该文件存在key 范围有重叠(overlap)的文件,如果存在重叠的文件就将其作为compaction的输入文件,在合并完成以后将其删除。这里需要注意的是level-0比较特殊,因为level-0的文件本身就有可能相互重叠,所以level-0进行compaction时我们同样选择level-0中相互重叠的文件。

compaction就是讲选择的文件进行合并输出为level L+1的SSTable。当文件大小超过2MB的时候我们新生成一个文件;或者当当前文件可以和level L+2中的10个文件都有重叠时,这个条件是为了保证下次compaction level L+1的时候不会选择太多的 level L+2中的文件。

这个过程中会删除(逻辑上)旧文件,然后将新的文件加到工作状态(即加入到version set中)。

compaction每个level的时候我们都是以循环(以key为基准)的方式进行的,即每次compact之后我们记住compact到的key,下一次我们查找包含这个key之后的下一个key的文件,然后进行compact。

compaction会丢弃呗覆盖的value,丢弃无用的删除,这里的无用是指在这个key都不在更高所有的level的key range中。

Timing

Level 0的compaction最多从level 0读取4个1MB(4个4MB?)的文件,以及所有的level 1文件(10MB),也就是我们将读取14MB,并写入14BM。 
Level > 0的compaction,从level L选择一个2MB的文件,最坏情况下,将会和levelL+1的12个文件有重合(10:level L+1的总文件大小是level L的10倍;边界的2:level L的文件范围通常不会和level L+1的文件对齐)。因此Compaction将会读26MB,写26MB。对于100MB/s的磁盘IO来讲,compaction将最坏需要0.5秒。 
如果磁盘IO更低,比如10MB/s,那么compaction就需要更长的时间5秒。如果user以10MB/s的速度写入,我们可能生成很多level 0文件(50个来装载5*10MB的数据)。这将会严重影响读取效率,因为需要merge更多的文件。 
解决方法1:为了降低该问题,我们可能想增加log切换的阈值,缺点就是,log文件越大,对应的memtable文件就越大,这需要更多的内存。 
解决方法2:当level 0文件太多时,人工降低写入速度。 
解决方法3:降低merge的开销,如把level 0文件都无压缩的存放在cache中。

Number of files

对于更高的level我们可以创建更大的文件,而不是2MB,代价就是更多突发性的compaction。或者,我们可以考虑分区,把文件放存放多目录中来降低这个的开销。 
然而在2011年2月4号,作者做了一个实验,在ext3文件系统中当当前文件夹含有不同的文件数量时进行100K次打开文件,结果表明现在的文件系统其实可以不需要分区。

 

Files in directory Microseconds to open a file
1000 9
10000 10
100000 16

 

了解了compaction的一些原理和机制以后我们该回到代码来看看具体的代码流程是怎么样的,首先回到DBimpl中的MakeRoomForWrite

Status DBImpl::MakeRoomForWrite(bool force) {
  bool allow_delay = !force;
  Status s;
  while (true) {
    if (!bg_error_.ok()) {
      // Yield previous error
      s = bg_error_;
      break;
    } else if (
        allow_delay &&
        versions_->NumLevelFiles(0) >= config::kL0_SlowdownWritesTrigger) {
          // 当L0的文件数量要达到阈值的时候,我们每次写入都延迟1ms,
           // 这样可以为后台的compaction腾出一定的cpu(当后台compaction
         //和当前线程是使用的一个内核的时候)这样可以降低写入延迟的方差
          //因为延迟被分摊到多个写上面,而不是在几个甚至一个写的时候
      env_->SleepForMicroseconds(1000);
      allow_delay = false; // 每次写只允许延迟一次
    } else if (!force &&  //当前mmetable的占用量未达到阈值
               (mem_->ApproximateMemoryUsage() <= options_.write_buffer_size)) {
      break;
    } else if (imm_ != NULL) {
      // 上一次memtable的compaction尚未结束,等待后台compaction完成
       // 因为compaction的过程为 mem ->imm 完成后删除imm
      bg_cv_.Wait();
    } else if (versions_->NumLevelFiles(0) >= config::kL0_StopWritesTrigger) {
      // level 0的文件数量超过阈值,等待后台compaction完成
      bg_cv_.Wait();
    } else {
      // memtable达到阈值,新生成日志和memtable,并将原先的mem转化为imm给后台compact
      s = env_->NewWritableFile(LogFileName(dbname_, new_log_number), &lfile);
    
      delete log_;
      delete logfile_;
      logfile_ = lfile;
      logfile_number_ = new_log_number;
      log_ = new log::Writer(lfile);
      imm_ = mem_;
      has_imm_.Release_Store(imm_);
      mem_ = new MemTable(internal_comparator_);
      mem_->Ref();
      force = false;             // Do not force another compaction if have room
      MaybeScheduleCompaction(); //触发后台compaction
    }
  }
  return s;
}

MaybeScheduleCompaction函数只是简单判断后台线程是否已经启动和一些其他的错误判断,如果未启动则启动后台compaction线程。这个compaction线程的实现在DBImpl::BackgroundCall,这个函数也只是简单的调用实现了compaction实际逻辑的函数BackgroundCompaction,我们这里就来仔细分析一下这个函数

void DBImpl::BackgroundCompaction() {
  if (imm_ != NULL) { //有转化的memtable,直接将MemTable写入SSTable即返回
    CompactMemTable();
    return;
  }
  if (is_manual) { //用户主动(手动)触发的compaction
    ManualCompaction* m = manual_compaction_;
   //取得进项compact的输入文件生成compaction类
    c = versions_->CompactRange(m->level, m->begin, m->end);
    m->done = (c == NULL);
    if (c != NULL) {
     //取得level中最大的一个key
      manual_end = c->input(0, c->num_input_files(0) - 1)->largest;
    }
  } else {
    c = versions_->PickCompaction();
  }
  if (c == NULL) {
  } else if (!is_manual && c->IsTrivialMove()) {
  //如果不是主动触发的,并且level中的输入文件与level+1中无重叠,且与level + 2中重叠不大于
  //kMaxGrandParentOverlapBytes = 10 * kTargetFileSize,直接将文件移到level+1中
    c->edit()->DeleteFile(c->level(), f->number);
    c->edit()->AddFile(c->level() + 1, f->number, f->file_size,
                       f->smallest, f->largest);
    status = versions_->LogAndApply(c->edit(), &mutex_);    //写入version中,稍后分析
  } else {//否则调用DoCompactionWork进行Compact输入文件
    CompactionState* compact = new CompactionState(c);
    status = DoCompactionWork(compact);
    CleanupCompaction(compact);      //清理compact过程中的临时变量
    c->ReleaseInputs();               //清除输入文件描述符
    DeleteObsoleteFiles();            //删除无引用的文件
  }
  delete c;
  if (is_manual) {
    ManualCompaction* m = manual_compaction_;
    if (!status.ok()) {//如果compaction出错,也将手动的compaction标记为done
      m->done = true;
    }
    if (!m->done) {//如果没有完成也仅仅记录基本状态,感觉manual的形式未实现完整逻辑
      m->tmp_storage = manual_end;
      m->begin = &m->tmp_storage;
    }
    manual_compaction_ = NULL;
  }
}

当手动触发compaction时,具体compaction哪些文件是由 versions_->CompactRange 根据,level, begin, end来计算的,下面我们来看看这个函数的实现,看看是如何取得输入文件的

Compaction* VersionSet::CompactRange(
    int level,
    const InternalKey* begin,
    const InternalKey* end) {
 //将Level-level中的range与begin,end有重叠的SSTable描述符放入inputs中
  current_->GetOverlappingInputs(level, begin, end, &inputs);
  if (inputs.empty()) {
    return NULL;
  }

  // 一次不能compact过大的量,将前N个已经大于的保存下来,后面的文件描述符从inputs中移除.
  const uint64_t limit = MaxFileSizeForLevel(level);   //kTargetFileSize = 2 * 1048576;
  uint64_t total = 0;
  for (int i = 0; i < inputs.size(); i++) {
    uint64_t s = inputs[i]->file_size;
    total += s;
    if (total >= limit) {
      inputs.resize(i + 1);
      break;
    }
  }
//new一个Compaction类
  Compaction* c = new Compaction(level);
  c->input_version_ = current_;
  c->input_version_->Ref();
  c->inputs_[0] = inputs;
  SetupOtherInputs(c); //尝试加入level中新的文件,条件为不再与level+1中新的文件重叠
  return c;
}

可以看到在初步得到了应该compaction的文件和范围以后,代码还调用了SetupOtherInputs这个函数,他的作用是为了在不影响性能的情况下尽可能多的compaction当前level的文件

void VersionSet::SetupOtherInputs(Compaction* c) {
  GetRange(c->inputs_[0], &smallest, &largest);
//上一层中oeverlap的加入inputs_[1]
  current_->GetOverlappingInputs(level+1, &smallest, &largest, &c->inputs_[1]);
  // 所有inputs的开始结束范围
  GetRange2(c->inputs_[0], c->inputs_[1], &all_start, &all_limit);

  // 看能否将level中与取出的level+1中的range重叠的也加到inputs中,
  // 而新加的文件的range都在已经加入的level+1的文件的范围中
  if (!c->inputs_[1].empty()) {
   //取得和level+1的inputs重叠的level中的文件
    current_->GetOverlappingInputs(level, &all_start, &all_limit, &expanded0);
    const int64_t inputs0_size = TotalFileSize(c->inputs_[0]);
    const int64_t inputs1_size = TotalFileSize(c->inputs_[1]);
    const int64_t expanded0_size = TotalFileSize(expanded0);
    if (expanded0.size() > c->inputs_[0].size() &&  //level中有新文件加入,所有的大小不大于阈值
        inputs1_size + expanded0_size < kExpandedCompactionByteSizeLimit) {
        //kExpandedCompactionByteSizeLimit = 25 * kTargetFileSize;
      GetRange(expanded0, &new_start, &new_limit);
     // 取得level+1中与新的level中的输入文件overlap的文件
      current_->GetOverlappingInputs(level+1, &new_start, &new_limit,
                                     &expanded1);
      if (expanded1.size() == c->inputs_[1].size()) {
      //如果level+1中无新的文件加入,设置为新的inputs和范围
        smallest = new_start;
        largest = new_limit;
        c->inputs_[0] = expanded0;
        c->inputs_[1] = expanded1; //这里应该是相等的,此句可以省略
        GetRange2(c->inputs_[0], c->inputs_[1], &all_start, &all_limit);
      }
    }
  }
  // 取得level+2 中重叠的文件放入grandparents_
  if (level + 2 < config::kNumLevels) {
    current_->GetOverlappingInputs(level + 2, &all_start, &all_limit,
                                   &c->grandparents_);
  }
  
  //记录本次compact到的key,下次从这个key继续往后compact
  compact_pointer_[level] = largest.Encode().ToString();
  c->edit_.SetCompactPointer(level, largest);
}

手动compaction时如何获取选择输入文件的逻辑就分析完了,那么leveldb满足其内部一些阈值条件后触发的compaction是如何选择输入文件的呢?这个逻辑在中,下面我们来仔细的分析一下

Compaction* VersionSet::PickCompaction() {
 //每次compact完成在VersionSet::Finalize中计算每个level中TotalFileSize / MaxBytesForLevel
 // 的值,并且将最大的值最为compaction_score_ ,和compaction_level_
  const bool size_compaction = (current_->compaction_score_ >= 1);  
  //对于每个SSTable会有一个 允许seek的次数 (f->file_size / 16384)超过这么多次会将其设置为
  const bool seek_compaction = (current_->file_to_compact_ != NULL);
  // 这两种可能导致的compaction中,我们优先compact第一种情况的
  if (size_compaction) {
    level = current_->compaction_level_;
    c = new Compaction(level);
    // 查找第一个包含比上次已经compact的最大key大的key的文件
    for (size_t i = 0; i < current_->files_[level].size(); i++) {
      if (compact_pointer_[level].empty() ||
          icmp_.Compare(f->largest.Encode(), compact_pointer_[level]) > 0) {
        c->inputs_[0].push_back(f);
        break;
      }
    }
    if (c->inputs_[0].empty()) {
      // 如果上次已经是最大的key,那么回到第一个文件开始compact
      c->inputs_[0].push_back(current_->files_[level][0]);
    }
  } else if (seek_compaction) {//如果是查找导致的,直接将导致compact的文件加入inputs_[0]
    level = current_->file_to_compact_level_;
    c = new Compaction(level);
    c->inputs_[0].push_back(current_->file_to_compact_);
  } else {
    return NULL;
  }
  c->input_version_ = current_;
  c->input_version_->Ref();
  // 如果是level 0 则还需查找level 0中其他和输入文件重叠的文件
  if (level == 0) {
    GetRange(c->inputs_[0], &smallest, &largest);
    current_->GetOverlappingInputs(0, &smallest, &largest, &c->inputs_[0]);
  }
  SetupOtherInputs(c); //尝试加入level中新的文件,条件为不再与level+1中新的文件重叠,这个函数已经分析
  return c;
}

选择好了需要进行Compaction的的文件以后,就该调用实际的Compaction过程了,我们来分析其逻辑,过程比较长但是只要仔细细心的阅读,其处理的逻辑并不复杂,主要是遍历所有输入文件,然后将相同的可以进行合并,以及删除一些无用的delete操作等。

Status DBImpl::DoCompactionWork(CompactionState* compact) {
    //将snapshot相关的内容记录到compact信息中
  if (snapshots_.empty()) {
    compact->smallest_snapshot = versions_->LastSequence();
  } else {
    compact->smallest_snapshot = snapshots_.oldest()->number_;
  }
  //遍历所有inputs文件
  Iterator* input = versions_->MakeInputIterator(compact->compaction);
  for (; input->Valid() && !shutting_down_.Acquire_Load(); ) {
    // 每次都判断如果有memtable 需要compact,先compact memtable
    if (has_imm_.NoBarrier_Load() != NULL) {
      if (imm_ != NULL) { 
        CompactMemTable();
        bg_cv_.SignalAll(); // Wakeup 等待空间的线程
      }
    }
    Slice key = input->key();
    if (compact->compaction->ShouldStopBefore(key) &&
        compact->builder != NULL) { //当前(level +1)生成的文件和level + 2中有过多的重叠
      status = FinishCompactionOutputFile(compact, input); //写当前文件到磁盘
      if (!status.ok()) {
        break;
      }
    }
    // Handle key/value, add to state, etc.
    bool drop = false;
    if (!ParseInternalKey(key, &ikey)) {
      // 解码错误,清除之前的状态
      current_user_key.clear();
      has_current_user_key = false;
      last_sequence_for_key = kMaxSequenceNumber;
    } else {
      if (!has_current_user_key ||
          user_comparator()->Compare(ikey.user_key,
                                     Slice(current_user_key)) != 0) {
        // 第一次出现的key,将seq设置为最大标记新key开始
        current_user_key.assign(ikey.user_key.data(), ikey.user_key.size());
        has_current_user_key = true;
        last_sequence_for_key = kMaxSequenceNumber;
      }
        //因为第一次出现会将last seq设置为最大,表示上一个key的关于seq的比较结束
      if (last_sequence_for_key <= compact->smallest_snapshot) {
        // Hidden by an newer entry for same user key
        drop = true; // (A)
      } else if (ikey.type == kTypeDeletion &&  
                 ikey.sequence <= compact->smallest_snapshot &&               //无snapshot引用
                 compact->compaction->IsBaseLevelForKey(ikey.user_key)) { //(1)
        // For this user key:
        // (1) there is no data in higher levels
        // 而我们知道在底层的文件中seq会更大,正在被compact的相同的key会稍后标记这个为删除(ruleA)
        drop = true;
      }
      last_sequence_for_key = ikey.sequence; 
    }

    if (!drop) {
      // 第一次进入compact或者上次文件刚刚写到磁盘,新建一个文件和table_builder
      if (compact->builder == NULL) {
        status = OpenCompactionOutputFile(compact);
        if (!status.ok()) {
          break;
        }
      }
    //新文件,记录当前key 为 整个文件的smallest
      if (compact->builder->NumEntries() == 0) {
        compact->current_output()->smallest.DecodeFrom(key);
      }
      //每遍历到一个就将其记录为largest
      compact->current_output()->largest.DecodeFrom(key);
      compact->builder->Add(key, input->value());
      // 超过level的阈值大小,将文件写到磁盘
      if (compact->builder->FileSize() >= compact->compaction->MaxOutputFileSize()) {
        status = FinishCompactionOutputFile(compact, input);
        if (!status.ok()) {
          break;
        }
      }
    }
    input->Next();
  }
//判断状态和将未写到磁盘的数据写入磁盘
  if (status.ok() && shutting_down_.Acquire_Load()) {
    status = Status::IOError("Deleting DB during compaction");
  }
  if (status.ok() && compact->builder != NULL) {
    status = FinishCompactionOutputFile(compact, input);
  }
  if (status.ok()) {
    status = input->status();
  }
  delete input;
  input = NULL;
  CompactionStats stats;
  stats.micros = env_->NowMicros() - start_micros - imm_micros;
  for (int which = 0; which < 2; which++) {//计算本次Compaction读入文件的总大小
    for (int i = 0; i < compact->compaction->num_input_files(which); i++) {
      stats.bytes_read += compact->compaction->input(which, i)->file_size;
    }
  }
  for (size_t i = 0; i < compact->outputs.size(); i++) {
    stats.bytes_written += compact->outputs[i].file_size;
  }//本次Compaction写出文件的总大小
  mutex_.Lock();
  stats_[compact->compaction->level() + 1].Add(stats);
  if (status.ok()) {//记录统计信息以及将Compaction导致的文件变动记录到versionedit中
    status = InstallCompactionResults(compact);
  }
  return status;
}

SSTable的Compaction就分析完了,关于Compaction还剩下MemTable的Compaction,或者也可以将其说明为Memtable的dump为SSTable。再分析完上面的SSTable Compaction后你就发现MemTable的Compaction是如此之简单了,我们简单罗列一下

void DBImpl::CompactMemTable() {
  Status s = WriteLevel0Table(imm_, &edit, base);
  // 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_);
  }
  if (s.ok()) {
    // Commit to the new state
    imm_->Unref();
    imm_ = NULL;
    has_imm_.Release_Store(NULL);
    DeleteObsoleteFiles();
  } else {
    RecordBackgroundError(s);
  }
}

这个逻辑中就一个主要的函数WriteLevel0Table,其流程如下:

Status DBImpl::WriteLevel0Table(MemTable* mem, VersionEdit* edit, Version* base) { 
  meta.number = versions_->NewFileNumber();
  pending_outputs_.insert(meta.number);
  Iterator* iter = mem->NewIterator();
  //新生成一个Table_builder负责写文件
    s = BuildTable(dbname_, env_, options_, table_cache_, iter, &meta);

  // Note that if file_size is zero, the file has been deleted and 
  // should not be added to the manifest.
  int level = 0;
  if (s.ok() && meta.file_size > 0) {
    const Slice min_user_key = meta.smallest.user_key();
    const Slice max_user_key = meta.largest.user_key();
    if (base != NULL) {
      /* 找到一个当层未overlap 且上册overlap 不会过多(kMaxGrandParentOverlapBytes)的层返回*/ 
      level = base->PickLevelForMemTableOutput(min_user_key, max_user_key);
    }
    //将文件加到versionedit中
    edit->AddFile(level, meta.number, meta.file_size,
                  meta.smallest, meta.largest);
  }
  CompactionStats stats;
  stats.micros = env_->NowMicros() - start_micros;
  stats.bytes_written = meta.file_size;
  stats_[level].Add(stats);
  return s;
}

这里有一个唯一需要注意的是——将Memtable dump到磁盘以后并不是如文档描述的“将新的SSTable加到level 0中.”,而是会用一个函数PickLevelForMemTableOutput选择一个最高的可以将这个SSTable放入的level中。一般来说会是level 0,但是还是存在一些特殊情况可以将其放到更高的level中,这样可以降低Compaction的频率。PickLevelForMemTableOutput的逻辑简单,请读者自行阅读。

至此comaction流程相关的函数就分析完了,本节内容比较多,但是只要静下心来慢慢品读理解还是不难的。至此leveldb中剩下的还有recover,new (新建一个数据库)、snapshot、get相关的代码没有分析了。我们在compaction的分析过程中涉及到了很多有关version的类、方法、结构,leveldb的vesion是整个系统极其重要的一环,而且recovery,snapshot,get在一定程度上都会依赖于version的实现,所以接下来的文章准备对version相关的内容进行介绍。敬请期待……

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

leveldb源码分析--SSTable之Compaction SetupOtherInputs 的相关文章

  • leveldb之log文件

    leveldb之log文件 1 log文件在LevelDb中的主要作用是系统故障恢复时 xff0c 能够保证不会丢失数据 因为在将记录写入内存的Memtable之前 xff0c 会先写入Log文件 xff0c 这样即使系统发生故障 xff0
  • leveldb性能调优

    许多的nosql都使用leveldb或者类似leveldb的系统作为存储引擎 xff0c 例如tair xff0c hbase xff0c canssandra xff0c 因此理解并调优存储引擎可以大大的提高系统的性能 前一篇大致介绍了原
  • leveldb之log文件

    leveldb之log文件 1 log文件在LevelDb中的主要作用是系统故障恢复时 xff0c 能够保证不会丢失数据 因为在将记录写入内存的Memtable之前 xff0c 会先写入Log文件 xff0c 这样即使系统发生故障 xff0
  • 【数据库】Redis和RocksDB、levelDB的区别

    区别 Redis 是一个服务 xff0c 独立的进程 xff0c 用户的程序需要与它建立连接才能向它发请求 xff0c 读写数据 RocksDB 和LevelDB 是一个库 xff0c 嵌入在用户的程序中 xff0c 用户程序直接调用接口读
  • leveldb源码分析--SSTable之Compaction 详解

    http www cnblogs com KevinT p 3819134 html leveldb源码分析 SSTable之Compaction 对于compaction是leveldb中体量最大的一部分 也应该是最为复杂的部分 为了便于
  • LSM树(Log-Structured Merge Tree)存储引擎

    LSM树 Log Structured Merge Tree 存储引擎 代表数据库 nessDB leveldb Hbase等 核心思想的核心就是放弃部分读能力 换取写入的最大化能力 LSM Tree 这个概念就是结构化合并树的意思 它的核
  • 键值数据库PebblesDB读后感

    键值数据库PebblesDB读后感 在LevelDB RocksDB这种分层思路上 PebblesDB提出了一种减少写放大的思路 下面学习并总结 所述以论文为基础 也有个人 观点 客观论述请看原文 虽然LSM的写放大最近被研究很多 但是就写
  • LevelDB源码阅读-key

    levelDB中的key 前言 在levelDB中有五种不同的key 在正式分析memtable之前我们先介绍一下这5中不同的key user key ParsedInternalKey InternalKey LookupKey Memt
  • LevelDb之七:根据Key读取记录

    LevelDb之七 根据Key读取记录 2012 09 08 17 54 41 分类 云计算 LevelDb是针对大规模Key Value数据的单机存储库 从应用的角度来看 LevelDb就是一个存储工具 而作为称职的存储工具 常见的调用接
  • leveldb之Compaction操作下之具体实现

    leveldb之Compaction操作下之具体实现 2015 05 17 19 40 438人阅读 评论 0 收藏 举报 分类 leveldb 13 版权声明 本文为博主原创文章 未经博主允许不得转载 目录 由上文可知 合并主要分为三种
  • Oceanbase列传

    Oceanbase列传 分布式与存储技术 跳至内容 首页 关于郁白 文章列表 文章预告 正在追越狱第五季 两阶段提交的工程实践 两阶段提交 2 Phase Commit简称2PC 协议是用于在多个节点之间达成一致的通信协议 它是实现 有状态
  • leveldb注释7–key与value

    作为一个kv的系统 key的存储至关重要 在leveldb中 主要涉及到如下几个key user key InternalKey与LookupKey memtable key 其关系构成如下图 user key就是用户输入的key 而Int
  • vcpkg编译第三方库leveldb

    vcpkg编译leveldb 1 安装vcpkg 使用git命令直接pull vcpkg源码 git clone https github com microsoft vcpkg 2 在vcpkg目录执行bootstrap vcpkg ba
  • okyo Cabinet简介

    http idning github io ssd cache html http blog 163 com zbr 4690 blog static 126613593200910312346337 http blog chinaunix
  • Log Structured Merge Trees(LSM) 原理

    Log Structured Merge Trees LSM 原理 十年前 谷歌发表了 BigTable 的论文 论文中很多很酷的方面之一就是它所使用的文件组织方式 这个方法更一般的名字叫 Log Structured Merge Tree
  • 怎样打造一个分布式数据库——rocksDB, raft, mvcc,本质上是为了解决跨数据中心的复制

    怎样打造一个分布式数据库 rocksDB raft mvcc 本质上是为了解决跨数据中心的复制 摘自 http www infoq com cn articles how to build a distributed database ut
  • mmap 返回无法分配内存,即使有足够的内存

    我正在使用 leveldb 进行压力测试 In util env poisx cc NewRandomAccessFile void base mmap NULL size PROT READ MAP SHARED fd 0 插入 300
  • LevelDB 与 std::map

    在我们的应用程序中我们使用std map存储 键 值 数据并使用序列化将该数据存储在磁盘上 通过这种方法 我们发现磁盘 I O 是性能瓶颈 并且使用 key 查找值并不是很快 我遇到过 LevelDB 并考虑使用它 但我有一些问题 Leve
  • C# 是否有一个好的 leveldb 端口? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我希望在我的纯 C 项目中使用 leveldb 我在 google 上搜索了 leveldb 的 C 版本 但没有找到 谁能告诉我在哪里可
  • g++ 找不到标头,但我确实包含了它们

    我开始使用 c 并且已经出错了 我正在尝试编译 levelDB 的一个小测试 include

随机推荐

  • 14-1_Qt 5.9 C++开发指南_网络编程及主机信息查询_HostInfo

    Qt 网络模块提供了用于编写 TCP IP 客户端和服务器端程序的各种类 如用于 TCP 通信的QTcpSocket 和 QTcpServer 用于 UDP 通信的 QUdpSocket 还有用于实现 HTTP FTP 等普通网络协议的高级
  • Flex4 Error #2032 Stream Error的解决方式

    最近在做一个项目 在程序发布的初期没有发现什么问题 但是有的用户反映看不到站点 并截图Error 2032错误 但是在研发中心测试没有问题 后来通过测试幸运地在一台测试机上发现了这个问题 而同时测试其他9台机器 发现都可以正常显示 而后到网
  • C语言中void*详解及应用

    void在英文中作为名词的解释为 空虚 空间 空隙 而在C语言中 void被翻译为 无类型 相应的void 为 无类型指针 void似乎只有 注释 和限制程序的作用 当然 这里的 注释 不是为我们人提供注释 而是为编译器提供一种所谓的注释
  • 开源毕业设计:基于嵌入式ARM-Linux的应用OpenCV和QT实现的人脸识别系统(源码+论文)

    毕业一载有余 把毕业设计作品分享一下 希望能帮助到有需要的同学们 资料获取 帮助 答疑 辅导 等请联系博主 请点如下链接 linux face txt zengzr share contact Gitee com 毕设课题选题参考 毕业设计
  • 在WIN10上用QT Creator写安卓APP

    操作系统 WIN 10 HOME QT 5 15 QT Creator 4 12 先讲一下踩过的坑 坑 本意用 虚拟机好复制移植 用了vm14 结果发现报错 adb fail to install 以为是QT的问题 结果不是 因为直接用指令
  • python字符串替换空格_Python去除、替换字符串空格的处理方法

    个人想到的解决方法有两种 一种是 replace old new 第一个参数是需要换掉的内容比如空格 第二个是替换成的内容 可以把字符串中的空格全部替换掉 第二种方法是像这样 str 1 data a b c str 2 list str
  • 用海伦公式求三角形周长与面积 C++

    海伦公式又译作希伦公式 海龙公式 希罗公式 海伦 秦九韶公式 它是利用三角形的三条边的边长直接求三角形面积的公式 表达式为 S p p a p b p c 其中p等于周长的一半 给出平面坐标上不在一条直线上三个点坐标 x1 y1 x2 y2
  • Netty (3)-ByteBuf、池、直接内存、16进制

    传统IO在收发数据时 会阻塞当前线程 一边接收数据 一边对数据进行处理 处理完一段数据再继续接收下一段 再处理 而NIO会一次性将接收的所有数据 放入内存 处理数据时只需要读取内存 而IO线程被完全释放 这就是非阻塞 而被放入内存的数据在
  • 最通透的KMP算法详解

    前言 以前自己写一个字符串匹配或者主串中查找子串的程序时 都是用一个指针指向主串 另一个指针指向子串 然后两指针按字母逐一比较 看着自己写的代码运行一切正常时还沾沾自喜 现在想来 虽然这种方法也行的通 但是当字符串足够长时 效率会很低 自从
  • ubuntu下安装apache2.2+mod_wsgi+django(一)

    http blog csdn net huangxiansheng1980 article details 7202319 为了让apache或者nginx或者lighthttpd支持python可以用mod python的方式 但是由于m
  • C++数据结构笔记(9)树与二叉树的基本概念

    1 只有一个结点也可以称为树 只不过没有叶子结点 也可以有0个结点 称为空树 2 树具有递归性 树中还有树 3 结点的度 结点所拥有的子树的个数 4 树的高度 树的子树的最高层数 5 树的广义表示法 软件学院 软件开发 移动互联 大数据 人
  • 深入Linux内核(内存篇)—页表映射分页

    深入Linux内核 内存篇 页表映射 一 分页 1 1 页表存在哪里 1 2 页表长啥样 1 3 分页机制如何完成进程地址空间切换 1 4 实际使用的分页机制 1 5 多级页表的缺点 1 6 Translation Lookside Buf
  • MySQL基础篇-第04章_运算符

    第04章 运算符 讲师 尚硅谷 宋红康 江湖人称 康师傅 官网 http www atguigu com 1 算术运算符 算术运算符主要用于数学运算 其可以连接运算符前后的两个数值或表达式 对数值或表达式进行加 减 乘 除 和取模 运算 1
  • 慢SQL,压垮团队的最后一根稻草!

    一 什么是慢 SQL 什么是慢SQL 顾名思义 运行时间较长的 SQL 语句即为慢 SQL 那问题来了 多久才算慢呢 这个慢其实是一个相对值 不同的业务场景下 标准要求是不一样的 我们都知道 我们每执行一次 SQL 数据库除了会返回执行结果
  • 前端多媒体小知识之:FFmpeg介绍

    是什么 Fast forward moving picture expert groups A complete cross platform solution to record convert and stream audio and
  • ajax conp,GitHub - soraino/v-autosuggest: A simple modular Vuejs component that autosuggest input fr...

    v autosuggest A simple modular Vuejs component that autosuggest input from a dyanamic or static data querying Table of c
  • 2014复旦机试

    1 二分查找 大家一定都能熟练掌握二分查找啦 那么来计算二分的次数吧 约定二分的中点mid left right 2 输入 第一行输入一个整数N N lt 10000 第二行输入N个升序整数 第三行输入一个待查找的整数 必定在第二行中出现过
  • 爬坑记录

    1 vue循环中的input 每输入一次都会失去焦点需要再次点击才能获取焦点 v for里面的key设置的不是固定值 是会跟随文本框变化而变化的 因为key 变化了 所以才引发了dom的重新渲染 把key设置成其他不会随其他变量改变的值就可
  • canvas详解08-基本动画

    由于我们是用 JavaScript 去操控 canvas 对象 这样要实现一些交互动画也是相当容易的 在本章中 我们将看看如何做一些基本的动画 可能最大的限制就是图像一旦绘制出来 它就是一直保持那样了 如果需要移动它 我们不得不对所有东西
  • leveldb源码分析--SSTable之Compaction SetupOtherInputs

    leveldb源码分析 SSTable之Compaction 对于compaction是leveldb中体量最大的一部分 也应该是最为复杂的部分 为了便于理解我们首先从一些基本的概念开始 下面是一些从doc impl html中翻译和整理的