LevelDB 存储格式 —— MemTable

    MemTable是leveldb的重要组件,写链路的数据都是直接写入MemTable的,而且查询链路也是首先查找MemTable的数据,因此MemTable的读写性能就十分关键了。leveldb中使用了SkipList作为MemTable,本篇文章主要讲解leveldb中MemTable的数据写入和查询,及其对应的Iterator的实现。

SkipList

    跳表是常见的加速查询的数据结构,如图1-1所示展示了一个含有4个节点的跳表:

图 1-1 跳表

这个跳表的最大高度是6,即从下往上分为6层,每一层可看作是一个跳表。查找时依次从最高层开始,一旦确定待查找的key不在这一层了之后,马上从下一层开始查找。直到找到最底层为止,返回查找的结果,或者返回不存在。

跳表特性如下:

  • 每一层都是一个有序跳表
  • 一旦第i层有这个节点,那么其下的每一层也都有这个节点
  • 最底层包含跳表的所有元素

SkipList的查找

跳表的查找无论找到与否,都要从最高层一直查到最底层。

在跳表中查找target key所在的节点:

首先在最高层链表中查找,找到最后一个< target_key的节点;然后去往该节点的下一层,继续查找< target_key的节点。直到最底层为止,在最底层找到最后一个< target_key的节点后,如果该节点的next节点 == target_key,那么就返回这个next节点,否则就返回不存在。

以图1-1为例,假如查找的target_key为34,查找流程如下:

  1. 在第六层中查找,找到最后一个 < target_key的节点,即24
  2. 向下一层到达第5层,找到最后一个 < target_key的节点,依然是24
  3. 向下一层到达第4层,找到最后一个 < target_key的节点,依然是24
  4. 向下一层到达第3层,找到最后一个 < target_key的节点,依然是24
  5. 向下一层到达第2层,找到最后一个 < target_key的节点,找到了30
  6. 向下一层到达第1层,找到最后一个 < target_key的节点,依然是30。找到第一层中节点30的next节点——67,说明target_key34不存在

跳表能有效第加速查询的性能,这个例子中的节点数较少,效果不明显。当节点数较多的时候,跳表比单层有序链表的查找快多了,效率跟AVL树和红黑树差不多

SkipList的插入

在跳表中插入节点的流程跟跳表的查找过程类似,在插入一个节点的时候它的高度是随机生成的,即跳表有一个最大高度MAX_HEIGHT,插入的每个节点在MAX_HEIGHT的范围内随机生成一个高度h,以插入34这个节点为例,假设该节点随机生成的高度为3

  1. 从最高层开始首先查找34,主要目的是找到每一层34节点的prev节点是谁
  2. 在第六层中查找,找到最后一个 < target_key的节点,即24,记下这个节点
  3. 向下一层到达第5层,找到最后一个 < target_key的节点,依然是24,记下这个节点
  4. 向下一层到达第4层,找到最后一个 < target_key的节点,依然是24,记下这个节点
  5. 向下一层到达第3层,找到最后一个 < target_key的节点,依然是24,记下这个节点
  6. 向下一层到达第2层,找到最后一个 < target_key的节点,即30,记下这个节点
  7. 向下一层到达第1层,找到最后一个 < target_key的节点,依然是30,记下这个节点
  8. 由于待插入的节点高度为3,从第一层开始一次插入这个节点,这就用到了之前记录的每一层中待插入节点的prev节点,第一层的prev节点是30,因此在prev节点后插入新节点34即可
  9. 依此类推,一直到第3层,在之前所记录的每层的prev节点后插入新节点

可以看到,插入新节点的过程依然是查找的过程,唯一不同的是在每一层的查找中,记录了该层待插入节点的prev节点,即 小于 待插入节点的最后一个节点。然后根据待插入节点的高度,再在相应层中插入这个节点即可。

SkipList源码解析

在简单介绍了跳表原理后,下面来看下跳表在leveldb中的实现

template <typename Key, class Comparator>
class SkipList {
 private:
  struct Node;

 public:
  explicit SkipList(Comparator cmp, Arena* arena);
  void Insert(const Key& key);
  bool Contains(const Key& key) const;
  class Iterator;
 private:
  enum { kMaxHeight = 12 };
  Node* NewNode(const Key& key, int height);
  int RandomHeight();
  bool Equal(const Key& a, const Key& b) const { return (compare_(a, b) == 0); }

