阅读时间:2022.07 - Storage Engine 相关部分
===== 2.3 Data Storage
Data Storage 有3个核心设计思想:
- 存储计算分离(Decoupling of Storage/Computation)
- 基于 tablet 的 数据layout(Tablet-based Data Layout):table & index 都拆分到 tablet 级别来进行管理
- 分离读写路径(Separation of Reads/Writes)
===== 3. Storage
Hologres 支持 混合行列存储 以支持 HSAP 场景:
- Row storage 面向低延迟点查
- Column storage 面向高吞吐scan
// 从 paper 中看,这里的 「混合行列存储」是指引擎中既有行存又有列存,而并不是指一个持久化文件中是行列混存的
===== 3.1 Data Model
所有的 table 都被分组为 table group。每个 table group 被拆分为 table group shards(TGS),影响数据分布的两个配置:clustering key & distribution key
- distribution key:表示数据的分布策略,仅支持 hash 类型,Hash(distribution key) % shard_count。
- clustering key:数据在tablet的排序依据(默认为空,则按照插入顺序)。从索引的角度来讲,Hologres 仅支持一个 聚簇索引(就是基于 clustering key的索引),非聚簇索引要指向到 cluster key or row locator
Hologres 中每个table都有一个用户定义的 clustering key(默认为空)和一个unique row locator。如果 clustering key 是 unique 的,那么 row locator 就是 clustering key;否则 row locator 为 (clustering key, uniquifier)。
每个 TGS 包含每个table的 base data 以及 index data,将 base data partition & index partition 都统一看作是 tablet,tablet 有 row tablet & column tablet 两种format。
Tablet 是要求有 unique key 的:
- 对于 base data tablet,这个key就是 row locator
- 对于 index data,如果 index column 是 unique的,那么key就是index column;否则需要在 index column 后添加 row locator
比如一个table有两个index:一个unique index:col1,一个非unique index:col2,那么base data tablet 的 key 是 「row locator」,index1 的 key 是 「col1」,index2 的 key 是 「col2, row locator」
通过将 table 分组的方式来进行管理,用户可以将相关的table划分到一个 table group中,相关的写入操作只产生一条 wal log,降低 wal log量、消除不必要的shuffle。
===== 3.2 Table Group Shard
TGS 由一个 WAL Manager 和 属于该TGS的 多个tablet 组成。
每个 tablet 统一由 LSM tree 管理:
- 每个 tablet 包括 一个 MemTable 和 一些不可变的 shard file
- MemTable 定期flush成为一个 shard file
- Level0 中,每个 shard file 对应一个 flushed MemTable
- 从 Level1 开始,所有的 record 都是按照 key 有序排列的, 所有各个shard file之间是没有 overlap 的
- Leveli+1 的 shard file 数量是 Leveli的 K 倍,每个 shard file 最大为 M
每个 tablet 维护一个 metadata file 记录 shard file 的状态(类似 RocksDB)。
每个 tablet 仅支持单并发写入 ,但是可以支持多个 reader 读取。HSAP场景对 一致性 要求不高,所以仅支持 原子写 和 RYW 隔离
===== 3.2.1 Writes in TGSs
Hologres 支持两种类型写入:single-shard write & distributed batch write,两种写入都是原子的。single-shard write 仅支持一个 shard 写入,distributed batch write 则支持大量数据导入多个TGS。
Single-shard write流程:
- WAL Manager 为 写入请求 分配一个 LSN,这个LSN由 timestamp & 自增id 组成
- 创建一个 log entry,并将其持久化
- 数据写入 memory table,随即对其他请求可见
- 按需 flush & compaction
Distributed Batch Write,使用 两阶段提交 来保证写入原子性:
- FE 节点作为 协调者,锁定所有需要访问的 tablet
- 每个 TGS 执行写入:分配 LSN、flush memory table、加载数据并将其flush成 shard file
- TGS 向 FE 投票、FE收集投票决定 commit or abort
- FE 节点释放 tablet 锁
===== 3.2.2 Reads in TGSs
Hologres 保证 RYW 隔离级别,client 总是可以看到其最后一次提交写入的 LSN。每次读取操作会包含一个 timestamp(这里应该就是最后一次写入的LSN),用来构建 LSNread。LSNread 与 record LSN 共同决定 record 是否可见。
TGS 还记录一个 LSNref,表示当前 tablet 维护的最老版本 LSN。LSNref 根据用户指定的保留时间定期更新,在数据 flush & compaction 时根据 LSNref 决策 record 是否被 merge
===== 3.2.3 Distributed TGS Management
一个TGS的所有读写都在同一个 worker 节点上完成,以共享 memory table,如果 worker 节点压力过大,则会将 TGS(这里应该是 tablet?)从 worker 节点上迁走。
为了支持更高的并发,后续有计划支持 两种类型的 read replica:fully-synced replica & partially-synced replica
- fully-synced replica:维护最新的 memory table & metadata file,支持最新的数据读取
- Partially-synced replica:仅维护最新的 metadata file,支持已经flush到磁盘的数据读取
===== 3.3 Row Tablet
内存态使用一个 Masstree 来维护,按照 key 来排序。
Shard file 是一个分块结构(block-wise structure),包含两种块:data block & index block:
- Data block 中数据按 key 排序,连续的record存储在同一个 data block 中
- Index block 索引每个 data block 的 starting key & offset:(key, block_offset)
内存态&shard file 中的key对应的value格式为 (values_cols, del_bit, LSN):
- values_cols:记录非key列的值 -- 这里应该只存储了变更数据,而非整行数据
- del_bit:记录是否是删除记录
- LSN:本次写入的LSN
Reads in Row Tablets,读请求包含 key 和 LSNread:
- 【data skipping】同时从 MemTable & shard files 中读取数据,根据 metadata 筛选包含对应key以及LSN匹配的数据的文件
- 【Merge on Read】并根据 LSN 顺序merge 数据,构建结果
因为这里需要 Merge on Read,所以values cols中应该存储的只是变更数据
Writes in Row Tables
- insert & update请求 包括 key、column value、LSNwrite
- Delete 请求 包括 key、deletion mark、LSNwrite
Row Tablet 是一个基于 LSM Tree 的 Delta Store,数据整体按照key来排序。每个数据文件都只存储了 delta 数据,所以需要 Merge on Read。
===== 3.4 Column Tablet
Column Tablet 由一个 column LSM tree & delete map 组成。
Column LSM tree 的value是 (value_cols, LSN):
- value_cols:记录非 key 列的值 -- 这里应该是整行数据
- LSN:本次写入的 LSN
数据在内存态使用 Apache Arrow 来维护,按照写入顺序排序。
在持久化文件(shard file)有三类 block:
- Data Block:按照 key 来排序,并拆分为 row group。同一列的数据在连续的 Data Block 中存储,即每个Data Block中存储一个 row group中的一列数据,接下来的Data Block仍然存储这一列的数据,这与 Parquet 的存储格式是不同的
- Meta Block:存储列的 metadata & 文件的 metadata
- Column metadata:data blocks 的 offset、每个 data block 的 range、编码格式
- File metadata:编码格式、row count、LSN & key range
- Index Block:为了快速完成key的定位,index block中存储了row group中的第一个key
Delete map 是一个 row tablet,key是 shard file id,value存储的是 bitmap、LSN 等信息,方便快速过滤。
Reads in Column Tablet,读请求只包括 target column & LSNread:
- 从 memory table & shard file 并行读取,根据 LSN 过滤
- 读取出来的数据与 delete map 进行merge
与 row tablet 不同的是,column tablet 的 shard file 都可以独立的输出,而不需要和其他shard file合并。因为 delete map 可以说明哪些数据被删除了。
Writes in Column Tablet
- Insert 请求包括key、column values、LSNwrite
- Delete 请求包括 key 、LSNwrite,根据key快速定位目标文件以及 row number
- Update 请求 = Delete + Insert
Column Tablet 整体是一个基于 LSM Tree 的 Delete+Insert 的结构,Delete 信息按照 file 级别存储在一个 Row Tablet中。每个数据文件中都存储了一行完整的数据。
Delete+Insert 的方案也不太能快速定位目标文件,因为L0层可能有很多 key range 包含目标key的shard file,相比于 delta store 的方案是更快的
===== 3.5 Hierarchical Cache
包括 local disk cache,block cache,row cache,用于查询加速