列式存储引擎分析比对

本文涉及的产品
云数据库 RDS SQL Server,独享型 2核4GB
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
传统型负载均衡 CLB,每月750个小时 15LCU
简介: 列式存储具有高压缩率、利于列裁剪、以及高CPU计算效率(Cache Friendly)等特点,是分析型业务场景所选择的主流数据存储方案。 本文介绍了工业界一些常见的面向OLAP或HTAP场景数据库的列存存储引擎设计思路,并进行了总结和对比。

列式存储具有高压缩率、利于列裁剪、以及高CPU计算效率(Cache Friendly)等特点,是分析型业务场景所选择的主流数据存储方案。 本文介绍了工业界一些常见的面向OLAP或HTAP场景数据库的列存存储引擎设计思路,并进行了总结和对比。

SQL Server Column Stores

SQL Server在11、13、15发了三篇列存相关的论文,在面向数仓以及HTAP场景都提供了相关的解决方案。

数据存储结构


SQL Server列存的基本结构如上图所示,大约1M行组成一个row group,每个row group中按列独立编码并压缩,row group中每列是一个column segment。使用字典编码的列还会产生数个字典,segment和字典都使用SQL Server 提供的 blob storage 存储;列存数据的存放是无序的(为了提升压缩效率可能会对行序进行重排),有一个direcotry维护某列所包含的column segments、column segment的位置信息、行数、大小、编码类型、最大最小值等元数据。

该架构下SQL Server Column Store只能作为二级索引主要是因为列存索引上没有key到rowid的映射,点查要扫描全表,无法支持unique-check,也无法支持快速更新主要面向数仓场景。

列存主索引表

SQL Server 2012中使用列存索引数据必须有两份拷贝:一份是primary index(行存的堆表或B-tree);另一份在二级列存素银。SQL Server 2014允许将列存索引作为primary index,解决了数仓场景下行存和列存数据冗余的问题,同时支持更新。列存存储引擎架构如下,为支持更新引入了delta store和delete bitmap

delete bitmap 用于标记某行被删除,内存中的组织形式是一个bitmap,磁盘上的组织形式是一个row_id的B-tree。大量的删除操作可能会导致查询性能的降低,这是因为虽然逻辑上行被删了但是scan时对应的row groups依然需要被读取,IO并没有减少而是一直累积。SQL Server认为列存引擎主要是用于服务大量插入少量删除的数仓场景,如果一个列存索引上有大量删除那么建议重建索引。

delta store 用于支持插入,是一个传统的行存B-tree,key为系统生成列存rowid:。超过一定的行数后有一个后台Tuple Mover组件会将当前delta转换为immutable的列存压缩格式,转换过程不会阻塞对当前索引的scan操作,但会阻塞delete操作(delete可能会直接删除delta中的record,与delta转列存有并发问题)。

同时SQL Server还支持跳过Delta Store直接将数据存为列存Row Group格式的Bulk inserts,该操作可以显著提升灌数据吞吐。

这样的架构依然是为了数仓场景服务,存在一个问题:没有PK->RowID的直接映射,条件删除需要扫全表,出于同样的原因也不支持Unique Check和外键。因此SQL Server 2016提出了如下解决方案:

为了在列存主索引上支持主键、外键并提升点查和小范围查询的效率,SQL Server 2016允许在列存表上建立B-tree索引二级B-tree索引的key是列值,value是列存上的row locator: 。引入二级索引后会导致一个问题:随着插入以及delta store迁移到compressed row group等操作的发生,二级B-tree索引需要及时修改列值对应的row locator,但这可能会带来较高的代价:一个主索引列存表可能有多个二级B-tree索引,每次行迁移需要更新所有index;一个compressed row group中包含了大约1M行,生成一个compressed row group需要往每个二级B-tree索引中更新1M个entry。

为解决上述问题,SQL Server引入了mapping index 组件,这也是一个B-tree,从某行的original locator指向当前最新locator,在行被第一次迁移时插入。对二级B-tree索引的查询会通过mapping index来重定位到数据当前的正确row locator。此外,二级B-tree索引会通过键值+original locator来唯一定位某个entry(比如从列存索引中删除一行的场景),所以需要额外在列存主索引上新增一个original locator列,这个操作仅仅是在新增row group中添加一个column segment,因此是非常高效的。

列存二级索引

SQL Server 2016同样支持了对列存二级索引的更新,基于持久化事务表的可更新二级列存索引的支持,和基于内存表的列存引擎Hekaton一起构成了SQL Server的HTAP能力。Hekaton引擎的一些设计也很有意思,不过篇幅所限暂不做讨论,具体可见论文。

支持磁盘表列存二级索引的更新的列存架构如下:

上图中颜色代表了记录更新的热度。base index可以是堆表或B-tree表,数据在二级列存索引CSI中冗余(包括compressed row groups和delta store)。冷数据存放在压缩的row group中,热数据的插入和更新存放在delta store中,secondary CSI的delta store是一个B-tree,key是base index的locator(heap table上就是rowID,B-tree table就是主键)。