  // Return whether @key > @Node n
  bool KeyIsAfterNode(const Key& key, Node* n) const;

  // Return the earliest node that comes at or after key. return null if no such node
  Node* FindGreaterOrEqual(const Key& key, Node** prev) const;

  // Return the latest node with a key < key. return head_ if no such node
  Node* FindLessThan(const Key& key) const;

  // return the last node of SkipList
  Node* FindLast() const;

  Comparator const compare_;
  Arena* const arena_;  // Arena used for allocations of nodes

  Node* const head_;

  // Modified only by Insert().
  std::atomic<int> max_height_;
};

可以看到leveldb中跳表最大高度为12,插入一个新节点时,该节点高度也在1~12之间随机。SkipList主要成员就是一个head_节点和max_height_. 其核心函数只有2个——FindGreaterOrEqualFindLessThan,前者如同之前所述,用于查找过程和插入的过程。后者主要是给Iterator的Prev用的,leveldb中跳表都是单向的,每个节点没有记录prev节点,可能是为了代码实现简单吧。

下面来看跳表节点的结构:

template <typename Key, class Comparator>
struct SkipList<Key, Comparator>::Node {
  explicit Node(const Key& k) : key(k) {}

  // Accessors/mutators for links.  Wrapped in methods so we can
  // add the appropriate barriers as necessary.
  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) {
    assert(n >= 0);
    // Use a 'release store' so that anybody who reads through this
    // pointer observes a fully initialized version of the inserted node.
    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) {
    assert(n >= 0);
    return next_[n].load(std::memory_order_relaxed);
  }
  void NoBarrier_SetNext(int n, Node* x) {
    assert(n >= 0);
    next_[n].store(x, std::memory_order_relaxed);
  }
 public:
  Key const key;
 private:
  // Array of length equal to the node height.  next_[0] is lowest level link.
  std::atomic<Node*> next_[1];
};

每个节点用一个next_数组来保存其每一层的next节点,数组长度就是该节点的高度,即从实现来看,每个节点的next_数组会指向不同层的next节点。

FindGreaterOrEqual

下面重点来看下FindGreaterOrEqual(const Key& key, Node** prev)函数,该函数主要作用是找出跳表中第一个>= key的节点:

template <typename Key, class Comparator>
typename SkipList<Key, Comparator>::Node*
SkipList<Key, Comparator>::FindGreaterOrEqual(const Key& key, Node** prev) const {
  Node* current = head_;
  int level = GetMaxHeight() - 1;
  while (true) {
    Node* next = current->Next(level);
    if (KeyIsAfterNode(key, next)) {
      // Keep searching in this list
      current = next;
    } else {
      if (prev != nullptr) prev[level] = current;
      if (level == 0) {
        return next;
      } else {
        // Switch to next list
        level--;
      }
    }
  }
}

第4-5行从当前跳表最高层的头节点开始遍历找出<= key的最后一个节点. 第7-8行读取当前层中current的下一个节点,并与key作比较,若next < key,则current向后移一位,直到next >= key,说明在当前层中,current是最后一个小于key的节点了,然后将current指针保存在prev[level]数组中. 若当前level已经是最底层,则next节点就是>= key的第一个节点了,否则向下移动一层继续查找。

FindLessThan

再来看FindLessThan(const Key& key)函数:

template <typename Key, class Comparator>
typename SkipList<Key, Comparator>::Node*
SkipList<Key, Comparator>::FindLessThan(const Key& key) const {
  Node* current = head_;
  int level = GetMaxHeight() - 1;
  while (true) {
    assert(current == head_ || compare_(current->key, key) < 0);
    Node* next = x->Next(level);
    if (next != nullptr && compare_(next->key, key) < 0) {
      current = next;
    } else {
      if (level == 0) {
        return current;
      } else {
        // Switch to next list
        level--;
      }
    }
  }
}

FindLessThan函数与FindGreaterOrEqual几乎一模一样,只不过FindLessThan返回的是current节点,即当前跳表中< key的最后一个节点。

Insert

最后来看一下跳表的插入吧:

template <typename Key, class Comparator>
void SkipList<Key, Comparator>::Insert(const Key& key) {
  Node* prev[kMaxHeight];
  Node* x = FindGreaterOrEqual(key, prev);

  // Our data structure does not allow duplicate insertion
  assert(x == nullptr || !Equal(key, x->key));

  int height = RandomHeight();
  if (height > GetMaxHeight()) {
    for (int i = GetMaxHeight(); i < height; i++) {
      prev[i] = head_;
    }
    max_height_.store(height, std::memory_order_relaxed);
  }

  x = NewNode(key, height);
  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].
    x->NoBarrier_SetNext(i, prev[i]->NoBarrier_Next(i));
    prev[i]->SetNext(i, x);
  }
}

第3行创建了一个大小为kMaxHeightNode* prev[kMaxHeight]数组,只后第4行调用FindGreaterOrEqual找到每一层中< key的最后一个节点,填充到prev数组中。然后第9行随机生成待插入的新节点的高度,如果新节点的高度超过了跳表当前的max_height_,那么第10~15行将prev数组中超过的部分都置为head_节点,然后设置好max_height_的新值. 第17行生成新的节点,第18~23行就是将新节点插入到每一层的跳表中了.

NewNode

其中第17行NewNode函数会接收keyheight来生成一个新节点,通过之前的描述,我们知道一个Node内部包含了key和一个next_数组,我们来看下NewNode主要是怎样创建节点的吧:

template <typename Key, class Comparator>
typename SkipList<Key, Comparator>::Node* SkipList<Key, Comparator>::NewNode(
    const Key& key, int height) {
  char* const node_memory = arena_->AllocateAligned(
      sizeof(Node) + sizeof(std::atomic<Node*>) * (height - 1));
  return new (node_memory) Node(key);
}

首先计算该Node一共需要多大的内存空间,即key的大小 + next_数组的大小(next_数组的长度是这个节点的高度),分配好这段内存buffer之后,通过placement new生成这个Node对象.

Contains

跳表判断指定的key是否存在也就变的显而易见了:

template <typename Key, class Comparator>
bool SkipList<Key, Comparator>::Contains(const Key& key) const {
  Node* x = FindGreaterOrEqual(key, nullptr);
  if (x != nullptr && Equal(key, x->key)) {
    return true;
  } else {
    return false;
  }
}

找到第一个>= key的节点,然后判断该节点的key是否和待查找的key相等。

查找SkipList

    levedb对跳表的查询都是通过创建跳表的Iterator来查询的,其实有了上述的那些函数后,跳表Iterator的实现也就非常简单了。

class Iterator {
   public:
    // Initialize an iterator over the specified list.
    // The returned iterator is not valid.
    explicit Iterator(const SkipList* list) {
      list_ = list;
      node_ = nullptr;
    }

    // Returns true iff the iterator is positioned at a valid node.
    bool Valid() const {
      return node_ != nullptr;
    }

    // Returns the key at the current position.
    const Key& key() const {
      assert(Valid());
      return node_->key;
    }

    // Advances to the next position.
    void Next() {
      assert(Valid());
      node_ = node_->Next(0);
    }

    // Advances to the previous position.
    void Prev() {
      assert(Valid());
      node_ = list_->FindLessThan(node_->key);
      if (node_ == list_->head_) {
        node_ = nullptr;
      }
    }

    // Advance to the first entry with a key >= target
    void Seek(const Key& target) {
      node_ = list_->FindGreaterOrEqual(target, nullptr);
    }

    // Position at the first entry in list.
    // Final state of iterator is Valid() iff list is not empty.
    void SeekToFirst() {
      node_ = list_->head_->Next(0);
    }

    // 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_;
    // Intentionally copyable
  };

Iterator持有了一个SkipList*指针和当前所遍历到的Node*指针,其成员函数的实现主要都依赖于FindGreaterOrEqualFindLessThan.


    leveldb SkipList的实现如上所述,还是比较简单的,leveldb对MemTable的操作不是直接操作SkipList,而是在其之上又封装了一层MemTable的类

MemTable

    leveldb的MemTable就是对SkipList的简单封装,对外提供了写入和查找两个接口,以及数据Iterator的封装.

class MemTable {
 public:
  // MemTables are reference counted.  The initial reference count
  // is zero and the caller must call Ref() at least once.
  explicit MemTable(const InternalKeyComparator& comparator);

  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.
  void Add(SequenceNumber seq, ValueType type, const Slice& key,
           const Slice& value);

