作者: 康凯森
日期: 2018-06-03
分类: OLAP
注:本文对SnappyData的描述仅基于对SnappyData论文和官方文档的阅读。
SnappyData诞生的背景是当时没有一个单一系统可以同时满足Streaming Processing + OLTP + OLAP这3个需求。
当时的常见解决方案如下:
这些解决方案的问题如下:
SQL ON Hadoop(Hive, Impala/Kudu and Spark SQL)主要面向离线的OLAP分析,具有以下问题:
HTAP系统可以同时支持OLTP和OLAP,但是需要依赖外部系统(Storm, Kafka, Confluent)进行Stream Processing;
SQL On Streaming系统无法高效处理复杂的OLAP查询,也无法高效Join实时和历史数据。
上图原始地址:https://untitled-life.github.io/images/post/the_lambda_architecture_for_big_data_systems.png
上图是Lambda架构的一个示意,Lambda架构虽然可以满足Streaming Processing + OLTP + OLAP这3个需求,但是有以下缺点:
由于现有解决方案的诸多问题,SnappyData团队决定打造一个能够同时满足Streaming Processing + OLTP + OLAP的单一系统,并希望这个系统相比于现有解决方案能有更好的性能,更少的资源和更低的复杂性。
SnappyData是一个基于Spark+GemFire(开源版本是Geode)实现的,可以同时满足Streaming Processing + OLTP + OLAP需求的分布式内存数据库。具有以下特点:
下图是SnappyData的组件图,灰色部分是Spark本身的模块。
Spark是一个高效的分布式计算引擎,GemFire是一个低延迟,高可用,支持事务,面向行存,分布式的Key-Value内存数据库。Spark的设计目标是高吞吐,GemFire的设计目标是低延迟。要将Spark和GemFire整合来同时满足Streaming Processing + OLTP + OLAP的需求,显然会有不少挑战:
将GemFire整合进Spark,GemFire让SnappyData可以实现低延迟,细粒度,高并发的OLTP操作。
SnappyData同时支持行存和列存。列存是基于Spark RDD的实现衍生而来,行存是基于GemFire实现;一个表是使用行存还是列存需要在建表时指定;行存可以同时支持分区表和复制表(会复制到所有节点上,适合OLAP星型模型中的小维表),列存只支持基于Hash的分区表;行存支持索引,基于索引的Keys可以实现快速的读写,列存不支持索引,是按照连续的Blocks组织,支持dictionary,run-length 和bit 3种编码方式;行存支持“read committed” and “repeatable read”级别的事务隔离,列存支持快照级别的事务隔离。
我们知道,行存实现点更新十分简单,但是列存如何实现高效的点更新呢?
如上图所示,和LSM的思想类似,列存表的点更新会先写入一个delta row buffer的数据结构, delta row buffer是一个合并队列,合并的含义是同一条记录的所有更新操作只会保留最终状态, delta row buffer使用Copy on Write的方式支持并发更新, 超过一定行数或大小时会转为列存。所以在查询列存表时需要Merge列存和delta row buffer的结果。
SnappyData同时支持标准SQL(扩展自Saprk的SparkSQL dialect)和Saprk API.
SnappyData的Stream Processing主要是通过Spark Streaming来实现的,不过SnappyData优化了Stream Processing的OLAP查询能力,比如columnar formats, approximate query processing, and co-partitioning。
SnappyData将OLAP查询交给Spark,将OLTP查询交给GemFire。SnappyData一般将OLTP请求视为低延迟请求,将OLAP请求视为高延迟请求,低延迟的OLTP请求会绕过Saprk的调度直接操作数据,OLAP请求会交给Saprk的公平调度器调度执行。 当然,一个查询也可以显式的标注优先级。 下图是个简单的示意图:
Spark Driver通过主备的方式实现高可用。
SnappyData的失败探测依赖依赖UDP neighbor ping和 TCP ack timeout。
SnappyData将细粒度的事务更新的Recovery交给GemFire,将Batched and streaming micro-batch的操作交给了Spark。Saprk的Recovery依赖RDD的lineage;GemFire的Recovery依赖replication。
如上图所示,SnappyData的节点分为三类:
SnappyData 主要通过以下手段来加速OLAP查询:
Data Colocate:核心思想是Join时避免Shuffle。具体有两种手段:
Code Generation: 借助了Spark Tungsten的Code generation
SnappyData 有3种部署模式:
1 Local 模式: Client Application, Executors和Data Store 都运行在一个JVM中,主要用来开发测试,如下图所示:
2 Embedded SnappyData Store 模式:Spark Executors和Data Store 都运行在一个JVM中,如下图所示:
3 SnappyData Smart Connector 模式: Spark Executors运行在独立的JVM中,把SnappyData当做Spark的一个普通的Data Source。 这就像Spark applications中访问Cassandra, Redis和HBase等外部Store一样。 如下图所示:
优点:
缺点:
SnappyData的设计目标是1个系统同时满足Streaming Processing + OLTP + OLAP,但是从我目前的视野和认知来看,Streaming Processing 似乎并不属于DataBase的职责,DataBase只要做好数据的存储和查询(OLTP + OLAP), 能够同时很好的支持实时和离线数据的摄入就可以了。 目前整个业界还没有1个开源的DataBase能够同时做好OLTP + OLAP的查询。 至于HTAP的DataBase怎么实现,我个人不倾向这种基于通用计算引擎Spark改造的思路,更倾向于一个针对OLTP+OLAP优化的查询引擎+针对DB优化的存储引擎(行列混存)的思路。