删除一行时,如果存在二级列存索引且待删行存在于delta store中(从base index上可以获取key的locator,从delta store中可以通过locator直接定位到对应的entry)直接将该行从delta store中删除即可,如果待删列已经被存入compressed row groups,需要定位到该行在列存中的RID并将RID插入delete bitmap中将该行从CSI中标记删除,这需要扫整个CSI,代价太大,因此引入了delete buffer。对compressed row group中某一行的删除不会立刻被应用到delete bitmap中,而是先存入delete buffer并通过一个后台任务被bulk apply到列存delete bitmap中。delete buffer是一个deleted row的B-tree,一个scan需要扫描所有row groups, deleted bitmap以及delete buffer, delta store。

Secondary CSIs是在OLTP表上建立的二级索引,主要面向HTAP场景,与面向数仓场景的列存主索引表不同,依然有高频的delete, update需求,导致需要引入delete buffer组件。这里有一个tradeoff:通过扫描性能的小幅度降低来换取删除(以及更新)的性能

总结

SQL Server 2016中对列存存储引擎的完善使得SQL Server有了一个比较均衡的数仓及HTAP解决方案。比如in-memory表上的列存允许内存换出以及自动clean up,全列存表允许建立二级B-tree索引以支持点查和小范围查询,二级列存索引支持高速更新等。此外,SQL Server 2016开始支持在一个SQL Server的readable sacondary replica上创建列存索引。

参考意义:

  1. 列存更新还是需要依赖delta store和delete bitmap,这基本上是一个业内比较统一的思路。
  2. 对于二级列存索引来说,维护PK到列存rowid的映射关系是个需要解决的问题,两种方案:1)添加隐藏列;2)额外的数据结构保存。hekaton引擎由于是全内存表并且有MVCC机制,选择隐藏列的方式;oracle直接在heap table上建立了列存索引,行存和列存的物理位置实际上是一一对应的;而SQL Server 列存主索引实际上可以通过额外建立B-tree索引来维护PK到列存rowid的映射;SQL Server 二级列存索引由于没有额外结构进行维护,为了支持更新不得不引入了有一个delete buffer。
  3. 在SQL Server列存引擎的三种实现方案,基本都是delta-main架构,其中一个比较大的优势是数据的冷热分离。对于频繁更新的行可以直接缓存在delta store里,删除请求可以被merge,不用下沉到列存row group,此外官方额外支持了compression delay参数filter功能来延迟delta store转列落盘来加强delta store的热数据缓存。但坏处就是delta store中的数据格式、row locator与main store不一致,从delta store到main store的switch过程会导致大量的rowid转换,这就导致了需要额外引入Mapping index组件。

可能存在的一些问题:

  1. 二级列存索引通过delete buffer来支持高速更新,但这会导致需要额外读取主键列来和delete buffer中的key做比对,同时delete buffer的bulk apply也会对扫描性能有一定的冲击
  2. 论文中没有提到磁盘列存表上的数据重整即自动clean up的解决方案,不过官方文档中提到了可以通过alter index ... reorganize和alter index ... rebuild语句来进行列存索引的重整或重建,SQL Server 2019中通过tuple-mover将reorganize在后台任务自动化。具体可见:https://docs.microsoft.com/en-us/sql/relational-databases/indexes/reorganize-and-rebuild-indexes
  3. 如果需要在列存主索引上建立B-tree二级索引,还需要额外在列存索引上加一个original locator列,如果由于删除数据过多导致需要对列存表做数据重整可能会付出一些额外代价( 需要和前台事务竞争,或选择直接重建索引)

参考文档

  1. SQL Server Column Store Indexes:https://dl.acm.org/doi/10.1145/1989323.1989448
  2. Enhancements to SQL Server Column Stores:https://dl.acm.org/doi/10.1145/2463676.2463708
  3. Real-Time Analytical Processing with SQL Server:https://dl.acm.org/doi/10.1145/2463676.2463708
  4. SQL Server 列存引擎的演进:https://mp.weixin.qq.com/s/Xr1kD_WgU0XfarcZOxG6Tg
  5. Get started with Columnstore for real-time operational analytics:https://docs.microsoft.com/en-us/sql/relational-databases/indexes/get-started-with-columnstore-for-real-time-operational-analytics?view=sql-server-ver15
  6. Columnstore indexes - Data Warehouse:https://docs.microsoft.com/en-us/sql/relational-databases/indexes/columnstore-indexes-data-warehouse?view=sql-server-ver15

TiFlash

TiFlash是TiDB提供的分析型引擎,作为列存副本,通过异步复制技术(Raft Leaner)以列存格式存储TiDB中的数据region,具有HTAP场景下的资源隔离和列存数据实时同步的优点。

总体架构

TiFlash架构如下:

TiDB将整个 Key-Value 空间分成为多个Region,TiFlash 中的 Region 副本与 TiKV 中完全对应,且会跟随 TiKV 中的 Leader 副本同时进行分裂与合并。不过与TiKV不同,TiFlash实现了另外一套数据组织与存储方式。

