Block
block 就是一个字节数组
1 2
   |  pub type Block = Bytes;
 
  | 
 
- blob block
 
- char block
 
- dict block
 
- nullable block
 
- primitive block
 
- rle block
 
RisingLight 提供了一些列 Block Builder trait 来构建 block,这些 build 都需要实现 BlockBuilder trait。BlockBuilder 定义了构建一个 block 需要实现的基本操作。除了 BlockBuilder trait,还定义了 NonNullableBlockBuilder trait 为不为空的类型的 block 定义了相关操作。
classDiagram
direction LR
  BlockBuilder <|.. PlainBlobBlockBuilder
  BlockBuilder <|.. PlainCharBlockBuilder
  BlockBuilder <|.. PlainPrimitiveBlockBuilder
  BlockBuilder <|.. NullableBlockBuilder
  BlockBuilder <|.. DictBlockBuilder
  BlockBuilder <|.. RelBlockBuilder
  NonNullableBlockBuilder <|.. PlainBlobBlockBuilder
  NonNullableBlockBuilder <|.. PlainCharBlockBuilder
  NonNullableBlockBuilder <|.. PlainPrimitiveBlockBuilder
  <<trait>> NonNullableBlockBuilder
  <<trait>> BlockBuilder
namespace trait {
  class BlockBuilder {
    + append(&mut self, item: Option<&A::Item>)
    + estimated_size(&self)  usize
    + get_statistics(&self)  Vec<BlockStatistics>
    + should_finish(&self, next_item: &Option<&A::Item>) bool
    + finish(self)  Vec<u8>
    + get_target_size(&self) usize
  }
  class NonNullableBlockBuilder {
    + append_value(&mut self, item: &A::Item)
    + append_default(&mut self)
    + get_statistics_with_bitmap(&self, selection: &BitVec<u8, Lsb0>) Vec<BlockStatistics>
    + estimated_size_with_next_item(&self, next_item: &Option<&A::Item>) usize
    + is_empty(&self) : bool
  }
}
namespace implement {
  class DictBlockBuilder {
  }
  class NullableBlockBuilder {
    - inner_builder: B,
    - bitmap: BitVec<u8, Lsb0>,
    - target_size: usize,
  }
  class PlainPrimitiveBlockBuilder {
    - data: Vec<u8>,
    - target_size: usize,
  }
  class PlainCharBlockBuilder {
    - data: Vec<u8>,
    - char_width: usize,
    - target_size: usize,
  }
  class PlainBlobBlockBuilder {
    - data: Vec<u8>,
    - offsets: Vec<u32>,
    - target_size: usize,
  }
  class RelBlockBuilder
}
一个编码后的 Block 的格式如下所示,其中 block_type, cksum_type, cksum 可以用 BlockMeta 结构表示
1 2
   | |    data     | block_type | cksum_type | cksum  | |  variable   |    4B      |     4B     |   8B   |
   | 
 
为了能够迭代 block,RisingLight 还定义了 BlockIterator 和 NonNullableBlockIterator trait 来指定一些操作
由于一个 Column 可能包含多个 block,为了能够快速访问 block,还定义了 BlockIndex 结构。
classDiagram
  class BlockIndex {
    + offset: u64,
    + length: u64,
    + first_rowid: u32,
    + row_count: u32,
    + first_key: ::prost::alloc::vec::Vec<u8>,
    + stats: ::prost::alloc::vec::Vec<BlockStatistics>,
    + is_first_key_null: bool,
  }
index block 的存储格式如下所示
1
   | | index | index | index | ... | magic number (4B) | block count (8B) | checksum type (4B) | checksum (8B) |
   | 
 
每生成一个 Block,就会构建一个 BlockIndex 结构,这个过程是在 BlockIndexBuilder#finish_block() 中实现的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
   |  pub fn finish_block(     &mut self,     block_type: BlockType,     column_data: &mut Vec<u8>,     block_data: &mut Vec<u8>,     stats: Vec<BlockStatistics>,     first_key: Option<Vec<u8>>, ) {     self.indexes.push(BlockIndex {          offset: column_data.len() as u64,         length: block_data.len() as u64 + BLOCK_META_SIZE as u64,         first_rowid: self.last_row_count as u32,         row_count: (self.row_count - self.last_row_count) as u32,                  is_first_key_null: first_key.is_none(),         first_key: first_key.unwrap_or_default(),         stats,     });
           self.last_row_count = self.row_count;
           self.block_header.resize(BLOCK_META_SIZE, 0);     let mut block_header_nonchecksum = &mut self.block_header[..BLOCK_META_NON_CHECKSUM_SIZE];
      let checksum_type = self.options.checksum_type;
      let mut header = BlockMeta {         block_type,         checksum_type,         checksum: 0,     };     header.encode_except_checksum(&mut block_header_nonchecksum);     debug_assert!(block_header_nonchecksum.is_empty());          block_data.extend_from_slice(&self.block_header[..BLOCK_META_NON_CHECKSUM_SIZE]);
           header.checksum = build_checksum(header.checksum_type, block_data);     let mut block_header_checksum = &mut self.block_header[BLOCK_META_NON_CHECKSUM_SIZE..];     header.encode_checksum(&mut block_header_checksum);      debug_assert!(block_header_checksum.is_empty());     block_data.extend_from_slice(&self.block_header[BLOCK_META_NON_CHECKSUM_SIZE..]);
           column_data.append(block_data); }
 
  | 
 