  // If memtable contains a value for key, store it in *value and return true.
  // If memtable contains a deletion for key, store a NotFound() error
  // in *status and return true.
  // Else, return false.
  bool Get(const LookupKey& key, std::string* value, Status* s);

 private:
  friend class MemTableIterator;

  struct KeyComparator {
    const InternalKeyComparator comparator;
    explicit KeyComparator(const InternalKeyComparator& c) : comparator(c) {}
    int operator()(const char* a, const char* b) const;
  };

  using Table = SkipList<const char*, KeyComparator>;
  KeyComparator comparator_;
  int refs_;
  Arena arena_;
  Table table_;
};

MemTable持有了一个SkipList<const char*, KeyComparator>对象,其写入数据,读取数据的操作都是借助于SkipList提供的方法来完成的。这里有必要先介绍一下SkipList存储的key的结构。

InternalKey

    SkipList不是存用户裸的key,而是存储一个叫作InternalKey的结构:

class InternalKey {
 private:
  std::string rep_;
 public:
  Slice user_key() const { return ExtractUserKey(rep_); }
};

Slice ExtractUserKey(const Slice& internal_key) {
  assert(internal_key.size() >= 8);
  return Slice(internal_key.data(), internal_key.size() - 8);
}

InternalKey由3部分组成:user_key + seq_num + value_type

其中seq_num + value_type合起来是8个bytes(64位),seq_num占高56位,value_type在低8位,

value_type只有如下两种类型:enum ValueType { kTypeDeletion = 0x0, kTypeValue = 0x1 };

InternalKeyComparator

    leveldb内部大量使用了InternalKeyComparator用来比较InternalKey,其比较逻辑是提取出其中的user_key,先比较user_key,若user_key相同的话,那么再按照desc order比较InternalKey的后8个bytes(即seq_num+value_type),后8个bytes越大的话,则比较结果越小.

// Compare user_key then seq number
int InternalKeyComparator::Compare(const Slice& akey, const Slice& bkey) const {
  // Order by:
  //    increasing user key (according to user-supplied comparator)
  //    decreasing sequence number
  //    decreasing type (though sequence# should be enough to disambiguate)
  int r = user_comparator_->Compare(ExtractUserKey(akey), ExtractUserKey(bkey));
  if (r == 0) {
    const uint64_t anum = DecodeFixed64(akey.data() + akey.size() - 8);
    const uint64_t bnum = DecodeFixed64(bkey.data() + bkey.size() - 8);
    if (anum > bnum) {
      r = -1;
    } else if (anum < bnum) {
      r = +1;
    }
  }
  return r;
}

但是看到MemTable的成员中使用的跳表类型是SkipList<const char*, KeyComparator>,这里就有点纳闷了,跳表key的类型是const char*,那么在比较key的时候怎么比较两个char*的大小呢?const char*里存的又是什么东西呢?

SkipList的数据格式

    下面就来揭秘SkipList里存的是什么数据。来看MemTable是怎样往跳表中写入数据的:

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()]
  //  tag          : uint64((sequence << 8) | type)
  //  value_size   : varint32 of value.size()
  //  value bytes  : char[value.size()]
  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);
  std::memcpy(p, key.data(), key_size);
  p += key_size;
  EncodeFixed64(p, (s << 8) | type);
  p += 8;
  p = EncodeVarint32(p, val_size);
  std::memcpy(p, value.data(), val_size);
  assert(p + val_size == buf + encoded_len);
  table_.Insert(buf);
}

可以看到每次往跳表中写入一个kv-pair的时候,直接申请了一整块buffer,然后序列化key和value的值,一个kv-pair的内存布局如下:

图 2-1 leveldb SkipList数据格式

如图2-1所示,在往MemTable中插入数据的时候,会申请一整块buffer来连续存放InternalKeyValue.这一整块buffer存放的key和value都是PrefixedSlice,即头部是后续数据的大小,然后是后续的数据,在解析的时候先读取并解析头部size的值,然后根据这个size的值就能知道后续的数据有多长了。第24行看到会往跳表中插入一个const char*的指针,即这一块buffer的头部指针,将这一个char*指针作为跳表的key,那么在跳表内部比较两个key的时候必然会根据char*解析出PrefixedSlice所代表的InternalKey,然后比较这两个InternalKey值的,这个比较逻辑在MemTable::KeyComparator中:

class MemTable {
 private:  
  struct KeyComparator {
    const InternalKeyComparator comparator;
    explicit KeyComparator(const InternalKeyComparator& c) : comparator(c) {}
    int operator()(const char* a, const char* b) const {
      // Internal keys are encoded as length-prefixed strings.
      Slice a = GetLengthPrefixedSlice(aptr);
      Slice b = GetLengthPrefixedSlice(bptr);
 			return comparator.Compare(a, b);
    }
  };

 如第8~9行所示,先将PrefixedSlice解析出来,然后再调用InternalKeyComparator::Compare来比较两个InternalKey. 不知道leveldb为什么不直接将Slice作为SkipList的key呢,这样更直观,而且也不需要在比较时解出PrefixedSlice了...


最后来看MemTable的查询逻辑:

leveldb是通过MemTable::Get这个函数来查找MemTable中的kv-pair的

bool MemTable::Get(const LookupKey& key, std::string* value, Status* s);

    MemTable::Get这个函数的作用就是查找指定的key,然后将这个key对应的value设置在参数std::string* value中,输入的LookupKey这个结构其实就是一个PrefixedSlice的封装:

class LookupKey {
 public:
  // Initialize *this for looking up user_key at a snapshot with
  // the specified sequence number.
  LookupKey(const Slice& user_key, SequenceNumber sequence);

  // Return a key suitable for lookup in a MemTable.
  Slice memtable_key() const { return Slice(start_, end_ - start_); }

  // Return an internal key (suitable for passing to an internal iterator)
  Slice internal_key() const { return Slice(kstart_, end_ - kstart_); }

  // Return the user key
  Slice user_key() const { return Slice(kstart_, end_ - kstart_ - 8); }

 private:
  // We construct a char array of the form:
  //    klength  varint32               <-- start_
  //    userkey  char[klength]          <-- kstart_
  //    tag      uint64
  //                                    <-- 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
};

 这个结构底层依赖的数据就是一个InternalKey的PrefixedSlice,存放在start_指向的一块内存buffer中。分别提供了memtable_key, internal_keyuser_key三个方法来返回这块buffer中不同的区域。

bool MemTable::Get(const LookupKey& key, std::string* value, Status* s) {
  Slice memkey = key.memtable_key();
  Table::Iterator iter(&table_);
  iter.Seek(memkey.data());
  if (iter.Valid()) {
    // entry format is:
    //    klength  varint32
    //    userkey  char[klength - 8]
    //    tag      uint64
    //    vlength  varint32
    //    value    char[vlength]
    // 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();
    uint32_t key_length;
    const char* key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length);
    if (comparator_.comparator.user_comparator()->Compare(
            Slice(key_ptr, key_length - 8), key.user_key()) == 0) {
      // Correct user key
      const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
      switch (static_cast<ValueType>(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;
      }
    }
  }
  return false;
}

MemTable::Get查询的时候会临时构造一个SkipList::Iterator,然后去Seek传入的InternalKey,注:Seek的结果是找到第一个>= InternalKey的key. 第15~17行取出iter当前指向的key,将这个key的user_key部分与待查找的user_key作比较,如果相等则说明找到了这个key,由于Seek函数是定位到第一个>= InternalKey的key,如果此时user_key相等了,那么InternalKey的seq_num + type部分一定 >= 待查找的InternalKey,即此时命中的key中的seq_num一定<=待查找key的seq_num. 最后看命中的key的type是什么,如果是kTypeValue那么就真的找到了;否则,说明这个key已经在MemTable中被删除了

MemTable迭代器 

    最后来看下MemTable的迭代器吧,上层DB在查找MemTable的时候一般都是操作迭代器的

class MemTableIterator : public Iterator {
 public:
  explicit MemTableIterator(MemTable::Table* table) : iter_(table) {}

  MemTableIterator(const MemTableIterator&) = delete;
  MemTableIterator& operator=(const MemTableIterator&) = delete;

  ~MemTableIterator() override = default;