Each Region uses the Raft consensus algorithm to maintain the consistency among replicas to achieve high availability. Multiple Regions can be merged into one partition when data is repli- cated to TiFlash to facilitate table scan.

数据存储结构

TiFlash存储列存数据的引擎叫DeltaStore,架构如下图所示:

TiFlash中将数据按主键序排序切分,每个segment可能包含多个region的数据,segment使用DeltaTree维护数据,并同样允许自动分裂或合并。

DeltaTree也是一个delta-main架构的数据结构,类似于LSM-tree,但通过分区(更少的数据)+更少的分层(两层)可以减少LSM-Tree的多层归并导致的读放大问题。如下图:

Delta区域数据直接append only写入以提高写性能,数据会先写入delta cache中,然后攒批落盘,一批数据内部按主键排序,这么做是为了与TiKV的数据顺序一致,复用原来的region调度逻辑。

Delta Layer超过一定大小会归并到Stable Layer,归并过程中会排序后按列切分,以提高查询性能。Stable Layer的数据全局有序,按segment划分存储目录,按列划分存储文件。为了加速scan时stable和delta数据的归并,Delta Layer额外添加了一个B+tree索引

总结

TiDB本身是一个share-nothing架构的数据库,TiFlash的数据管理可以依托于TiDB,不需要自己去额外维护数据segment的拓扑并处理复杂的数据重分布、弹性scale out、故障恢复以及负载均衡问题,整个系统架构是比较一贯统一的,拥有同样的复制体系、调度体系、事务模型和一致性保证。而MultiRaft复制架构则为TiDB的行存和列存提供了一个松散耦合,从而达到行列资源隔离以及数据实时一致性的目的,这是基于分布式数据库实现HTAP能力的一个可行思路。

DeltaTree数据全局有序这一点有别于其他的分析性列存引擎(如ClickHouse仅在DataPart内部有序,SQL Server、Oracle等数据库的列存引擎数据无序),官方这么做的原因是:“存储按照主键顺序整理不止是为了快速读取定位,也是为了写入更新加速”。但是这么做依然会带来一定的读写放大

因此TiFlash对DeltaTree有很多精心的设计。数据分区+DeltaTree设计很大原因是为了读加速,官方认为相比起LSM Tree来说Delta Tree在读路径上所需要经历的归并层数要少很多;对于高频写入在delta layer中增加了delta cache以实现攒批落盘;同时给stable和delta层分别设计了不同的持久化存储方式。但这里有一个问题,由于delta内部数据不是全局有序的,而要求stable数据全局有序,同时segment允许分裂合并,所以会有比较重的后台merge过程,merge会进一步带来写放大,所以merge的频率需要在读写性能之间做折中。此外,TiFlash中数据删除也是通过del_mark来实现。

参考文档

  1. TiDB: A Raft-based HTAP Database:https://dl.acm.org/doi/10.14778/3415478.3415535
  2. TiDB HTAP 深度解读:https://zhuanlan.zhihu.com/p/205663113
  3. TiDB 的列式存储引擎是如何实现的:https://zhuanlan.zhihu.com/p/164490310

Hologres

Hologres是一个云原生的HSAP(hybrid serving and analytical processing)服务,存算层分离,支持分区表,每个分区都可以独立并行地支持读写。支持行列混存以加速点查、列scan和数据插入等操作。

总体架构

Hologres以table group来管理数据库表,一个table group包含多个表,同一个table group的所有表分片策略一致,用户可以通过“distribution_key"来指定分片列。分片称为TGS(也就是上图中的shard)每个TGS可以在work nodes之间自动迁移,以支持故障恢复、负载均衡以及集群扩容。为了在高吞吐写入的同时支持低延时查询,分片实现了MVCC。TGS由多个tablet组成,tablet按照表数据、表索引为单位组织,每个tablet都可以按照行存或列存形式进行存储,并用LSM结构来组织以尽可能提高写吞吐和减少写入时延。用户可以通过“clustering_key"来指定表记录的排序,如果没有指定排序列,存储引擎会按照插入的顺序自动排序。

数据存储结构

tablet是LSM结构,落盘的sst-file被称为shard file。tablet还维护了一个metadata file用来维护shard file的状态。基于LSM-Tree的结构,tablet中所有记录都是带版本的并且读写分离。基于HSAP的场景相比于HTAP来说只有弱一致性需求,Hologres选择仅支持原子写和read-your-writes read的一致性级别以取得更高的读写吞吐和读写时延。

行存和列存的tablet内部结构不太一样。行存基本上是一个LSM-Tree,同时为了加速点查额外维护了block级别的索引index_block。重点看列存的实现:

列存tablet包含两部分:1、列存LSM-Tree;2、delete map