Column
和 Block 一样,构建 Column 时必须使用 Column Builder 来构建,不同类型的 Column 必须实现 ColumnBuilder trait。ColumnBuilder 接受一个泛型 A,其必须实现 Array trait,否则就不是一个合法的 column 类型
classDiagram
  direction LR
  ColumnBuilder <|.. BlobColumnBuilder
  ColumnBuilder <|.. CharColumnBuilder
  ColumnBuilder <|.. PrimitiveColumnBuilder
  <<trait>> ColumnBuilder
  class ColumnBuilder {
    + append(&mut self, array: &A)
    + finish(self)  (Vec<BlockIndex>, Vec<u8>)
  }
  class PrimitiveColumnBuilder {
    - data: Vec<u8>,
    - options: ColumnBuilderOptions,
    - current_builder: Option<BlockBuilderImpl<T>>,
    - nullable: bool,
    - block_index_builder: BlockIndexBuilder,
    - first_key: Option<Vec<u8>>,
    + new(nullable: bool, options: ColumnBuilderOptions) Self
    - finish_builder(&mut self)
  }
  class EncodedColumn {
    + index: Vec<u8>,
    + data: Vec<u8>,
  }
Column 表示 row 中的一个 attribute,当数据量很大时,数据会水平切分为 row groups(RisingLight 中使用术语 rowset), 在 row group 内部又会进行垂直切分,将row 拆分为 column。也就是说每个 row group 中的 column 都是一个单独的文件。而每个 column 文件又被划分为多个 block,为了快速访问 block,RisingLight 在 col 文件中存储了 index 结构。data + index 组成了 EncodedColumn 结构,这也是 column 在 col 文件中的存储的数据。需要注意的是,data 和 index 是分开存储的,即 data 对应一个 .col 文件,idx 对应一个 .idx 文件
RowSet
我们知道,一个 rowset 包含多个 column,为了识别 column,还需要存储一些其他的信息,比如 Column 的 scheme
classDiagram
  class EncodedRowset {
    + size: usize,
    + columns_info: Arc<[ColumnCatalog]>
    + columns: Vec<EncodedColumn>
    + cardinality(&self)  usize
    + is_empty(&self) bool
  }
需要注意的是并不需要将 column 的 scheme 存储在 col 或者 idx 文件中,RisingLight 选择使用 <column_id>.col/idx 对 col 或者 idx 文件命令(比如 1.col, 1.idx),这样就可以知道某个文件是 row 中的第几列了。
还有一点需要注意的是每个 rowset 都对应一个目录,其命名格式是 <table_id>_<rowset_id>,这样就能识别出某个目睹对应哪个 table 的那个 rowset 了。同时结合 col 和 idx 文件的命名,我们可以通过文件名唯一地确定 col 或者 idx
既然 col 和 rowset 都没有存储 column schemes 信息,那么就必然需要将这些信息存储到一个位置,否则当系统崩溃是就无法恢复数据了。RisingLight 选择将 column schems 信息存储到 manifest 文件中。manifest 是一个 json 文件,记录了所有操作(包括创建/修改表的 scheme 信息)。manifest 记录的信息示例如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | {   "CreateTable": {     "schema_id":0,     "table_name":"t2",     "column_descs": [       {         "id":0,         "desc": {           "datatype": {             "kind": "Int32",             "nullable":true           },           "name":"a",           "is_primary":false         }       },     ],     "ordered_pk_ids":[]   } } {"AddRowSet":{"table_id":{"schema_id":0,"table_id":1},"rowset_id":15}} {"AddDV":{"table_id":{"schema_id":0,"table_id":1},"dv_id":0,"rowset_id":15} {"DropTable":{"table_id":{"schema_id":0,"table_id":0}}} {"DeleteRowSet":{"table_id":{"schema_id":0,"table_id":0},"rowset_id":23}}
  | 
 
下图是 Insert 数据的存储过程(图源官方)
