学习《数据密集性应用系统设计》
Lsm-tree:日志结构存储引擎,写入友好,tidb/elasticsearch
b tree:面向叶的存储引擎,读友好, oracle/mysql/postgres。
Lsm-tree:每个日志结构的存储段都是kv对序列,对该序列按键排序,即为sstable,即排序字符串表。通过压缩过程确保每个键在每个合并的段文件中只能出现一次。
如有更新即保留最新值,文件查找特定键时,只需要知道范围和偏移量,即可的到结果。虽然仍然需要内存索引来记录键的偏移,但它可以是稀疏的。在写磁盘之前将这些记录保存在一个块中进行压缩,可以节省磁盘空间,减少io带宽占用。
写入时基于内存维护排序结构,由于写入可以以任意顺序出现,将其添加到内存中的平衡树结构中,内存中的树被称为内存表,当内存表大于某个阈值时,将其作为sst文件写入磁盘。
读请求时,可以先在内存表中查找键,然后是最新的段文件,然后是次新的磁盘文件
由于es涉及到分词,所以es提供的是近实时搜素,新增的索引必须写入到segment后才能被搜索到。对于lucene,键即为term,值为所有包含该单词的文档id列表(倒排表)。从词条到posting list的映射关系保存在类sstable的排序文件中,这些文件在后台合并。
不同策略会影响sstable的具体顺序和时机,常见的是大小分级和分层压缩。大小分级的压缩中,较新的和较小的sstable被连续合并到旧的和较大的sstable中,es采用这种压缩方法。由于shard大小不一,合并为大的segment后,存在数据分布不均问题,同时segment merge是及其消耗系统资源的操作。基于分层压缩中,将键的范围分裂成多个小的sstable,旧数据被移动到单独的层级,压缩可以逐步节省磁盘空间,tidb基于rocksdb采用这种压缩方法,由于分层,导致一次写入操作,实际执行写入操作数倍增,即写放大。同时过期的数据,重复的,已删除的数据,需要通过Compact进行逐步的清理,占用大量系统资源需要谨慎使用。
B tree:将数据库分解成固定大小的块和页,页是内部读写的最小单元,这种设计更接近于底层硬件,磁盘也是以固定大小的块排列。执行查找操作,从根节点开始,沿着页的引用,找到包含单个键的页,该页包含内联键的值或包含可以找到值的页的引用。如mysql innodb采用聚簇索引组织表,b+数的叶子节点存放主键加数据,非叶子节点存放主键值。如果要更新键现有值,首先搜索叶子页,并将页写回磁盘,如果添加新键,需要找到其范围包含新键的页,并将其添加到该页。如果页中没有足够的空间,需要将起分裂为两个半满的页。写操作时是新数据覆盖磁盘上的旧页,lsm-tree仅追加更新文件,最终删除文件,但不会修改文件。Pg为了优化该操作,对删除文件仅打删除标记,并不在写入时直接修改文件,而是通过后台VACUUM处理。如果插入导致页溢出,因而需要分裂页,那么需要写两个分裂的页,并且覆盖其父页以更新对两个子页的引用。如果此时发生崩溃,最终会导致索引破坏。为了能使其在崩溃中恢复,采用预写日志,如mysql binlog/redolog,pg的pg_xlog。Oracle Redo log。原地更新页,多线程同时访问b-tree,需要锁存器保护页的数据结构
b tree:读取更快 Lsm-tree:写入更快
B tree必须要写2次数据,一次写入预写日志,一次写入树的页本身,即使该页只有几个字节的更改,必须承受整个页的开销。Lsm tree:以顺序方式写入sst文件,不必重写页,因此更快。Lsm tree容易压缩。但存在写放大,后台重压缩操作时,会发生读写等待。
B tree:每个键恰好对应于索引的某个位置,可以支撑强大的事务语义。Lsm在不同段中具有相同键的多个副本。
So mysql支持的是read commit/repeatable read,tidb更偏向于si(snapshot isolation)