列存更新先写入Memtable,为提高写吞吐直接按照写入顺序排序。MemTable攒到一定大小后作为shard file存入共享存储,在shard file中record按key排序并被划分到不同的row groups(定长),一个row group中的一列被存储为一个data block,同一列的data blocks会被连续地存储在shard file中以加速顺序扫描;所有列的数据都在一个shard file里;为了加速大范围数据查询shard file中还维护了每列和当前shard的meta block以及每列的index block。

delete map是一个row tablet,结构为>。fileid是shard file的一个ID,bitmap标识了当前一次删除操作在当前shard-file中所涉及到的记录。

添加delete map组件有利于加速大规模并行扫描每一个shard file可以被独立地读取而无需与其他层的shard file结合,因为delete map可以准确地告知LSNread之下某行是否被删除。同时,列存的shard file实际应用中只会有两层,做compaction时会横向合并,文件大小会比行存shard file大很多,这些都是为了大规模并行扫描做的优化。

但是这里每次删除,哪怕删除一个record也要写入整个shard file级别的删除bitmap,写放大还是比较大的,所以感觉Holo的列存还是主要面向批量更新的场景。

总结

宏观上,Hologres以table group来管理数据库表的底层逻辑是基于大部分写入都是写入到一批相关联的表中,因此可以将一个TGS的不同tablets中的写操作作为一个原子操作并仅仅往文件系统中插入一条日志记录。论文认为这种group方式可以有效减少文件的flush次数并提高写入性能。此外,从TableGroup基础上进行partition产生的TGS可以在多个worker node之间流转,存算分离架构,持久化的shard file直接存储在共享存储上,使得整个架构是可以做到弹性scale-out和负载均衡的,这点要优于ClickHouse的官方集群版。

存储层提供了行存和列存两种引擎,可以分别应对不同的数据负载,并允许用户随意搭配,比如主表用列存,索引用行存,也是支持的。

列存存储总的来说还是一个LSM的有序结构,不过也为了写入性能,MemTable直接append only,待写入shard file中时再排序。同时单个shard file比较大,LSM-Tree不会有太多层,并且引入了一个delete map来避免LSM多层合并来确认一个记录是否删除的问题,这些机制主要是为了减少LSM-Tree的读放大问题,同时可以增大扫描并行度,加速大范围scan。但是delete map本身是一个LSM-Tree,而且删除,哪怕删除一个record也要写入整个shard file级别的删除bitmap,这个结构本身也是有一定的读写放大的。但基于holo面向的大数据量高并发批量更新的分析场景,delete map不可能常驻内存,也需要落盘,选择用LSM-Tree来维护该结构应该是一个比较自然的思路。

参考文档

  1. Alibaba Hologres: A Cloud-Native Service for Hybrid Serving/Analytical Processing:https://dl.acm.org/doi/10.14778/3415478.3415550
  2. 云原生HSAP产品Hologres原理论文解读:https://zhuanlan.zhihu.com/p/360750135
  3. 首次揭秘Hologres存储引擎:https://developer.aliyun.com/article/779284

Clickhouse

ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS),使用列存引擎存储数据,支持数据压缩,被设计用于工作在传统磁盘上,支持多核并行和多服务器分布式处理,支持向量化查询。

数据存储结构

MergeTree是ClickHouse的存储引擎,支持数据partition、按写入时间维护DataPart、数据按排序键排序、支持稀疏索引、数据采样。此外还支持TTL、数据sharding、数据副本、数据压缩等功能。

MergeTree支持分区,会先按照用户定义的分区键将数据划分到不同的分区中,各个分区相互独立,每个分区内部,MergeTree根据日期将数据分为多个parts,每个part对应一个目录,具体数据是按照granule(行的集合,可以理解为row_group,是查询的最小单位,不定长)和block(每列按照block为粒度做压缩,每个block中会包含若干个Granule)存储的。一个part内部分为以下几种文件:

  • 列数据文件<$column_name>.bin,按排序键顺序排列
  • 列索引文件<$column_name>.mrk,列的稀疏索引,记录每个granule的行数、所在block的文件偏移以及自己在解压后block中的偏移
  • 主键索引primary.idx,主键的稀疏索引,存储每个Granule起始行的主键值,主键索引不能去重,而是用于快速定位主键行。
  • 分区键索引minmax_<$partition_column_name>.idx,用于统计每个data part中每个分区键的最大最小值,用于分区裁剪,可以有多个
  • skip索引skp_idx_<$column_name>_minmax.idx,用于定义在某个列上的min_max索引,聚合N个Granule上的数据生成粗糙索引,用户可以指定聚合函数,如minmax, set, bloom_filter等,用于补充主键索引的索引能力。

Data Part是immutable状态的,可以在后台异步merge。查询会在每个data part内部独立进行,先通过primary.idx定位到数据存储哪些Granule中,然后可以通过skip索引继续过滤掉不需要的Granule,最后在Data Part间并发访问通过column.mrk文件获取对应数据快的偏移。

ClickHouse支持多版本机制,不过多版本机制是以Data Part为粒度的,这可以保证读写不互相阻塞。

