从应用上来讲,GPU 数据库带来了三大方面的进步:加载速度、实时处理和宽表多条件查询。它最大的革新点之一在于,提供了一种不依靠索引,并大幅提升速度的手段。 所以,要搞清楚 GPU 数据库,先让我们聊聊数据库,尤其是数据存储。
索引、分区和组合主键
传统数据库的存储是在磁盘旋转的碟片上。读数据时碟片先旋转到一定位置,磁头再伸到相应的扇区,称之为寻道,并读取一个或多个数据块(Block)。旋转到位的时间和转速有关,以 7200 转 / 秒的磁盘为例,旋转延迟为 4.16ms。普通台式 PC 磁盘的寻道时间为 10ms, 数据传输时间可忽略不计,因此,光是从扇区上读取数据,就需要 14 毫秒左右。这是什么概念呢? 大家后面可以看到,GPU 数据库的响应时间常常也就几十毫秒。
数据库如何找到想要的记录?最糟的办法是扫描整个表,这就意味着要一个个的扇区地读,直到读到目标记录。每读一个扇区用 14 毫秒的话,就洗洗睡了吧。 每个记录都有一个所属的扇区和该记录在该数据块里的位置。不同的操作系统和数据库对这两个概念有不同的名称,让我们姑且称为 PageID 和 SlotID。如果知道每条记录的 PageID 和 SlotID,无论运气好不好,读取时间都是一个常数——直接到磁盘上的目标块,整块读出,并按 SlotID 找到该记录在本块里的所在位置,读出即可。这也称为时间复杂度为 O(1)。
索引可以看作一张表,最重要的功能就是记录每条记录的 PageID 和 SlotID, 并通过一系列指针,让人们尽可能快地找到或更新它们。
以数据库最基本的查询之一: SELECT…WHERE ID=123456 为例, 最有效的是哈希索引。它本质上是一个 Key-Value 表,Key 是 ID 的哈希值,对应该 Key-Value 表里的第几行,Value 包括一个指针,指向存储该条记录的 PageID 和 SlotID。对于 ID=123456,先用哈希函数算出哈希值,比如是 9231,那么直接去索引表的第 9231 行即可找到 PageID 和 SlotID,读出对应扇区的数据块里的数据。因为索引通常放在内存里,因此访问速度比一块块地从磁盘读快得多。
真实的索引远比这个复杂,并分为哈希、B 树,B+ 树等不同技术,来处理等值、范围扫描、非等值查询等。基本原理都类似,将扫描磁盘的多个扇区变为扫描一个多层次的地图, 以便逐层找到目标记录所在的 PageID 和 SlotID。
索引的最大坏处是多了一张或多张索引表。每增加一条记录,不仅要将该记录写到磁盘里,而且要计算哈希值、编入索引表、并调整索引里受影响的指针位置等等。将一个写操作变成了多个操作,更容易出错甚至损坏,而且延长了写入时间,大大降低了数据加载入库的速度。因此,大家可以看到,基于磁盘的数据库,无论是传统关系型还是现代的分布式 Hadoop 数据库,都会提供多种加载方式,其中一种一定是直接写文件,而不写索引,不考虑事务,以便加快加载。 在这点上,GPU 数据库很不一样,也是其最重要的优势之一,后面再讲。
用户们不会那么乖,只按 ID 查,如果还需要按时间、店铺 ID、产品 ID 之类查怎么办?最简单的办法是建多个索引。用哪个字段查,就为哪个字段建索引。如果字段数少还行,但是如果有 10 个常用字段怎么办? 这 10 个字段的组合就可能产生 1024 种索引,如果所有记录都需要编入索引,数据量将膨胀到何种程度?
用户行为分析的巨头 Heap Analytics 就用这个办法:将 Web 访问事件的所有属性记录进一张大表,对常用字段分别建索引。其使用的 PostgreSQL 支持部分索引(partial Index),允许按 Where 条件筛选,仅将符合 Where 条件的记录建入索引,以便大大地减小建索引的开销和索引表的大小。据称,他们需要维护几百甚至上千个这样的索引。 好在这张大 Fact 表是张稀疏表,每种属性的数据都不多,大量空白值,因此通过 Where 条件筛选后的部分索引会大大缩小。
Hive、SQL-On-Hadoop 之类的产品还会增加其他手段,比如分区。在建表时增加一个“分区”字段,通常用基数低,而用户经常查询的字段,比如年月、店铺 ID 或产品 ID。往数据库里增加记录时,同一分区的记录尽量连续存放在磁盘的同一个或相邻的数据块、或分布式集群的同一个 Region/Partition/Bucket/Division 里 (不同的技术对这个概念的称谓不同)。如果用户经常用这些字段查询,或者 Group By,那么只要先找到分区,一个读操作就可以读出同一个年月、店铺 ID 或产品 ID 的大量记录,大大节省旋转、寻道或分布式存储系统寻址的开销。
对于索引问题,惠普等老牌数据库公司还推出了 MDAM 等技术,将分区字段放在主键之前,拼成多个字段的组合主键,建入同一个 B 树索引中,从而用一个索引达到两个甚至更多索引的效果。 查询时,先组合主键里的分区字段,对每个分区进行并行的范围扫描(Range Scan)。可以想象,如果按性别分区,一下子可以缩小一半的搜索范围。 有兴趣的同学可以读读 HP Labs 大牛 Goetz Graefe 的有关论文,此人对惠普大型数据库和 Microsoft SQL Server 都有神一般的影响。 这一机制在惠普的 Nonstop SQL/MX 和 Apache Trafodion 里仍能找到。
(点击放大图像)
不过组合分区也有其他问题。如果存储系统是键- 值(Key-Value)的分布式系统,如HBase,那么对于每对键- 值都需要存Key。如果Key 是组合主键,包括多个字段,那么Key 就会很长,大大增加存储开销。所以还需要进行编码和压缩,来减小空间开销。代价是,在数据加载时,仍会有增加额外的编码开销。
GPU 和 SIMD
表和图像很类似,对象都是很有规律的一个个格子。 处理起来也很类似:可以并行地对每个格子里的数值执行一组单向步骤的指令——即通过 SIMD(Single Instruction Multiple Data)用同一组指令处理多个数据单元。加上 GPU 有多核,因此可以并行大量线程。每个线程都有对应的核支撑,无需操作系统来回切换,将大大加快速度。对 NVidiaK80 这样的 GPU 设备来讲,硬件并行意味着可以用 4992 颗核来支持上万个线程并行处理,这比操作系统 +CPU 的超线程快多了。GPU 上的内存也增加了不少,K80 有 24G 显存,在 RDMA 等技术的支持下,可以通过 PCI-E 总线和网口实现 GPU 显存之间的高速数据交换。 K80 显存的 IO 总吞吐量也达到 480G/S。因此,将 GPU 用于并行计算和数据库是水到渠成的事。
用 GPU 进行几千万行的扫描通常仅需几十毫秒。如果网络速度够快,几十亿行的扫描,返回几亿行结果,也仅需几百毫秒。
GPU 数据库
物联网的迅猛发展,让人们不得不调整数据平台的设计思路和处理方式。2017 年 Gartner 发布的 Top strategic predictions for 2017 一文指出,到 2020 年,210 亿只 IoT 设备对数据中心存储需求增长将不超过 3%。 先入库再处理的方式不适合高速、巨量的物联网数据。
和银行或商业交易记录相比,这些物联网数据的存储价值不高,因为同样的传感器之间的个体差异很小,更接近于随机过程,我们更关心在某个时间段内,发生某个事件的概率,因此需要确保相应统计模型的可靠,和等,而不那么关心 A 区的传感器在二季度比去年同期多发了多少个警报,更无需考虑 A 区传感器半年以来的购物和存贷记录,是否适合我行 180 天期的金茉莉理财产品。
在数据加载的同时进行分析,显得前所未有地重要。第一个坎就是要摆脱对索引的依赖。在多个数据流高速加载的情况下,无需写索引表所换来的性能非常可观。据 Kinetica 的创立者称,2010 年美国陆军情报和安全指挥中心(US Army Intelligence and Security Command) 希望处理 200 个实时数据流,包括手机、无人机、社交网络和 Web 访问,每小时 2 千亿条记录,为分析师和开发者们提供接口,来结合时间和地理位置监控危险信号。 他们尝试了 HBase,、Cassandra 和 MongoDB, 但一直未能解决同样的问题:能支持的查询极为有限,索引越来越多,越来越复杂,并一直需要增加硬件。得到结果的速度越来越慢,开始是 1 个小时,随着索引越来越复杂,逐渐需要 1 天,后来发展到一个星期。 用两年时间,他们开发了基于 GPU 的系统——用 HBase 作为永久存储,用 GPU 数据库提供实时加载、实时查询。多个实时流可以从多个 Head 节点,多线程加载,可处理每分钟 14 亿条。
该 GPU 数据库以列式存储,结合内存 + 显存,无需优化,无需建索引。内置可视化层、地理空间层、流处理层,并有 Hadoop 和 Spark 等接口。常见的使用方法是从 Kafka、Storm、Nifi 上获取数据,高速处理,并输出到 BI 工具如 Tableau、Qlikview 里。
(点击放大图像)
他们在2017 年纽约举行的O’Reilly AI 大会里有个演示: 8 个GPU 设备组成的集群,每台服务器256G 内存,整个集群有244 亿条记录,1.7TB,其中41 亿条推特。
(点击放大图像)
推特表的情况如下:
(点击放大图像)
用户可以在地图上任意圈定区域,结合邮编、名称等筛选、关联,一秒以内可以从41 亿条里,返回5.13 亿条结果。
(点击放大图像)
这种城市级的区域选择,得到4300 多万条结果,需几十毫秒。
同时,也可以复制区域形状,拉到其他地区放大缩小范围,用关键词搜索文本内容,也可以在几十毫秒得到几百条结果。
用SQL 对这张40 多亿条记录的大表,跑下面这条聚合查询,72 毫秒就出结果。
SELECT count(*), sum(sentiment_vader_p),avg(sentiment_vader_p), min(sentiment_vader_p),stddev(sentiment_vader_p),var(sentiment_vader_p),sum(txtblob_p),avg(txtblob_p),stddev(txtblob_p) FROM MASTER.Twitter
对两张 2000 万条记录的电影统计表关联、排序和模糊匹配,响应时间是 53 毫秒。既然是排序,那么 limit 100 对响应时间的帮助应该不大。
SELECT m.title, m.genres, avg(r.rating),count(r.rating) from movie m, rating r where r.movieID=m.movieID and (genres like'Action%' or genres like 'Thriller%') group by m.title, m.genres order by 4 desc limit 100
总的来说,分析型应用涉及到的大多数数据库操作是读。在生产时间,将数据加载到显存和内存里,无需访问磁盘,因此无需依靠索引来降低访问扇区的开销。通过每个 GPU 的几千个核,并发上万个线程并行扫描全表,尤其适合几千万到几十亿行的 JOIN、模糊匹配、Group By、全表扫描或聚合等等,比如七八千万条的大表在非主键字段上 JOIN,性能提升很明显。单机处理几千万行的数据也能在几十毫秒内返回。这样的低成本查询,使用起来就比 Hadoop 和传统关系型数据库灵活、高效得多。人们不仅可以更自由地选择任意字段分析,按任意组合,一次查询的条件更多,而且能够对高速加载的数据流实时处理——先处理,再落地。
同时,GPU 显存和内存之间的交换带宽很高,所以即使是 TB 级数据,或者有更新、数据注入时,可以用内存作为桥梁,实现磁盘–内存–显存缓存的三级缓存来解决。
多维交叉可视化查询
传统的 BI 只能每次看一个视图,比如先从销售,再碰碰运气加入库存,但实际上有价值的结论需要多个视图一起看,因此产生了多维度交互式可视化查询,让大家的尝试更高效。
Tableau 无疑是这方面最成功的企业之一。这种做法的颠覆性意义在于,同一个仪表盘上同时展现多个视图,反映不同的角度,比如销售、库存、客户等等,点击任何一个视图都会对所有视图添加同样的筛选条件,展现所有角度的变化。
每次点击实际上是对所有视图加了 Where 条件。当数据超过 1 千万条,用普通数据库的响应时间就会很慢,不再是交互式。如果视图超过 10 个,每次互动产生的查询很多,数据库压力也会很大。Tableau 等 BI 工具的解决方法是将查询结果缓存起来,和对大数据集先采样再计算。但这种缓存的设计比较考究,也增加了更多步骤——要先查缓存,缓存里的记录也可能未及时更新。而 GPU 数据库的响应速度快,每个查询的开销低,所以即使不用缓存、不采样,每次都发新的 Query,同样可以达到实时的效果。
MapD 曾有一个很棒的 Demo,展示纽约市出租车的交互式分析,数据量超过 10 亿条。利用 GPU 数据库的性能,加上查询结果在显存里,能实现动画式的交互:用户可以在时间轴上拉一个窗口,并拉动该窗口,其他视图随之变化,请见下图底部时间轴的浅蓝色块,从左到右移动效果。这就超越了指标计算,可以更好地洞察趋势。
这种拉动窗口产生的查询更密集,因此仍可以借助查询缓存,利用之前的结果来加速响应,实现流畅的动画效果。不过这种缓存更加简单,比如将查询结果按 Key-Value 的形式放入缓存,Key 是查询语句,Value 是查询结果。
(点击放大图像)
(点击放大图像)
(点击放大图像)
举个例子,如果用X、Y 轴分别展示航班的到达延误和满座率,通过拉动时间窗口,可以看出不同航空公司的这两个指标组成的点的移动。哪些公司的差距逐渐增大?哪些在减小?
小空间,大数据
大家都知道,GPU 数据库的显存小,那么有没有处理TB 级以上的例子呢?
除了Kinetica 宣称能处理100TB 级的数据之外,Sqream 也比较擅长大数据量的GPU 应用:其最初客户来源于癌症研究中心,需要处理基因排序,比如对比癌症患者的缺陷基因序列和正常序列,需要大表之间JOIN。每对序列有200 多GB,多对序列的比对就需要TB 级的处理能力。用关系型数据库对比12 对序列需要2 个月,Sqream 通过GPU 比对几百对序列,仅用了2 小时。
他们的另一个场景和Kinetica 最初的安全监控很相似,反映了GPU 数据库的另一个优势。
5 个无人机加一个巡逻车进行边境巡逻,每小时无人机总共传输 8TB 给巡逻车,巡逻车要及时发现异常以便及时准备扔石头(好吧,后面这句是我编的,如有雷同,纯属巧合)。车里最多能放一两台服务器,GPU 数据库当仁不让。
同样,无人机在人群集中的广场、景点巡逻时,可以将视频传输到坐在咖啡馆里的便衣工作人员的笔记本上,由人脸识别等视频软件产生结构化或半结构化的数据,并由 GPU 数据库实时处理,来识别可疑人物或异常移动的车辆。灵活转移的工作地点和隐秘性,使得一台带 GPU 的笔记本比几台服务器更适合。
机器学习和深度分析
GPU 数据库在机器学习和深度分析上的应用也越来越多。对于机器学习来讲,在模型上跑机器学习算法,需要用多个数据集在多个模型上计算。越快算完,可验证的模型就越多。 这方面的应用有很多文章,就不再赘述。
越来越多的厂家开始用 UDF 将人工智能、回归、分类、预测等现成的代码集成到 GPU 上运算,以充分利用 GPU 加速线性回归、随机森林、灰度提升树(GBT)或蒙特卡罗仿真等。一般的实现方式是先将自己的 Java/Python/C/C++ 的函数和 library 向 GPU 数据库注册成 UDF 函数,然后在自己的 Python/Spark/Tensor Flow/Caffe 里访问数据库,比如通过 UDF 向 GPU 数据库传入一个表,在 GPU 上执行 UDF,返回数值结果或二进制的表结果。
GPU 数据库的局限
GPU 数据库擅长能够被转化成并行处理的任务,比如 Group By, JOIN, Aggregates, Hash。但不擅长某些难以并行的,比如排序,或者先排序再处理。
同时,由于显存有限,在处理较大数据集时,需要内存、显存之间的交换。数据准备、分块、各个阶段 IO 访问和 Kernel 的同步协调等执行细节,对开发者的要求和代码质量比较高。考虑到数据的实际物理分布、并行度、对 JOIN 优化等情况,还可能涉及到数据重分配,广播等跨 GPU、跨节点的交换。虽然 NVidia 的 RDMA 通过支持 GPU 和网卡、GPU 同伴之间基于 PCI-E 总线的高速交换,能提高数据交换速度,但仍和开发水平关系很大。
一旦涉及到大量的写操作,GPU 显存小的劣势更明显,需要频繁地和内存、磁盘之间交换。随着计算的比例下降,IO 的比例上升,GPU 在计算上的优势就被 IO 上的劣势逐渐掩盖。同时,分析型应用和显存的有限,导致 GPU 数据库常用列式存储。因此对于一致性要求较高的场景,需慎用。它们一般提供最终一致性。读写并行时,如果有 20 个线程访问,19 个读,1 个写,会让所有读都通过,无锁,无事务,有可能带来脏读等问题。
在并发处理上,更多的是依靠高速计算能力,十几毫秒的响应速度结合分布式、管道、队列、消息系统等来提升并发性能。而如果十几毫秒仍然不够快, 从 GPU SIMD 的实施方式上来讲,是独占和单向的,有的指令要等所有核都计算完,数据同步后才能进入下个指令。 因此,如果某个核所需的数据没准备好,有可能造成其他核空等。
实际上,GPU 数据库这个称谓,和其发展现状来比,还有甚远的距离。“数据库”包括很多内容,性能仅仅是一个小部分。MySQL、PostgreSQL 等开源数据库的成功远远不只是“性能”或“兼容”。窗口函数、CTE 等带来的便捷、高可用、事务、ACID、元数据管理、冷热数据分离、混合工作流等等都是大型数据库集几十年、数代的开发和应用逐渐积累的,基于 GPU 的数据产品还不能全面和商业数据库媲美。
同时,站在数据湖的角度来看,GPU 能解决某些实时流处理、查询和加工工作。而更多的报表、ETL、数据集市、关系型数据库迁移、混合云等工作,还是需要基于 Hadoop 等分布式数据平台,依靠全面的 ETL、索引、分区、冷热数据分离、冷数据转移、清理、合并、复制等。数据治理、元数据管理、安全和权限、弹性计算等方面,几个 Hadoop 平台厂家也有成熟、全面,可靠、好用的方案。
任重而道远,且行且珍惜!
感谢蔡芳芳对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论