1. HTAP数据库背景及现状
1.1 起源
- 大型实时分析应用的逐渐流行(实时库存/定价、欺诈检测,风险分析,物联网等);
- 这些系统需要一个分布式的数据管理系统,要求能处理高并发的TP请求,同时支持对近期的数据进行分析;
- 有些应用甚至会在TP请求中进行AP操作;
- Gartner:即有事务又支持分析的系统叫HTAP;
- 实时分析:指的是实时交易过程中的分析需求,不是数据时效性上的实时。
1.2 方案1:一套系统同时支持OLTP和OLAP
- 传统的DBMS可以在一套系统上支持TP和AP,但对这两种业务场景都不高效;
- 基于内存的行存( VoltDB, Hekaton, MemSQL, Silo)和列存( MonetDB , Vertica , BLU , SAP HANA)系统,这些系统开始只是为一个场景专门设计的,后来为了支持HTAP增量了另一场景的支持。
1.2.1 底层数据分开存储
- SAP HANA 和 Oracle’s TimesTen 最初是为OLAP专门设计的,同时也支持ACID事务操作;不过事务的数据时以行存方式支持的,而分析操作是通过列存支持的;
- MemSQL最初是为in-memory OLTP设计的,后来也支持分析。对于TP数据是以行存方式存在内存中的,当数据需要写到磁盘中时,会转换成列存;
- IBM dashDB也是从传统的行存向HTAP演进的系统。通过行列混合的方式来分别支持TP和AP;
- HyPer是从一开始就为HTAP设计的,最初使用行存来同时支持TP和AP,不过现在也支持通过列存方式来更好的支持AP场景。
1.2.2 底层用一种方式存储数据
- H2TAP,学术项目,以行存的方式同时支持TP和AP;
- Hive,SQL-on-hadoop的解决方案之一,AP系统,在0.13版本开始通过ORCFile支持事务,但主要的应用场景是维度表更新以及实时流数据摄取;
- Impala + kudu:支持SQL分析及数据的更新和删除。 这类系统跟传统DBMS面临同样的问题,没办法同时为AP和TP进行优化。对于采用列存的系统,需要通过批量的方式来提高事务的吞吐能力;对于采用行存的系统,无法实现最佳的分析效果。
1.3 方案2:两套系统来组合来支持OLTP和OLAP
1.3.1 解耦AP和TP的底层存储
现状
- 需要用户的应用程序自己来协调AP和TP系统的使用;
- 数据在两个系统之间是通过ETL方式同步的;
- 这类方案在目前的大数据系统中非常常见,通常使用kv系统(如cassandra)来承载TP负载,然后将数据转换成 Parquet or ORC 文件存储到HDFS中,再使用SQL-on-hadoop方案进行数据分析。
1.3.2 AP和TP共享底层存储
这类系统通常是使用Spark之类的系统,在原有数据上支持大规模分析的场景。由于数据只有一份,因此AP分析的数据是实时数据。
- SAP HANA Vora:TP业务负载有vora直接支撑,而分析场景则通过Spark-SQL来支持;
- SnappyData:使用GemFire来承载TP业务,通过Spark生态来支持AP业务;
- HBase 和Cassandra等kv系统被广泛应用在需要实时更新的场景,而对这些数据的分析场景在通过组合SQL-on-Hadoop的解决方案来实现。TP和AP系统访问的是同一份数据,这需要开发一些额外的扩展,以使得AP引擎能直接读取TP存储中的数据,比如Spark HBase connector,Spark Cassandra connector等。通过connector来进行AP分析通常非常慢;
- 另外一些SQL-on-hadoop系统,使用HBase作为支持快速更新的存储引擎,如: HIVE, Impala, IBM Big SQL, and Actian VectorH。数据的处理是通过HBase的处理引擎来实现的;
- Splice Machine 、 Phoenix则是在HBase之上构建了SQL引擎;
- 基于HBase的AP系统,在处理AP任务时都比较慢,因为在HBase上执行Scan非常慢。HBase适合快速更新和点查的场景;
- Wildfire:IBM最近开发的一套HTAP系统,使用列存方式来同时支持AP和TP,使用的是Parquet格式。Wildfire可以立即分析最近提交的更新,同时支持使用Spark生态来执行大规模并行分析任务,提交给SparkSQL的请求会被尽可能的下沉到Wildfire引擎来进行数据处理。
1.4. 现状总结
目前没有一个系统支持true-HTAP;
- 大多数系统已经分别支持了AP请求和TP请求的处理,但是没有系统支持在TP中执行AP的场景;
- 要支持真正的HTAP,需要支持在同一个请求中既有TP又有AP的场景。 目前大多数系统需要组合各种解决方案来达到HTAP场景的需求,这对用户部署和维护系统带来挑战;
目前大多数TP系统为了加速TP的更新和点查,将索引全部放在了内存中,但是对于更大规模数据的场景,索引全部在内存中会导致TP系统变慢,应当考虑一种仅保留部分索引在内存中的方案,以提高常用数据的访问性能;
为AP场景设计的存储引擎,通常使用对象存储或者共享文件系统来存储数据。这些存储格式主要是为scan场景进行优化,无法提供高效的点查和更新能力。目前这还是一个未解决的难题。
1.5. 设计HTAP系统要做的一些决策
- Query processing and ingestion engines(数据分析和数据摄取引擎)
- Storage options (存储方式:一套存储还是混合存储)
- Data organization (数据组织格式:行存还是列存)
- Transactional semantics (事务语义)
- Recency of data being read by OLAP
- Indexing support(索引支持)
2. HTAP类数据库分析
2.1 TiDB
2.1.1 TiSpark
所属分类
- 两套系统来组合来支持OLTP和OLAP(采用Spark生态)
- AP和TP共享底层存储
TiSpark 是将 Spark SQL 直接运行在 TiDB 存储引擎 TiKV 上的 OLAP 解决方案
- TiSpark 深度整合了 Spark Catalyst 引擎, 可以对计算提供精确的控制,使 Spark 能够高效的读取 TiKV 中的数据,提供索引支持以实现高速的点查
- 通过多种计算下推减少 Spark SQL 需要处理的数据大小,以加速查询;利用 TiDB 的内建的统计信息选择更优的查询计划
2.1.2 TiFlash
所属分类
- 底层数据分开存储,TP采用行存,AP采用列存
- AP存储在一定程度上属于TP的一个副本,在比较高的层面上来看,可以认为是一套存储同时支持行列
- 两套系统来组合来支持OLTP和OLAP,TP通过原生的TiDB来支持SQL和事务;AP则通过TiFlash(基于clickhouse开发的向量化分析引擎)来实现
- 通过动态的SQL路由,TiDB在更高的层面隐藏了AP和TP引擎
技术实现
- TiFlash 基于列存和向量化分析引擎(Clickhouse)实现;
- TiFlash 作为 TiDB 的另外一个存储层,通过raft learner从TiDB 复制数据,并保证数据的事务性;
- TiFlash目前不支持SQL直接写数据,因此其角色目前不能成为leader或者follower;
- TiFlash节点如果发生故障,需要从TP集群重新同步数据;
- TiFlash作为TiDB的扩展,属于TiDB集群的一部分;
- 通过全局时间戳来保证用户从TiFlash中读到的是最新的数据;
- 通过LSM-Tree来解决列存的更新带来的写放大问题;同时利用其局部有序性来解决AP场景对scan的需求;
- 可以通过动态增减不同类型的节点来增减,TP或AP能力;
- TP节点和AP节点在系统层面有不同的标签,当启发式算法任务用户下发的SQL是一个AP运算时,会将请求路由到AP节点上执行。在一定程度上隐藏了AP和TP的具体角色分配;
- 一条join语句的两部分数,一份数据需要全表扫描、另一份可以利用索引,则可以分别将这两个子查询调度到不同类型的节点上执行,一部分利用TiFlash的scan能力、一部分利用TiKV的点查能力。
后续计划
- 支持SQL直接更新TiFlash中的数据
- TiFlash支持MPP,从而可以将TiDB或TiSpark的一部分计算下推到TiFlash执行
- 重新设计TiFlash的存储引擎,从而将性能提升2倍(目前TiSpark+TiFlash的性能,与Spark+Parquet的性能差不多)
TiKV 对应yugabyte的TabletServer(即DocDB),定位和实现框架几乎差别不大,都是在实现一个支持事务的分布式kv。TiKV是一个单独的项目,更解耦(意味着性能要差)。
- 都是提供RPC接口
- 都是Raft
- 都是基于rocksdb
2.2 MemSQL
2.2.1技术细节
所属分类
- 一套系统同时支持OLTP和OLAP
- 底层数据分开存储
技术实现
- completely in-memory rowstore(行存,全部在内存中,为TP/HTAP服务,主要针对实时场景)
- disk-backed columnstore(数据写到磁盘时,改成列存,为AP以及需要查询大量历史数据的HTAP应用服务)
- 一条查询语句可以同时查询内存和磁盘中的数据
- 也支持内存无法承载情况下的OLTP任务(使用hash indexes)
- 主从复制模式,支持同步和异步。同步会导致写延迟变高,异步复制则从节点数据落后一段时间
- 没看到如何实现列数据的更新的(MemSQL开启了列存的表不适合频繁更新的场景<参考>)
- MVCC + lock free 充分发挥内存的性能
- 每当事务修改一行时,MemSQL都会创建一个新版本,该版本位于现有版本之上。该版本仅对进行修改的事务可见。Read查询访问同一行,“查看”该行的旧版本;
- MemSQL仅在同一行发生写-写冲突的情况下才使用锁;
- MemSQL为死锁实现了锁等待超时。默认值为60秒;
- MemSQL将已修改的行排入垃圾收集器。通过避免全表扫描,垃圾收集器可以非常有效地清理旧版本;
- MemSQL尽可能将单行更新查询优化为简单的原子操作;
- skip list 代替btree,以提供更好的扩展性(skip list可以实现lock free访问,更适合纯内存场景)
- code gen,提高SQL查询性能
- 将SQL编译成C++代码,然后用GCC编译成native code,动态加载到MEMSQL进程。该过程是在SQL首次执行时进行的,并且编译后的代码重启后仍旧有效;
- 编译后的结果会被存储到hash表中,下次如果有相同模式的sql请求,直接使用编译后的代码执行。
- CPU效率意味着更高的吞吐量。由于MemSQL每个查询使用较少的指令,因此它们可以实现更高的吞吐量
- 无锁数据结构扩展性好,同时也减少了数据争用期间导致的CPU浪费。MemSQL引擎的每个组件都建立在无锁的数据结构上:链表,队列,堆栈,跳表和哈希表
- 内存数据持久化:定时快照+WAL
- 事务首先提交到内存缓冲区中,然后异步开始写入磁盘。日志刷新器线程每隔几毫秒将事务刷新到磁盘一次,通过批量提交来提高磁盘IO吞吐;
- 当日志文件达到已经大小时(2G),会压缩成快照。快照更紧凑、恢复更快;
- MemSQL完全避免了页面交换,并且可以保证在读/写查询上具有一致的高吞吐量SLA。MemSQL中的任何读取查询都不会等待磁盘。此属性对于对数TB的数据进行实时分析扫描极为重要。
- Aggregator:专门的聚合节点,类似Gateway,负责接收用户的查询请求,将请求调度到leaf节点,对查询结果进行聚合,返回最终结果给客户端。根据系统负载,可以配置任意数量的Aggregator节点
- 对于开启了列存的表,数据也会先写到内存中的面向行的skip list中,然后批量往列存中刷;只要数据写到内存skip list中,对客户端就是可见的
特性&定位
- 支持从各种数据源(文件、kafka、数据库、S3、HDFS)load数据
- 可以在一条语句中对实时数据和历史数据进行操作
- Full SQL. MemSQL supports full SQL and transactional semantics.
- 只支持READ COMMITTED隔离级别
- 支持冗余和持久化
- 面向高并发、高吞吐、小事务的场景
- 面向需要使用MEMSQL赚钱的场景:因为纯内存架构比较昂贵
- 解决实时需求,能回答当前正在发生什么事情的场景
- 提供实时分析功能,如:min、max、distinct、average
- 不是和BI场景
- Write-heavy, read-heavy workloads:Machine data、Traffic spikes、Streaming data
2.3 HyPer
简介
HyPer最开始是一个学术项目,产出了不少最新的研究成果。目前已经被收购。
3. 其他数据库
kudu HTAP: 放弃事务,只保留TP系统的高性能点查、插入特性;列存:提高OLAP的性能
参考资料
Hybrid Transactional and Analytical Processing - A Survey https://blog.csdn.net/tidb_pingcap/article/details/76178413 https://blog.csdn.net/TiDB_PingCAP/article/details/100201889 https://docs.memsql.com http://www.hyper-db.de/