Data Part可以以 Wide 或 Compact 格式存储。在 Wide 格式下,每一列都会在文件系统中存储为单独的文件,在 Compact 格式下所有列都存储在一个文件中。Compact 格式可以提高插入量少插入频率频繁时的性能。

稀疏索引内存占用不会很多,所以方便常驻内存缓存,但可能会导致读放大,不适合高频点查(一次读需要读整个index_granularity的范围并对压缩块进行解压)。另外稀疏主键索引无法在insert时做唯一性校验,表内可以存在许多相同主键。

数据更新和合并

ClickHouse中数据是直接落盘的,一次插入就会产生一个Data Part,Data Part内部按排序键排序,因此ClickHouse只适合数据批量插入(当然后期社区也提供了一些特性去支持频繁小批量写入,比如引入compact格式in-memory part及wal支持、log engine family等)。

当然为了支持part合并以及数据“更新”,ClickHouse也提供Mutation功能支持数据删除更新,并支持异步合并,但依然具有比较多的局限性,主要还是面对数仓这种批量插入然后做分析,以及修订T+1离线数据的场景。

可以结合TTL将表数据在不同磁盘或卷上自动转移,也可以直接通过系统配置设置分层数据存储策略。

集群

ClickHouse支持集群化部署,总体是一个share-nothing架构,官方引入Zookeeper作为分布式集群的集群管理机制,很多云原生ClickHouse取消了这一依赖。一个ClickHouse集群可以包括多个Cluster,一个Cluster管理多个shard,数据可以保存在不同的shard上,每一个shard可以由一组用于容错的replica组成(ReplicatedMergeTree),ClickHouse可以提供丰富的sharding策略,如随机分发、固定分发、按列值分发或自定义表达式分发等。查询可以并行地在所有shard上处理。ClickHouse将副本策略继承在表引擎实现上,因此可以实现表级的副本策略设置。ReplicatedMergeTree的同步粒度是Data Part级别的。

一个比较严重的问题是对Zookeeper的依赖太深,ReplicatedMergeTree写入一个Data Part和Zookeeper的交互非常多,如果写入次数过多或分区过多可能会对ZK产生很大压力。

总结

ClickHouse是专业并极致优化的OLAP数据库,其设计的核心动机就是为了支持大数据集的高效并发查询。append only插入充分利用磁盘带宽,数据在Data Part内部按排序列有序存储有利于高效压缩,并在后台异步merge进一步整合数据。具有极致的查询延迟时间和大查询吞吐,支持并行查询,单条query可以利用整机CPU,分区和可以常驻内存的稀疏索引有利于数据剪枝和快速查找,支持向量化执行、SIMD以及表达式级别的runtime codegen;同时也提供了分布式集群支持,可以支持可线性拓展的分布式查询

此外,ClickHouse提供了完备的DBMS功能,多种表引擎、丰富的聚合功能和查询方式来满足用户场景。在多个级别(列、行、分区、表)提供数据TTL功能,并提供数据存储冷热分层,有效降低存储成本。

ClickHouse was initially built as a prototype to do just a single task well: to filter and aggregate data as fast as possible.

ClickHouse的缺点是没有完整的事务支持。数据可见性的保证也有限(可能在插入一段时间后才可见)

对小批量高频率插入不友好(官方建议每次写入不少于1000行或每秒不超过一个写入),也缺少高频低延迟修改或删除已有数据的能力。

仅提供稀疏索引使得ClickHouse不适合提供点查和主键唯一性约束,也不推荐使用大量短查询(官方建议每秒最多查询100次)。

有限的SQL支持。

分布式集群的副本同步、宕机恢复以及重分布能力弱,集群管理操作需要人工介入,存在一定的数据不一致可能。

参考文档

  1. https://clickhouse.com/docs/en/
  2. https://github.com/ClickHouse/ClickHouse
  3. https://developer.aliyun.com/article/762092

Oracle IMCS

Oracle In-Memory Column Store是Oracle的HTAP解决方案,允许数据同时由行存和内存列存两种格式存储,同时允许在RAC集群和Oracle高可用方案中横向扩展。

Oracle的内存列存需要基于heap-organized table建立,列存对象存放在连续分配的内存单位IMCU(In-Memory Compression Units,类似于一个row group)中,IMCU中按列分割为CU

从行存数据上建立IMCS非常自由,可以populate指定的Tablespace、表、表分片、表列,可以设置populate列存对象的优先级,Oracle也提供了分析工具可以根据历史负载给出建立列存索引的建议,同时允许在同一张表的不同分片上使用不同的压缩级别

数据更新

Oracle IMCS通过和行存事务共享SCN(System Change Number,可以理解为MVCC的版本)使得列存数据与行存事务具有同样的隔离级别,构建IMCU时获得初始SCN,并通过SMU(Snapshot Metadata Unit)组件跟踪后续事务对IMCU内行的修改并记录在SMU内的一个transaction journal模块中。这样,扫描时会根据扫描版本来通过IMCU中的数据+Journal中记录的数据修改情况来确定扫描数据的最终版本。同时由后台线程触发repopulation以加速扫描效率

