Mini-LSM Week 1 Day3
Mini-LSM Week 1 Day3
Week1 Day3 的内容,实现 SST block 编解码和 iterator
Task 1: Block Builder
前两章实现了 LSM 在内存中的结构,现在实现 on-disk 的结构,即 Block 块。
Blocks 一般 4-KB 大小(可能因存储介质不同),和操作系统以及 SSD 中的页 page 大小一致。块存储排序后的 key-value pairs。
SST 由多个 blocks 组成,当 memtables 的数量超过了 system limit,就会 flush memtables 成为 SST。这一章实现 encoding and decoding of a block.
需要修改的文件:
src/block/builder.rs
src/block.rs
教程的 block 的编码格式为:
----------------------------------------------------------------------------------------------------
| Data Section | Offset Section | Extra |
----------------------------------------------------------------------------------------------------
| Entry #1 | Entry #2 | ... | Entry #N | Offset #1 | Offset #2 | ... | Offset #N | num_of_elements |
----------------------------------------------------------------------------------------------------
每个 Entry 是 key-value 对:
-----------------------------------------------------------------------
| Entry #1 | ... |
-----------------------------------------------------------------------
| key_len (2B) | key (keylen) | value_len (2B) | value (varlen) | ... |
-----------------------------------------------------------------------
key_len 和 value_len 都是 2 个字节,所以最长长度为 $2^16 = 65535$,一般使用 u16
表示
block 具有大小限制 target_size
,除非第一个 key-value pair 超过了 block size,不然你需要保证编码后的 block size 小于或等于 target_size
(提供的代码里,target_size
与 block_size
本质一样)
当 build
被调用时,BlockBuilder
会产生 data part 和 unencoded entry offsets。信息会被存在 Block
结构中,key-value entries 使用 raw 格式,offsets 用单独的 vector,这减少了解码数据时不必要的内存分配和处理开销,你只需要简单地拷贝 raw block data 到 data
vector 并且每 2 个 bytes 进行 decode entry offsets,而不是创建 Vec<(Vec<u8>, Vec<u8>)>
这样的结构,在一个 block 和内存中去存所有的 key-value pairs。这样 compact memory layout 更高效。
在 Block::encode
和 Block::decode
,你需要按照上述的结构 encode/decode block.
Task 1: Solution
查看 src/block/builder.rs
观察 Block 结构体,offsets Vec<u16>
偏移量, data vec<u8>
kv 数据等等,具有 new(block_size: usize) -> Self
, add(&mut self, key: KeySlice, value: &[u8]) -> bool
, is_empty(&self) -> bool
和 build(self) -> Block
方法。
/// Builds a block.
pub struct BlockBuilder {
/// Offsets of each key-value entries.
offsets: Vec<u16>,
/// All serialized key-value pairs in the block.
data: Vec<u8>,
/// The expected block size.
block_size: usize,
/// The first key in the block
first_key: KeyVec,
}
/// Creates a new block builder.
pub fn new(block_size: usize) -> Self {
Self {
offsets: Vec::new(),
data: Vec::new(),
block_size,
first_key: KeyVec::new(),
}
}
先实现 new(block_size)
方法创建一个 BlockBuilder
结构体,需要注意这里使用了 bytes::BufMut
库,BufMut
是 bytes
库中的一个 trait
,它定义了一些方法来操作可变的字节缓冲区。通过实现 BufMut,你可以为自定义类型添加一些方便的方法来操作字节数据。
BufMut trait
中定义了一个 put
方法,用于将数据写入缓冲区。这个方法可以用于多种类型,包括 u8
、&[u8]
、&str
等。此时 Vec
使用 put
也更合理,持批量写入缓冲区,减少内存复制。
然后实现 add
函数,向 Block 添加一个 key-value 值,首先需要知道数据的 offset 即存放的位置 self.data.len()
,然后按照格式 key_len key value_len value
存入数据。但需要注意判断当前编码后的 Block 是否超过了 block_size
(注意:除非第一个 key-value pair 超过了 block size):
/// Adds a key-value pair to the block. Returns false when the block is full.
#[must_use]
pub fn add(&mut self, key: KeySlice, value: &[u8]) -> bool {
// data: [u8] | offsets: [u16] | num of elements: u16
if !self.is_empty() && /* can store one large key */
self.data.len() + self.offsets.len() * 2 + 2 + /* current encode */
key.len() + 2 + value.len() + 2 + 2 /* key_len: 2b | key: keylen | value_len: 2b | value: val_len + offset */
> self.block_size
{
return false;
}
if self.offsets.is_empty() {
self.first_key = key.to_key_vec();
}
self.data.put_u16(key.len() as u16);
self.data.put(key.into_inner());
self.data.put_u16(value.len() as u16);
self.data.put(value);
self.offsets.push(self.data.len() as u16);
true
}
/// Check if there is no key-value pair in the block.
pub fn is_empty(&self) -> bool {
self.offsets.is_empty()
}
/// Finalize the block.
pub fn build(self) -> Block {
Block {
offsets: self.offsets,
data: self.data,
}
}
所以检查 Block::is_empty
是否为空就很简单了,只需要检查 offset
数组是不是空的,同时 build
就直接返回当前的 Block
带上 offset
和 data
然后实现 Block::encode
编码数据,按照 data offset
这种编码格式 encode,即教程中的 拷贝 raw block data 到 data vector 并且每 2 个 bytes 进行 decode entry offsets,由于此时的 data 是 Vec<u8>
类型,需要转成 Bytes
返回,可以直接使用 .into()
方法。但是教程提到只需要一个 Vec<u8>
,所以需要将 offsets
也塞到 data
里去:
impl Block {
/// Encode the internal data to the data layout illustrated in the tutorial
/// Note: You may want to recheck if any of the expected field is missing from your output
pub fn encode(&self) -> Bytes {
let mut buf = self.data.clone();
for offset in &self.offsets {
buf.put_u16(*offset);
}
// num of elements
buf.put_u16(self.offsets.len() as u16);
buf.into()
}
/// Decode from the data layout, transform the input `data` to a single `Block`
pub fn decode(data: &[u8]) -> Self {
let offsets_len = (&data[data.len() - 2..data.len()]).get_u16() as usize;
let offsets = (&data[data.len() - 2 - offsets_len * 2..data.len() - 2])
.chunks(2)
.map(|mut chunk| chunk.get_u16())
.collect();
let data = data[..data.len() - 2 - offsets_len * 2].to_vec();
Self { data, offsets }
}
}
相应的,decode
也是从一个 data: &[u8]
取得所有的 data
和相应的 offset
,从缓冲区最后一个 u16
可以获得 offset
数组的长度
代码里的所有 2 都是 u16 的长度,更规范应该使用常量来表示,比如
pub(crate) const SIZEOF_U16: usize = std::mem::size_of::<u16>();
Task 2: Block Iterator
修改 src/block/iterator.rs
,这一小节,因为有了 encoded block,需要实现 BlockIterator
接口,使得用户可以 lookup/scan blocks 里的 keys。
BlockIterator
可以被 Arc<Block>
实现,如果 create_and_seek_to_first
被调用,它会放在 block 的第一个 key。如果 create_and_seek_to_key
被调用,iterator 会被放在第一个 >=
大于等于相应 key 的位置,比如 1, 3, 5
在一个 Block 时
let mut iter = BlockIterator::create_and_seek_to_key(block, b"2"); // 创建 key 2
assert_eq!(iter.key(), b"3"); // 此时 iterator 位置在第一个大于等于 2 的位置即 3 的位置
上面的 seek 2
将使迭代器定位在下一个可用键 2
,在本例中为 3
。
iterator 应该从 block 拷贝 key
并且存到 iterator 本身(未来会有 key compression 压缩的内容),对于值 value,必须在 iterator 存储起始/结束 begin/end offset 偏移,并且不能拷贝。
当 next
被调用,iterator 会移动到下一个位置。如果抵达 block 结束位置,可以设置 key
为空然后从 is_valid
返回 false
,这样调用者可以切换到另外的 block。
Task 2: Solution
本节需要实现的函数较多,先观察 BlockIterator::new
构造函数,传入一个 Arc<Block>
,但观察测试,基本都没有使用这个构造函数而是使用 create_and_...
等函数。
所以 create_and_seek_to_first(block: Arc<Block>)
和 create_and_seek_to_key(block: Arc<Block>, key: KeySlice)
都接受一个原子计数引用,调用构造函数,调用相应函数,返回迭代器:
pub fn create_and_seek_to_first(block: Arc<Block>) -> Self {
let mut iter = Self::new(block);
iter.seek_to_first();
iter
}
/// Creates a block iterator and seek to the first key that >= `key`.
pub fn create_and_seek_to_key(block: Arc<Block>, key: KeySlice) -> Self {
let mut iter = Self::new(block);
iter.seek_to_key(key);
iter
}
对于 key
value
函数,直接返回当前的 entry
:
/// Returns the key of the current entry.
pub fn key(&self) -> KeySlice {
self.key.as_key_slice()
}
/// Returns the value of the current entry.
pub fn value(&self) -> &[u8] {
&self.block.data[self.value_range.0..self.value_range.1]
}
/// Returns true if the iterator is valid.
/// Note: You may want to make use of `key`
pub fn is_valid(&self) -> bool {
!self.key.is_empty()
}