Goal
Kudu 主要面向 OLAP 应用,支持大规模数据存储,支持快速查询,并且支持实时数据更新。相比Hive 之类的SQL on Hadoop,性能会好不少,并且支持数据实时更新,这也是 Hive 的一个痛点;相比于一个传统的 OLAP 数据库,它所支持的数据规模可能要大一点,毕竟 Kudu 是水平扩展的。
Kudu 的paper里提到,它的一个设计目标是统一存储日志数据和线上数据,并且提供高效的查询。这也是我们团队目前想要实现的一个目标。
相关工作
目前团队使用的 Hive,Hive 能够查询大规模数据,但痛点也很明显:一来占用资源很多,经常一个 MapReduce 的Job 就能跑半个小时几十分钟,对集群资源的占用是相当大的;二来,查询延迟相当高,如果只是跑一些报表还没有太大问题,但是如果着急需要一些数据,等上半个小时可能就是想当麻烦的;三来,Hive 不支持实时数据更新,虽然 ORC 看起来能实现数据更新,但延迟、吞吐量都想当捉急,略显鸡肋。
也有调研过 Hive on HBase 之类的方案,能够实现数据实时更新,但最致命的一点是,它的效率比 Hive 本身还要低,例如在 join 两个大表的时候会用 hash join 的方式,并且赤裸裸地把其中一个表转成 hash table load 到内存里,但千万级的表根本就很难 load 到内存里。对于普通的查询来说,效率也要低不少。究其原因,还是 HBase 的scan 性能不足,对于OLAP 来说,顺序 scan 的性能相当关键,这也是 HDFS 能够胜任的原因所在。
目前看来,基于 HDFS的方案通常都能提供不错的查询性能,但对于 实时更新的要求来说就有点捉襟见肘了;基于HBase、Cassandra的方案能够支持实时数据更新,但 scan 的吞吐量不能满足。
还有一些方案,例如 Facebook 的Presto,一些商业的 OLAP 数据库,Vertica 之类,使用门槛也并不低,并且前景也不明确。
Performance
评价 Kudu,当然是先看性能。官方的 paper 上有一些性能的比较:对于顺序 scan,比parquet 格式的 HDFS 存储性能不相上下;相比于 Apache Phoenix,性能秒杀;而 Random Access 的性能,略逊色于 HBase。
对于这样的结果,应该可以说是非常赞的。不过我们还是持谨慎的态度,自己又做了一次 benchmark。考虑到使用场景,我们并没有采用 tpc-h benchmark,而是 采用了 tpc-ds ,这也是 Hive 所采用的 benchmark,能有效评价大规模数据下的表现。
机器性能一般,四台机器,12核,32GB 内存,SSD 硬盘。实际测试中发现内存和CPU 占用都不太高。
使用了10GB数据的benchmark,具体 benchmark 的结果过于冗长,简而言之,在机器配置接近的情况下(Hive 用了另一个集群跑的,性能要高一点),Kudu 的执行时间通常在 Hive 的十分之一左右。不得不服。不过也存在一些问题,不支持 bulk load,导入数据这一过程还是非常慢的,几亿行的数据要几十分钟了。
不过从目前的结果来看,基本能满足我们的要求。
Features
Columar Storage
既然是面向 OLAP,那么 Kudu 还是使用列式存储,比 HBase 要好一点的是,它每列都是单独存储,几乎没有 column family 的限制。
C++
Kudu 使用 C++ 开发,相比于 Hadoop 生态众多使用 java 开发的程序来说,性能会有一定优势,并且在 GC上,算是解决了 HBase GC停顿的痛点。由于使用了 C++,Kudu 在一些细节上也做了很多优化,例如 SSE 指令,在row projection 的时候使用 LLVM 进行 JIT编译,据说这些优化带来了显著的性能提升。不过使用 C++ 也可能会存在一些问题,比如说和 Hadoop 生态的整合如何搞定呢?
Hadoop Ecosystem
Kudu 在开发时完全考虑 Hadoop 生态,尽量减少使用成本。首先查询引擎使用 Impala,如果一开始就使用 Impala 的话使用成本会降低很多;与 Spark 集成时,也有相应的接口,可以把 Kudu 的数据 load 到一个 RDD中进行操作,或者一个 DataFrame,用SparkSQL 进行查询。
实际使用了一番,虽然 API 确实很好用,跟开发一个普通的 Spark 程序别无二致,但是其吞吐量还是差了不少,把整个数据库的内容 load 到内存还是相当慢的。在这一点上应该没办法完全替代 HDFS。
HA
Kudu 还是继承了GFS、Bigtable 的传统,集群架构跟 Bigtable 很像,分为 master、tablet server。master 存储元数据,tablet 管理数据。不过 master/slave 的 HA 做得并不好,master 通常还是存在单点问题。在这一点上,Kudu 采用了multi master 的方案:master 的数据用Raft做replication,多个master也分 leader/follower,用Raft 做 leader election。至于
tablet server,也分leader/follower,同样使用 Raft 做replication和leader election。
使用 Raft这样的一致性协议貌似已经成了共识,新出现的分布式系统很少使用简单的 master/slave 了。对于 multi master 的了解还不是很多,不知道会不会带来新的问题。
Partition
Kudu 的 partition 策略有两种,并且是正交的,一种是 hash partition,另一种是range partition。range partition 比较适合时间序列数据,例如日志,可以每天划分一个partition,这样的访问效率也会比较高。
存储模型
Kudu 的存储模型类似关系模型,支持primary key,数据也是强类型的,有 int、string之类的数据类型。不过目前还不支持辅助索引,也许以后会实现。
一致性模型,支持snapshot scan,用MVCC保证,也就是说一次scan 过程中读到的数据是一致的。不过不支持多行事务,对于 AP数据来说也没有太大的必要性。
时间戳,与 HBase 不同,不支持 write 操作指定时间戳,但是在read 的时候可以指定。
存储引擎
Kudu 最大的特点还是它的存储引擎,也是它的性能保证。Kudu 的存储引擎没有像 HBase 一样基于 HDFS,而是基于单机的文件系统。(貌似现在的另一个流行趋势,就是不再基于分布式文件系统来搞分布式数据库了,可能是基于 immutable 存储来搞 mutable 确实比较累。)
单机的存储引擎整合了 LSM、B tree等经典的结构,可以看到 LevelDB、Parquet的影子。存储还是分为 memory 和 disk,数据先写入 memory和 write ahead log,再刷到 disk。整个存储抽象成 RowSet,细化为 MemRowSet 和 DiskRowSet。
MemRowSet,就是一个 B+ tree,树叶节点的大小是 1K,刚好是4块 cache-line(!!!);使用 SSE2 指令进行 scan,据说性能非常高。
对于 DiskRowSet,分为 base 和 delta,更新的数据写到 delta,定时 compact 到 base,没有像 LevelDB那样使用多级的 LSM。base 是一个经典的列式存储实现,针对不同的数据类型采用了不同的编码方案,例如字典编码、行程编码、front encoding 等等技术都用上了,尽量减少空间占用。在数据存储的基础上,使用了B+ tree 对primary key 进行索引,也用了 BloomFilter 加速查找。值得一提的是,BloomFilter
的大小是4KB,刚好又是filesystem pagecache 的大小。DiskRowSet 的设计借鉴了很多 Parquet 的思想,值得深入学习。
实际使用
- 部署安装相当简单,添加相应的 repository 就可以安装,配置精简,无须一些乱七八糟的配置
- 稳定性还可以,跑了几天,也跑了一些负载比较高的任务,没有挂掉
- 管理控制做的还不是很到位,命令行提供的功能相对较弱
- 资源占用很少,机器的负载很低
- 性能相当赞,碾压 Hive 的快感
总结
之后还会做一些使用跟调研,希望能够尽快在生产环境中用上 Kudu。