显然,Journal缓存DML更新是为了让列存可以跟上行存更新,这与Delta-Main架构下通过Delta区域缓存行存更新数据是类似的思路,但扫描时需要同时加载Journal中的更新会一定程度上拖慢扫描速度,因此Journal不能累积太多。比较有意思的一点是由于Oracle的列存索引建立在堆表上的,列存数据可以和行存数据一一对应,因此可以很方便地从行存中随时加载出新版本的IMCU,也不存在列存数据碎片问题,Journal中只需要记录更新数据的rowid和SCN,也很省空间。整个列存population、更新和repopulation的过程也都不会入侵前台事务的关键路径,不会影响TP性能,甚至不用关心repopulation期间列存数据的一致性和原子性(这段时间内新到来的读请求直接读行存数据即可)。当然列存索引仅支持堆表也是一个局限性,可能并不能适用于使用其他数据库引擎而有HTAP需求的用户。

数据分布和弹性扩展

Oracle IMCS可以结合RAC集群提供透明scale-out和高可用能力,IMCS在RAC集群上可以看做是一个共享行存数据的share nothing架构,每个instance可以独立populate IMCUs。有额外的组件来管理列存对象的布局以及行列事务一致性,并在集群拓扑变更时可以保证最小化的IMCU rebalancing以提供高效的故障恢复能力。

用户可以使用 DISTRIBUTE子句指定如何在 RAC 集群的实例中分布一张表中的IMCS,默认可以根据表的分区标准和优化器的统计信息自动选择最优分配方案。

Oracle的内存列存分布式方案中比较有意思的一个点在于虽然是share nothing架构,但是通过一个轻量的共识算法使得一个RAC集群中的instance都可算出一个完整的全局一致性IMCU分布,然后各个实例独立populate自己负责的IMCUs,并保证集群拓扑变更时的最小数据重分布。

这里依然得益于IMCS是建立在堆表上的,data block和IMCU一一对应, 即使某个instance上没有预期的IMCU数据也依然可以通过行存数据访问。所以Oracle的IMCS分布式方案,可以看做是一个无状态的分布式架构,对于列存实例的生命周期控制要求可以不那么严格,同时每个节点都感知全局列存数据分布信息,对于多机执行的实现更加方便,任何实例上的SQL优化器都可以根据本地的全局列存拓扑来比对全集群跨机scan的cost和基于行存索引访问的cost然后生成多机执行计划,即所有节点都可以作为多机执行的coordinator。

此外,用户还可以通过DUPLICATE子句来指定某张表或某个分片存储为容错模式,这样所有关联的IMCU会独立存储在两个实例上且都可读。进一步加强了IMCS的高可用、负载均衡和最小数据rebalance能力。

此外,Oracle 2020年的ICDB论文提出Oracle同时还允许在其ADG解决方案上支持在standby上维护In-memory column store,通过redo log同步数据,用户可以根据不同的访问模式在主库、备库的内存列存中对数据进行分区,在不影响关键性能SLA的情况下获得容错和资源隔离的优势,也降低了空闲冗余资源的成本(较冷的数据可以offload到standby上populate IMCU)。

这其实PolarDB HTAP方案在RO上提供列存数据的挖掘、同步与保证事务一致性的思想一致, 不同的是Oracle standby上实时的DML redo应用实际上只是标记了一个IMCU数据行上失效的flag,需要repopulation或者回退到行存读才能读到更新后的数据,而PolarDB HTAP解决方案则可以通过redo log实时构建出完整的列存数据。

总结

得益于构建在堆表上的内存列存是的datablock可以与IMCU一一对应,Oracle IMCS可以通过更加自由的population+transaction journal+repopulation机制来解决列存数据更新问题,列存数据更新和后台任务基本不会对前台OLTP性能产生影响,当然代价是如果列存数据版本落后需要repopulation或者回退到行存读,对列存scan性能有影响。

Oracle IMCS的特点是比较丰富的分布式策略、压缩策略以及副本冗余策略,让用户可以对任意的数据库对象(表、分片、列)进行策略定制。

其分布式思想上,IMCS作为一个share nothing架构,强调其scale-out能力、最小化数据rebalance和容错能力。可以共享行存数据使得scale-out和拓扑变更相对更简单一点,不用关系持久化数据的重分配问题;所有节点都有一致性的全局IMCU location视图也有助于执行的并行、容错以及负载均衡;允许在standby上populate IMCU使得分析性负载在不影响关键性能SLA的情况下获得容错和资源隔离的优势,也降低了空闲冗余资源的成本。

