动机和背景
在Meta(Facebook),Presto是一个分布式实时查询引擎,使用SQL语言作为接口,对PB级数据进行快速、交互式查询。它支持标准的ANSI SQL,包括查询、聚合、JOIN和窗口函数。
Alluxio是数据协调平台,作为支持Presto和其他各种数据分析应用和用例的关键技术。Alluxio创建了一个虚拟数据层,将来自任何文件系统或对象存储的数据联合起来,提供跨存储系统的统一命名空间,并使用行业标准的接口将数据提供给应用程序,实现快速数据访问。
为了提高Presto的性能,了解缓存大小和缓存命中率的影响是至关重要的。Presto需要从Alluxio了解某些缓存信息,以确定在缓存存储有限的情况下,扩大缓存规模是否有助于提高缓存命中率和性能。这些信息对优化缓存算法也有帮助。我们还想优化路由算法,以达到更好的平衡和效率。因此,如何更好地跟踪和管理Alluxio缓存数据是presto优化决策的关键。
从Presto方面需要解决两个关键问题。
1.如何为每个租户确定缓存的大小
2.潜在的缓存命中率的提高是什么?
我们提出了"Shadow Cache",这是一个轻量级的Alluxio组件,用于跟踪工作集大小和缓存命中率。
为了回答第一个问题,Shadow Cache会告诉管理员,在过去24小时内,高速缓存收到了多少非重复字节,以估计未来的高速缓存需求。对于第二个问题,Shadow Cache会告诉管理员,如果缓存能保留过去24小时内的所有请求,即未命中的是从未出现过的请求,那么有多少请求命中了缓存,因此可以计算出缓存的最大命中率。
这个轻量级的Alluxio组件,Shadow Cache,可以提供对缓存工作集的洞察力,以及如果有无限的缓存空间,缓存命中率会是什么样子。为了监测集群的缓存状态,我们定义了以下关键指标。
- C1: 某一时间点上的真实缓存使用率
- C2:在一个时间窗口(1天/1周)的影子缓存工作集
- H1:真实缓存命中率
- H2:影子缓存命中率
挑战
当我们试图为Alluxio的缓存提供上述指标时,我们遇到了一些挑战。
低内存和CPU开销
影子缓存是一个轻量级的组件,它记录了缓存工作集的大小。在有限的内存下很难跟踪一个 "无限的 "工作集。影子缓存还必须有较低的CPU开销,因为它在处理每个查询时都会缓存数据。否则,用户的请求将被阻断很长时间。
精确性
影子缓存还必须保证准确性。在Presto中,Shadow Cache测量集群的缓存状态,如果估计的极限缓存命中率太低,Presto可能会错误地判断这个作业是不适合缓存的。相反,如果估计的极限缓存命中率太高,Presto可能会认为此时扩展集群的缓存会显著提高整体性能。
动态更新
Presto和其他现代数据应用主要用于发现当前或未来趋势。因此,影子缓存也应该实时丢弃过时的项目。否则,很可能会给决策带来噪音干扰。滑动窗口是存储最新项目的最常用的方法之一,但为滑动窗口模型创建数据结构并不容易。当窗口滑动时,我们需要实时删除刚刚移出的项目。尽快找到需要删除的项目并将其删除是很重要的。
解决方案
考虑到高精度和低开销这两个要求,我们马上想到了布鲁姆过滤器,它在各种分布式数据库中已经得到了普及。Shadow Cache基于Bloom过滤器来估计工作集的大小和极限命中率。下面是布鲁姆过滤器是如何解决这三个挑战的。
布隆过滤器。开销和准确性挑战的解决方案
布隆过滤器是一种空间效率高的概率数据结构成员测试。布隆过滤器是一个初始化为所有零位的数组,每个对象只用几个比特表示,大大节省了空间开销,并提供了查询的出色效率。布隆过滤器可以确定一个项目是否存在。如果布隆过滤器返回该项目不存在,则该项目一定不存在。请注意,假阳性是可能的,但假阴性是不可能的。
布隆过滤器有k 的哈希函数。要添加一个元素,应用每个哈希函数并将位设置为1。要查询一个元素,应用每个哈希函数并将位相加。当k 位置上的所有比特为1时,该项目被认为是存在的。否则,该项目被认为不存在。
布隆过滤器链。动态更新的解决方案
布隆过滤器可以同时提供低开销和高准确度,那么我们是否可以直接将其应用于影子缓存?
我们遇到的第一个问题是,布隆过滤器不支持删除。这是因为我们只关心用户应用的工作集在一段时间内的大小,而Shadow Cache则需要做到这一点。Shadow Cache通过将多个过滤器连接在一起,创建一个Bloom过滤器链来做到这一点。
下面是如何利用布隆过滤器链来实时更新工作集的负载大小。
查询
如上所示,Shadow Cache是一个由多个Bloom过滤器组成的链。当跟踪一个用户在过去24小时内的工作集的大小时,我们可以将24小时分为四个时期。在Shadow Cache中,每个时期都有一个Bloom filter跟踪,每个Bloom filter跟踪一个时期。Shadow Cache使用所有现有的Bloom filter或者为查询创建一个新的Bloom filter,如下图所示。
实时更新
为了保持数据的实时性,我们需要Shadow Cache丢弃那些在时间窗口滑动时已经过时的数据。布隆过滤器的值必须随着时间t不断地更新,已经超出时间窗口的布隆过滤器项目必须被删除。由于我们是将多个布隆过滤器结合在一起,所以很容易确定过时的项目位于布隆过滤器的最末端,如下图所示。每当一个新的时期开始,我们就从链上删除最老的过滤器,并添加一个新的全空过滤器来记录最新的数据。
工作集 大小
由于Bloom过滤器将一个项目映射到多个比特,仅仅根据比特数来判断工作集的大小会带来不可接受的误差,因为一个比特可能代表多个项目,而一个项目可能分散在多个比特中。因此,我们采用Swamidass & Baldi(2007)得出的公式。我们利用以下公式的近似值来衡量工作集的大小。
其中n*是对过滤器中项目数量的估计,m是过滤器的长度(大小),k是哈希函数的数量,X是设置为1的比特数。
无限大小命中率
在提供了工作集的大小指标后,Shadow Cache还需要提供无限大的命中率。我们可以使用布隆过滤器作为无限空间的缓存,因为它们可以在占用很少内存的情况下跟踪海量的数据。撞击布鲁姆过滤器的用户请求数等于无限大缓存的命中率,表示为命中。"用户请求 "的总数表示为queryNum。QueryNum是 "用户请求 "的总数,所以命中率等于hit/queryNum。
使用影子缓存来确定Presto集群的缓存状态
在完成布鲁姆过滤器链后,我们可以快速了解之前定义的指标H1、H2、C1、C2。在下一步,Presto可以通过比较它们之间的大小关系来确定集群的缓存状态,如下图所示。
当H2较低时,表明这个集群中的应用程序的缓存命中率即使在无限的缓存空间下也无法达到。这意味着这个应用对缓存不友好。当H2高,H1低,C2>C1时,表明该集群的缓存空间分配不足,如果扩大缓存容量,命中率可以进一步提高。当H2高,H1高,C2<C1时,说明集群缓存分配过多,资源被浪费了。如果H2>H1,C2>C1,C2>C1,则集群处于良好状态,这意味着不需要对缓存进行扩展。
实施
Shadow Cache的布隆过滤器的实现是基于Guava BloomFilter库,并支持基于用户定义的内存开销预算和影子缓存窗口的特定过滤器配置。目前,Shadow Cache支持以#pages和#byte为单位的工作集大小,分别表示工作集包含多少页和多少具体字节。对于命中率的计算,影子缓存支持无限大的字节命中率和对象命中率。
以下是配置情况。
- #定义工作集的过去窗口。
alluxio.user.client.cache.shadow.window=**24h** - #用于跟踪的bloom过滤器的总内存开销。
alluxio.user.client.cache.shadow.memory.overhead=**125MB** - #用于跟踪的bloom过滤器的数量。每个人跟踪一段窗口。
alluxio.user.client.cache.shadow.bloomfilter.num=**4**
测试结果
我们测试了Shadow Cache,发现只有125MB的空间,Shadow Cache可以追踪27TB的工作集,错误率只有3%。此外,通过使用HyperLogLog可以进一步降低错误率,但如果使用HyperLogLog,将不支持无限大的命中率估计。
Presto路由优化
为了提高性能,Presto需要一种方法来及时调整集群,如果它从Shadow Cache了解到具体的集群状态。我们的下一步是描述当前的Presto路由算法,然后在引入Shadow Cache后提供几种路由优化的方案。
Presto路由
Presto在不同的集群中存储不同的表,通过表名在各集群中共享缓存。因此,访问同一个表的查询总是会去同一个目标集群,以最大化其缓存。如果不这样做,集群缓存将被各种不同的表填满。下面是一个路由算法的图示。
如上图所示,表1到表4有不同的表名,因此被分配到不同的群集。当从表1请求数据时,路由算法将把请求发送到集群1,而当从表3请求数据时,路由算法将把请求发送到集群3。
路由优化选项
集群请求的响应时间是判断集群是否工作的一个简单方法。当集群的响应速度很慢或者响应时间过长时,我们就认为集群出现了问题。如上所述,通过影子缓存,结合H1、H2、C1和C2,我们可以快速确定一个集群是否因缓存压力而出现性能下降。
对于这样一个表现不佳的集群,Presto提出了以下三种路由优化方案。当然,每个选项都有其权衡。
方案一
当主集群繁忙时,有一个指定的副集群,为这些查询打开缓存。然而,这种方法需要在每个集群上存储额外的表缓存。
选项2
两个集群都被当作 主集群来服务请求,并在两个集群中进行负载平衡。然而,这个方案会使缓存的磁盘空间占用成倍增加。
选项3
从表到集群建立Map,使CPU的利用率更加均匀。但是,它可能使缓存的存储分布不均匀,需要额外的缓存空间。
总结
跟踪和估计缓存中工作集的大小是一个很大的挑战,所以我们使用Bloom过滤器开发了一个轻量级的Alluxio组件Shadow Cache。因为我们只对工作集的最新状态感兴趣,所以有必要使用一个时间窗口模型来消除过时的项目。为此,Shadow Cache将时间窗口划分为四个部分。每段都用不同的布隆过滤器进行追踪。一个新的布鲁姆过滤器被创建来跟踪最新的数据,在每次消除中替换最早的一个。最后,当需要提供工作集的大小时,我们使用Swamidass & Baldi(2007)提出的公式进行基础估计。
总的来说,Shadow Cache为Presto提供了四个方便的指标。H1、H2、C1和C2,其中H1和C1分别代表真实的缓存命中率和使用率,而H2和C2代表一段时间内缓存的极限命中率和用户的工作集大小。Presto可以快速确定缓存容量和应用性能之间的关系,并根据上述四个指标优化路由算法,以达到更好的平衡和效率。
请在GitHub上查看合并后的代码。
缓存(计算) 过滤器(软件) Presto(SQL查询引擎) 机器学习