亚马逊AWS官方博客

基于 IoT 数据平台案例看 EMR HBase BucketCache 调优

在上一篇 blog(https://aws.amazon.com/cn/blogs/china/emr-hbase-gc-optimization-based-on-iot-data-platform-case/)中,我们提到当 HBase 使用 Amazon S3 作为底层存储时,可以通过预热 BucketCache 来提升读取性能。随着 BucketCache 大小的增加,内存占用也呈正比上升。那么,BucketCache 对于性能的提升有多少?这些提升能否弥补 cache 带来的额外内存与算力成本呢?在这篇 blog 中,我们将讨论这一问题。

BucketCache 的优势

EMR on S3 提供了显著的数据存储成本优势和可用性优势。然而, 作为一个独立的对象存储服务,S3 在 I/O 性能方面与本地存储相比仍然存在一定的差距。这种差距可能会影响数据处理的效率,尤其是在需要频繁访问或实时处理数据的场景中。此外,由于 S3 不能像 EBS 一样直接更改吞吐与 IO,我们需要通过引入本地存储的缓存机制来提升数据的读写性能,从而减少对 S3 的直接访问次数,降低延迟并提高吞吐量。这就是 BucketCache 的设计初衷。

BucketCache 的优势如下:

  • 成本与可用性:BucketCache 可以自定义本地盘容量,并且由于原始数据在 S3 中,可以采用单副本模式,限制降低本地存储成本,整个集群可用性与 S3 的可用性保持一致。
  • 缓存机制:通过引入 BucketCache 作为本地存储的缓存机制,可以减少对 S3 的直接访问次数,降低延迟并提高吞吐量。当启用 BucketCache 后,如果读取请求命中缓存,数据可以直接从本地磁盘(如 EBS)中获取,从而极大提升了读取性能。
  • 动态调整:BucketCache 默认采用 LRU(最近最少使用)策略进行内存管理和淘汰机制,根据实际请求动态调整缓存内容,确保最常用的数据始终保留在缓存中,从而优化资源利用率并提升整体性能。

在 EMR on S3 使用 BucketCache 之后,数据的流向如下图示意:

可以看到,enable BucketCache 后,read 请求如果命中缓存,可以直接从 Local Disk(EBS) 中获取数据,极大提升了读请求的性能。在实际实践中,BucketCache 的读性能优于 HDFS,显著加快数据访问速度。在官方文档 https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-hbase-s3.html 中,也明确提出 To improve performance, we recommend that you cache as much of your dataset as possible in EC2 instance storage(为了提高性能,我们建议您尽可能将数据集的更多部分缓存到本地存储,也就是 Bucketcache 中。)

BucketCache 的冷启问题

首先,用户是一个基于 IoT 的交互查询与报表 workload。如前文所述,BucketCache 在默认配置下,需要有一个读请求让 S3 block 加载到 EBS 的 cache block 上,后续读请求才能命中缓存并性能提升。很遗憾,这并不满足用户的场景需求:由于每次终端用户都只会请求一周内的新数据,并且绝大部分的请求都是非重复的,因此 BucketCache 默认配置下的冷启问题变得难以接受:如果百分之九十以上的请求都不能命中缓存,那么缓存机制有什么意义呢?

因此,我们仔细研究了 BucketCache 的缓存机制,发现有多个参数可以控制缓存方式,其中有两个参数: hbase.rs.cachecompactedblocksonwrite 和 hbase.rs.cacheblocksonwrite。它们的具体含义如下:

  • hbase.rs.cacheblocksonwrite:此参数用于控制在写入时是否将 HFile 块添加到块缓存中。设置为 true 时,所有写入的块都会自动添加到缓存中。
  • hbase.rs.cachecompactedblocksonwrite:此参数则用于控制在进行数据压缩时,压缩后的块是否仍然保留在缓存中。

这两个参数都可以在数据写入时进行同步写入 cache,区别为是 Hfile 写入还是 compact(压缩)后写入。

为了提升查询效率,实时表 enable 了 minor compaction(实时的数据聚合和压缩功能,可以聚合小 HFile,减少 IO 开销,降低成本),而 minor compaction 是持续进行的,因此一旦原始 Hfile 完成 compaction,就无法命中 BucketCache。

随后,我们测试了同时 enable cacheblocksonwrite 和 cachecompactedblocksonwrite。但是很快发现,在存储和算力都非常极限的前提下,同时 cache HFile 和 compact block 会极大影响集群性能。

因此,我们继续测试 cacheblocksonwrite 保持为默认 False,cachecompactedblocksonwrite 调为 True。随之而来的问题是:如果刚刚好在数据刚刚写入 HFile,还没有来得及 minor compaction 的时候用户进行查询,查询性能是否会不可接受?

幸运的是,在用户业务场景下,大部分用户进行的查询请求都在一个相对固定的时间段,足够绝大多数的数据 compaction 完成。而定时报表任务也都是每小时/每天一次,频率不高。再加上绝大多数读请求都落在一个月内,超过一个月的请求也都可以改为异步回调,因此通过只 enable cachecompactedblocksonwrite,缓存命中率可达到 90% 以上,同时让用户在有限的预算下可以留出足够的 EBS 与内存空间。

BucketCache 的资源占用

在上线前进行压力测试时,整个 HBase 已经满足用户需求:S3 带来成本与可用性优化,BucketCache 带来性能提升。然而,当系统上线一段时间后,我们发现了明显的 GC 问题,同时业务侧开始报错 timeout。

我们注意到一个有趣的现象:每次重启工作节点后,GC 问题都会缓解一阵,大约一周后问题会复现。我们了解 HBase 的堆外内存大部分用于 BucketCache,由于堆外不受 JVM 控制,随着 Cache 的数据增多,这部分内存会逐步增加直到触发 eviction。但是,我们不理解在计算好 cachesize 后,GC 问题仍然随着 cache 上升越来越明显,最后影响 jvm 的其他 workload。

BucketCache 为什么会影响 GC?根据后台工程师的解答,BucketCache 通过将存储块分为多个“桶”来管理缓存,每个桶都有一个目标块大小。当数据被读取时,即使是从堆外缓存中读取,也需要在堆内存中分配空间来处理这些块。这种从堆外到堆内的转换会产生额外的内存开销,并可能导致 GC 频繁触发。后台建议 cache 总大小在10T以下,但是这不满足用户场景需求。最后通过持续降低 BucketCache 的 cachesize 参数,最终我们确认 cache size 为 20T。

此外,我们也对 BucketCache 写入的队列长度与并发进行了优化。总体而言,BucketCache 在提升 HBase 性能方面具有显著优势,通过合理配置和优化,能够实现比 HDFS 更高效的数据处理和查询响应,同时通过 S3 实现低成本与高可用性存储。

本篇作者

赵安蓓

亚马逊云科技解决方案架构师,负责基于亚马逊云科技的解决方案咨询和设计,机器学习 TFC 成员。在数据处理与建模领域有着丰富的实践经验,特别关注医疗领域的机器学习工程化与运用。