参考文档

  1. Oracle Database In-Memory: A Dual Format In-Memory Database:https://ieeexplore.ieee.org/document/7113373
  2. Distributed Architecture of Oracle Database In-memory:https://dl.acm.org/doi/10.14778/2824032.2824061
  3. Oracle Database In-Memory on Active Data Guard: Real-time Analytics on a Standby Database:https://ieeexplore.ieee.org/document/9101796
  4. https://docs.oracle.com/en/database/oracle/oracle-database/21/inmem/repopulation-and-dml.html
  5. https://www.oracle.com/technetwork/database/in-memory/overview/twp-oracle-database-in-memory-2245633.pdf


总结

SQL Server提供了一个经典的delta-main列存架构解决方案,行存之上的二级列存索引可以提供real time analytics能力;

TiFlash使用MultiRaft技术将行存与列存松散耦合,Oracle允许在standb上构建IMCS,这是行列存资源隔离数据一致性的实现思路;

Hologres的云原生架构、Oracle的极致弹性、负载均衡和高可用的集群架构可以看到行列混存数据库在分布式架构上的实践;

ClickHouse的MergeTree家族所提供的的丰富索引以及TTL功能也有一定的借鉴意义。


总体来说,列存存储一般是通过append only内存攒批来实现高速插入,通过分列压缩来提供高速的列扫描能力,这是列存存储架构的一些共性。而HTAP系统一般是通过行列共存来实现,列存通过标记删除来提供高并发更新,通过后台任务来进行碎片整理和布局优化。至于如何对应行存和列存数据,各家都有各家的实现,如行存添加虚拟列、添加额外索引、日志复制技术+列存按PK排序等。


下表是对上述几个提供分析负载数据库系统的粗略对比:

SQL Server CCI

SQL Server NCCI

TiFlash

Hologres

ClickHouse

Oracle

存储层架构

delta-main

delta-main

类LSM-Tree(虽然也区分了delta-main,但归并排序、后台compaction等逻辑依然更类似于一个LSM-Tree)

LSM-Tree,实现细节上区分行列存储引擎

类LSM-Tree(类似于一个没有memtable直接产生l1层的LSM-Tree,并且官方路线也在朝内存攒批靠拢)

data block和IMCU一一对应,通过每个IMCU维护Trx Journal的方式支持更新

数据局部性

支持分区表及索引分区

支持分区表及索引分区

按主键range分区,数据全局有序

支持按分区键分区,数据按排序键排序

支持按分区键分区,DataPart内部按排序键排序,DataPart会在后台merge

支持分区表,在此基础上可以根据额外的分布式算法将列存数据分布在不同instance上

列scan

delta_store + main_store + delete_map
scan需要merge delta store

delta_store + main_store + delete_map + delete_buffer由于需要兼顾二级索引更新,
scan需要merge delta store和delete buffer,代价稍大于CCI

LSM-Tree scan,存在多层merge,读放大,但DeltaTree专门针对读放大做了优化

LSM-Tree scan,存在多层merge读放大,引入了一个delete map来减少读放大,同时可以增大扫描并行度,加速大范围scan。但是delete map本身是一个LSM-Tree因此也存在读放大

分区+按DataPart组织,支持高并发扫描,但根据具体的MergeTree类型或查询类型需要进一步归并

如果列存数据版本满足scan版本要求就直接大平层读,不满足需要回退到行存读更新后的数据

范围查询

通过二级B-Tree索引支持

很慢,需要全表扫描,可以通过行存支持

按主键分区+排序,可以加速主键范围查询

按排序键排序,可以加速排序键范围查询

按排序键排序,可以加速排序键范围查询,同时有丰富而强大的索引可以进一步加速查询

点查

通过二级B-Tree索引支持

很慢,需全表扫描,可以通过行存支持

列存不适合,直接查行存索引更好

列存不适合,直接用行存查

不支持

ACID

支持,同时可以通过二级B-tree索引支持unique-check

通过行存支持

通过行存支持

有限支持,仅支持原子写和read-your-writes

不支持

支持,行列事务一致,拥有同样的版本控制机制。

数据更新

通过二级B-tree索引定位更新行,引入mapping index解决行号变更问题,通过delete-map进行列存标记删除

通过delete-buffer缓存删除记录,通过delete-map进行标记删除。delete-buffer会影响查询效率,同时合并也会一定程序影响前台写入性能

记录有序直接定位到更新行

记录有序直接定位到更新行,通过delete map进行标记删除。delete map本身存在读写放大

不支持

通过Trx Journal支持。积攒太多会影响scan性能。

数据重整

支持手动的数据重整,也支持后台row group合并和碎片整理

支持segment自动分裂、合并。TiDB本身架构做这一套是很合适的

LSM-tree本身支持后台compaction,行存按level,列存按size

有后台merge逻辑,size tiered策略

后台repopulation任务直接原地从行存重新构建列存数据即可,没有数据碎片问题。

集群架构

单机,也可以在一个replica上创建二级列存索引

share nothing multi raft
独立列存节点

存算分离
数据分区
share storage

share nothing

share nothin,共享行存数据,共享列存数据布局视图。

弹性

灵活的scale-out能力

灵活的scale-out能力

有scale-out能力

灵活的scale-out能力

负载均衡

segment自动分裂、合并、迁移,有负载均衡能力