  bool Valid() const override { return iter_.Valid(); }
  void Seek(const Slice& k) override { iter_.Seek(EncodeKey(&tmp_, k)); }
  void SeekToFirst() override { iter_.SeekToFirst(); }
  void SeekToLast() override { iter_.SeekToLast(); }
  void Next() override { iter_.Next(); }
  void Prev() override { iter_.Prev(); }
  Slice key() const override { return GetLengthPrefixedSlice(iter_.key()); }
  Slice value() const override {
    Slice key_slice = GetLengthPrefixedSlice(iter_.key());
    return GetLengthPrefixedSlice(key_slice.data() + key_slice.size());
  }

 private:
  MemTable::Table::Iterator iter_;
  std::string tmp_;  // For passing to EncodeKey
};

    MemTableIterator是对SkipList::Iterator的简单封装,相应的方法都是直接调用SkipList::Iterator来完成的。特别注意的是SkipList::Iterator返回的key()const char*,而MemTableIterator是给上层DB直接使用的,因此上层需要的是InternalKey的结构,因此MemTableIterator::key()会将跳表返回的const char*做一次PrefixedSlice的解析,生成代表InternalKey的Slice然后返回。上一小节介绍了SkipList中存储的数据格式——紧接着InternalKey之后就是kv-pair的value部分了,因此MemTableIterator::value()方法先将InternalKey解出,然后再将key之后存储的value解出,然后返回。

    此外,MemTableIterator::Seek(const Slice& k)方法传入的是一个InternalKey的Slice,所以需要先将它转化成PrefixedSlice的const char*然后才能交给跳表去查找,因此第11行的EncodeKey函数就是将InternalKey转换为PrefixedSlice的:

// 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) {
  scratch->clear();
  PutVarint32(scratch, target.size());
  scratch->append(target.data(), target.size());
  return scratch->data();
}

其实现也一目了然,就是在头部拼上了key的size. 如果leveldb将Slice作为SkipList的key,那么就没这些事了。。同时也能降低查询开销,不然每次Seek的时候都得拼上size前缀,然后拷贝一段buffer数据,有点浪费。不知道leveldb未来会不会做这个优化。

结语

    以上就是leveldb MemTable的全部内容了,主要就是跳表的一些操作,还是比较简单的。基于跳表上层又实现了插入和查找数据的操作,以及MemTableIteratorSkipList::Iterator的封装。

    这篇文章是leveldb存储篇的完结,至此,leveldb数据存储结构就全部介绍完了。MemTable的结构加上前2篇SST文件的结构就组成了leveldb所有的数据存储结构,这是leveldb的基石,后续的数据链路都是在操作这些底层的存储结构。这3篇文章是对leveldb底层存储结构和存储格式的全面剖析,基本上对leveldb的存储做了透彻的解析,看完之后能够基本掌握leveldb的存储。

    下一篇文章会介绍leveldb的读写路径,敬请期待...

p.s. 开通微信个人公众号啦,会不定期更新一些大数据/数据库技术文章和个人思考等,欢迎关注微信公众号  搜索:一些次要时刻

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LevelDB 中,不可变 memtable(immutable memtable)的引入有以下几个原因: 1. 提高写入性能:LevelDB 使用了一种称为 Log-Structured Merge-Tree(LSM-Tree)的存储结构,其中包含了多个层级的数据文件。当写入操作发生时,数据首先会被写入 memtable,然后再根据一定的策略将 memtable 转化为不可变的 SST(Sorted String Table)文件。这样一来,写入操作就可以快速完成,因为不需要进行磁盘上的随机写入操作。而不可变 memtable 的引入,则是为了将写入操作转化为创建新的数据结构,避免频繁的数据移动和复制,进一步提高写入性能。 2. 提高读取性能:不可变 memtable 的存在使得读取操作可以并发地进行,而不需要等待写入操作完成。因为在不可变 memtable 创建期间,旧的 memtable 仍然可以用于读取操作。这种方式有效地解耦了读取和写入操作,提高了系统的并发性能。 3. 简化持久化:LevelDB 使用了基于日志的持久化机制,即将写入操作以日志的形式追加到磁盘上的文件中。当 memtable 转化为不可变的 SST 文件后,可以将其直接持久化到磁盘上,而不需要对其进行额外的复制和移动操作。这样可以简化持久化过程,提高系统的效率和可靠性。 因此,不可变 memtableLevelDB 中的引入是为了提高写入性能、并发读取操作以及简化持久化过程。它是 LevelDB 数据库引擎的重要组成部分,对于提高整体性能和效率非常关键。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值