TGS可以快速在worker-node之间迁移,有负载均衡能力

有限负载均衡支持,需要运维手段接入,ReplicateMergeTree可以给单表加副本

为IMCU选择绑定的instance时会尽可能负载均衡,此外提供了列存对象副本冗余能力

适用场景

数仓

HTAP

HTAP

HSAP

数仓

HTAP

其他特点

支持filter,和
delta store延迟压缩,有冷热数据分层的思想,从正向路径上减少列存碎片

MultiRaft使得列存节点与行存松散耦合,同时可以依托于TiDB的整体架构

多个table汇聚成table group的思想。
整体架构更面向云原生、弹性

有TTL功能,数据冷热分区、分层。丰富索引。

丰富的用户自定策略,强调极致弹性和容错的分布式架构,允许在standby上构建列存数据提供了TP, AP负载资源隔离的解决方案


此外,PolarDB的HTAP解决方案,利用PolarDB原本的物理复制能力能够很好的在一个数据库实例中结合TP、AP的优势并提供行列存数据之间的一致性保证。依托于共享存储和一写多读架构可以提供一个云原生的HTAP解决方案并实现实时分析和资源隔离。详见:400倍加速, PolarDB HTAP实时数据分析技术解密

相关实践学习
数据库实验室挑战任务-初级任务
本场景介绍如何开通属于你的免费云数据库,在RDS-MySQL中完成对学生成绩的详情查询,执行指定类型SQL。
阿里云云原生数据仓库AnalyticDB MySQL版 使用教程
云原生数据仓库AnalyticDB MySQL版是一种支持高并发低延时查询的新一代云原生数据仓库,高度兼容MySQL协议以及SQL:92、SQL:99、SQL:2003标准,可以对海量数据进行即时的多维分析透视和业务探索,快速构建企业云上数据仓库。 了解产品 https://www.aliyun.com/product/ApsaraDB/ads
目录
相关文章
|
存储 SQL 自然语言处理
基于 HBase 的海量数据查询与检索解析|学习笔记
快速学习基于 HBase 的海量数据查询与检索解析
901 0
基于 HBase 的海量数据查询与检索解析|学习笔记
|
存储 SQL 分布式计算
|
1月前
|
存储 分布式数据库 Apache
记录级别索引:Apache Hudi 针对大型数据集的超快索引
记录级别索引:Apache Hudi 针对大型数据集的超快索引
46 2
|
8月前
|
数据采集 存储 缓存
【如何提高数据采集和分析的性能】如何优化数据查询、数据分区和数据压缩方面的处理
【如何提高数据采集和分析的性能】如何优化数据查询、数据分区和数据压缩方面的处理
|
存储 搜索推荐 大数据
大数据数据存储的搜索引擎Elasticsearch的基本操作(含API使用)的基本检索的结构化检索
在使用Elasticsearch进行数据搜索时,我们通常需要进行结构化检索。结构化检索是指使用预定义的查询语言和结构来查询数据库中的信息。
179 0
|
存储 NoSQL Oracle
「时序数据库」使用cassandra进行时间序列数据扫描
「时序数据库」使用cassandra进行时间序列数据扫描
|
SQL 存储 分布式计算
【时序数据库】时间序列数据和MongoDB第三部分-查询、分析和呈现时间序列数据
【时序数据库】时间序列数据和MongoDB第三部分-查询、分析和呈现时间序列数据
|
存储 JSON 算法
基于HBase构建千亿级文本数据相似度计算与快速去重系统
前言 随着大数据时代的到来,数据信息在给我们生活带来便利的同时,同样也给我们带来了一系列的考验与挑战。本文主要介绍了基于 Apache HBase 与 Google SimHash 等多种算法共同实现的一套支持百亿级文本数据相似度计算与快速去重系统的设计与实现。该方案在公司业务层面彻底解决了多主题海量文本数据所面临的存储与计算慢的问题。 一. 面临的问题 1. 如何选择文本的相似度计算或去重算法? 常见的有余弦夹角算法、欧式距离、Jaccard 相似度、最长公共子串、编辑距离等。这些算法对于待比较的文本数据不多时还比较好用,但在海量数据背景下,如果每天产生的数据以千万计算,我们如何对于这些海
693 0
|
存储 SQL NoSQL
海量结构化数据存储技术揭秘:Tablestore存储和索引引擎详解
海量结构化数据存储技术揭秘:Tablestore存储和索引引擎详解
369 0
海量结构化数据存储技术揭秘:Tablestore存储和索引引擎详解
|
存储 监控 分布式数据库
解密OpenTSDB的表存储优化
本篇文章会详细讲解OpenTSDB的表结构设计,在理解它的表结构设计的同时,分析其采取该设计的深层次原因以及优缺点。它的表结构设计完全贴合HBase的存储模型,而表格存储(TableStore、原OTS)与HBase有类似的存储模型,理解透OpenTSDB的表结构设计后,我们也能够对这类数据库的存储
27973 0