OceaBase开发者大会落地上海!4月20日共同探索数据库前沿趋势!报名戳 了解详情
写点什么

规模化时间序列数据存储 Part1

  • 2018-03-18
  • 本文字数:4485 字

    阅读完需:约 15 分钟

引言

因特网互联设备的发展,提供了大量易于访问的时序数据。越来越多的公司有兴趣去挖掘这类数据,意图从中获取一些有意义的洞悉,并据此做出决策。技术的最新进展提高了时序数据的收集、存储和分析效率,激发了人们对如何处理此类数据的考量。然而,大多数现有时序数据体系结构的处理能力,可能无法跟上时序数据的爆发性增长。

作为一家根植于数据的公司,Netflix 已习惯于面对这样的挑战,多年来一直在推进应对此类增长的解决方案。该系列博客文章分为两部分发表,我们将分享 Netflix 在改进时序数据存储架构上的做法,如何很好地应对数据规模的成倍增长。

时序数据:会员视频观看记录

每天,Netflix 的全部会员会观看合计超过 1.4 亿小时的视频内容。观看一个视频时,每位会员会生成多个数据点,存储为视频观看记录。Netflix 会分析这些视频观看数据,实时准确地记录观看书签,并为会员提供个性化推荐。具体实现可参考如下帖子:

视频观看的历史数据将会在以下三个维度上取得增长:

  1. 随时间的推进,每位会员会生成更多需要存储的视频观看数据。
  2. 随会员数量的增长,需要存储更多会员的视频观看数据。
  3. 随会员每月观看视频时间的增加,需要为每位会员存储更多的视频观看数据。

Netflix 经过近十年的发展,全球用户数已经超过一亿,视频观看历史数据也在大规模增长。这篇博客帖子将聚焦于其中的一个重大挑战,就是我们的团队是如何解决视频观看历史数据的规模化存储的。

基本架构的初始设计

最初,Netflix 的云原生存储架构使用了 Cassandra 存储观看历史数据。团队是出于如下方面的考虑:

  • Cassandra 对时序数据的建模提供了很好的支持,支持一行中的列数动态可变。
  • 在观看历史数据上,读操作和写操作的数量比大约为 1:9。因为 Cassandra提供了非常高效的写操作,特别适用于此类写密集的工作负载。
  • CAP 定理方面考虑,相对于可用性而言,团队更侧重于实现最终一致性。Cassandra 支持可调整的一致性,有助于实现 CAP 上的权衡。

在最初的架构中,使用 Cassandra 存储所有会员的观看历史记录。其中,每位会员的观看记录存储为一行,使用CustomerId标识。这种水平分区设计支持数据存储随会员数量的增长而有效扩展,并支持简单并高效地读取会员的完整观看历史数据。这一读取操作是历史数据存储上最频繁发生的操作。然而,随着会员数量的持续增长,尤其是每位会员观看的视频流越来越多,存储的数据行数和整体数据量也日益膨胀。随着时间的推移,这将导致存储和操作的成本增大。而且对于观看了大量视频的会员而言,性能会严重降低。

下图展示了最初使用的数据模型中的读操作和写操作流。


图 1:单表数据模型

写操作流

当一位会员开始播放视频时,一条观看记录会以一个新列的方式插入。当会员暂停或停止观看视频流时,观看记录会做更新。在 Cassandra 中,对单一列值的写操作是快速和高效的。

读操作流

为检索一位会员的所有观看记录,需要读取整行记录。如果每位会员的观看记录数量不大,这时读操作是高效的。如果一位会员观看了大量的视频,那么他的观看记录数量将会增加,即记录的列数增加。读取一个具有大量列的数据行,会对 Cassandra 造成了额外压力,进而对读操作延迟产生负面影响。

要读取一段时间内的会员数据,需要做一次时间范围查询。这同样会导致上面介绍的性能不一致问题。因为查询性能依赖于给定时间范围内的观看记录数量。

如果要查看的历史数据规模很大,需要做分页才能进行整行读操作。分页对 Cassandra 更好,因为查询不需要等待所有数据都就绪,就能返回给用户。分页也避免了客户超时问题。但是,随着观看记录的增长,分页增加了读取整行的整体延迟。

延迟的原因

下面介绍一些 Cassandra 的内部机制,进而理解为什么我们最初的简单设计会产生性能下降。随着数据的增长, SSTable 的数量也随之增加。因为只有最近的数据是维护在内存中的,因此在很多情况下,检索观看历史记录时需要同时读取内存表和SSTable 。这对于读取延迟具有负面影响。同样,随着数据的增长,合并(Compaction)操作将占用更多的IO 和时间。此外,随着一行记录越来越宽,读修复(Read repair)全列修复(Full column repair)也会变慢。

缓存层

Cassandra 可以很好地对观看数据执行写操作,但是需要改进读操作上的延迟。为优化读操作延迟,我们考虑以增加写路径上的工作为代价,在 Cassandra 存储前增加了一个内存中的分片缓存层(即 EVCache )。缓存实现为一种基本的键 - 值存储,键是CustomerId,值是观看历史数据的二进制压缩表示。每次 Cassandra 的写操作,将额外生成一次缓存查找操作。一旦缓存命中,直接给出缓存中的已有值。对于观看历史记录的读操作,首先使用缓存提供的服务。一旦缓存没有命中,再从 Cassandra 读取条目,压缩后插入到缓存中。

在添加了缓存层后,多年来 Cassandra 单表存储方法一直工作很好。在 Cassandra 集群上, 基于CustomerId的分区提供了很好的扩展。到 2012 年,查看历史记录的 Cassandra 集群成为了 Netflix 的最大专用 Cassandra 集群之一。为进一步实现存储的规模化,团队需要实现集群的规模翻番。这意味着,团队需要冒险进入 Netflix 在使用 Cassandra 上尚未涉足的领域。同时,Netflix 业务也在持续快速增长,其中包括国际会员的增长,以及企业即将推出的自制节目业务。

重新设计:实时存储和压缩存储

很明显,为适应未来五年中企业的发展,团队需要尝试多种不同的方法去实现存储的规模化。团队分析了数据的特征和使用模式,重新定义了观看历史存储。团队给出了两个主要目标:

  • 更小的存储空间;
  • 考虑每位会员观看视频的增长情况,提供一致的读写性能。

团队将每位会员的观看历史数据划分为两个数据集:

  • 实时 / 近期观看历史记录(LiveVH):一小部分频繁更新的近期观看记录。LiveVH 数据以非压缩形式存储,详细设计随后介绍。
  • 压缩 / 归档观看历史记录(CompressedVH):大部分很少更新的历史观看记录。该部分数据将做压缩,以降低存储空间。压缩观看历史作为一列,按键值存储在一行中。

为提供更好的性能,LiveVH 和 CompressedVH 存储在不同的数据库表中,并做了不同的优化。考虑到 LiveVH 更新频繁,并且涉及的观看记录数量不大,因此可对 LiveVH 做频繁的 Compaction 操作。并且为了降低 SSTable 数量和数据规模,可以设置很小的 gc_grace_seconds 。为改进数据的一致性,也可以频繁执行读修复全列族修复(full column family repair)。而对于 CompressedVH,由于该部分数据很少做更新操作,因此为了降低 SSTable 的数量,偶尔手工做完全 Compaction 即可。在偶尔执行的更新操作中,会检查数据一致性,因此也不必再做读修复以及全列族修复。

写操作流

对于新的观看记录,使用同上的方法写入到 LiveVH。

读操作流

为有效地利用新设计的优点,团队更新了观看历史 API,提供了读取近期数据和读取全部数据的选项。

  • 读取近期观看历史:在大多数情况下,近期观看历史仅需从 LiveVH 读取。这限制了数据的规模,进而给出了更低的延迟。
  • 读取完整观看历史:实现为对 LiveVH 和 CompressVH 的并行读操作。

考虑到数据是压缩的,并且 CompressedVH 具有更少的列,因此读取操作涉及更少的数据,这显著地加速了读操作。

CompressedVH 更新流

在从 LiveVH 读取观看历史记录时,如果记录数量超过了一个预设的阈值,那么最近观看记录将由后台任务打包(roll up)、压缩并存储在 CompressedVH 中。打包数据存储在一个行标识为CustomerId的新行中。新打包的数据在写入后会给出一个版本,用于读操作检查数据的一致性。只有验证了新版本的一致性后,才会删除旧版本的打包数据。出于简化的考虑,在打包中没有考虑加锁,由 Cassandra 负责处理非常罕见的重复写问题(即以最后写入的数据为准)。


图 2:实时数据和压缩数据的操作模型

如图 2 所示,CompressedVH 的打包行中还存储了元数据信息,其中包括最新版本信息、对象规模和分块信息,细节稍后介绍。记录中具有一个版本列,指向最新版本的打包数据。这样,读取CustomerId总是会返回最新打包的数据。为降低存储的压力,我们使用一个列存储打包数据。为最小化具有频繁观看模式的会员的打包频率,LiveVH 中仅存储最近几天的观看历史记录。打包后,其余的记录在打包期间会与 CompressedVH 中的记录归并。

通过分块实现自动扩展

通常情况是,对于大部分的会员而言,全部的观看历史记录可存储在一行压缩数据中,这时读操作流会给出相当不错的性能。罕见情况是,对于一小部分具有大量观看历史的会员,由于最初架构中的同一问题,从一行中读取 CompressedVH 的性能会逐渐降低。对于这类罕见情况,我们需要对读写延迟设置一个上限,以避免对通常情况下的读写延迟产生负面影响。

为解决这个问题,如果数据规模大于一个预先设定的阈值,我们会将打包的压缩数据切分为多个分块,并存储在不同的 Cassandra 节点中。即使某一会员的观看记录非常大,对分块做并行读写也会将读写延迟控制在设定的上限内。


图 3:通过数据分块实现自动扩展

写操作流

如图 3 所示,打包压缩数据基于一个预先设定的分块大小切分为多个分块。各个分块使用标识CustomerId$Version$ChunkNumber并行写入到不同的行中。在成功写入分块数据后,元数据会写入一个标识为CustomerId的单独行中。对非常大的打包观看数据,这一做法将写延迟限制为两次写操作。这时,元数据行实现为一个不具有数据列的行。这种实现支持对元数据的快速读操作。

为加快对通常情况(即经压缩的观看数据规模小于预定的阈值)的处理,我们将元数据与观看数据合并为一行,消除查找元数据的开销,如图 2 所示。

读操作流

在读取时,首先会使用行标识CustomerId读取元数据行。对于通常情况,分块数是 1,元数据行中包括了打包压缩观看数据的最新版本。对于罕见情况,存在多个压缩观看数据的分块。我们使用元数据信息(例如版本和分块数)对不同分块生成不同的行标识,并行读取所有的分块。这将读延迟限制为两次读操作。

改进缓存层

为了支持对大型条目的分块,我们还改进了内存中的缓存层。对于存在大量观看历史的会员,整个压缩的观看历史可能无法置于单个 EVCache 条目中。因此,我们采用类似于对 CompressedVH 模型的做法,将每个大型缓存条目分割为多个分块,并将元数据存储在首个分块中。

结果

在引入了并行读写、数据压缩和数据模型改进后,团队达成了如下目标:

  1. 通过数据压缩,实现了占用更少的存储空间;
  2. 通过分块和并行读写,给出了一致的读写性能;
  3. 对于通常情况,延迟限制为一次读写。对于罕见情况,延迟限制为两次读写。


图 4:运行结果

团队实现了数据规模缩减约 6 倍,Cassandra 维护时间降低约 13 倍,平均读延迟降低约 5 倍,平均写时间降低约 1.5 倍。更为重要的是,团队实现了一种可扩展的架构和存储空间,可适应 Netflix 观看数据的快速增长。

在该博客系列文章的第二部分中,我们将介绍存储规模化中的一些最新挑战。这些挑战推动了会员观看历史数据存储架构的下一轮更新。如果读者对解决类似问题感兴趣,可加入到我们的团队中

查看英文原文: Scaling Time Series Data Storage — Part I

感谢蔡芳芳对本文的审校。

公众号推荐:

2024 年 1 月,InfoQ 研究中心重磅发布《大语言模型综合能力测评报告 2024》,揭示了 10 个大模型在语义理解、文学创作、知识问答等领域的卓越表现。ChatGPT-4、文心一言等领先模型在编程、逻辑推理等方面展现出惊人的进步,预示着大模型将在 2024 年迎来更广泛的应用和创新。关注公众号「AI 前线」,回复「大模型报告」免费获取电子版研究报告。

AI 前线公众号
2018-03-18 18:211875
用户头像

发布了 391 篇内容, 共 126.7 次阅读, 收获喜欢 255 次。

关注

评论

发布
暂无评论
发现更多内容

OH应用程序集成AGC认证服务实现邮箱登录

白晓明

OpenHarmony AGC认证服务

大数据培训学习后没有面试机会怎么办

小谷哥

教育行业数据可视化应用方案与实践

葡萄城技术团队

前端 数据可视化 智慧医疗 智慧工程

【jquery Ajax 练习】图书管理

坚毅的小解同志

ajax 11月月更

还不会正则表达式? 放心 我会出手(万字教学)

坚毅的小解同志

正则表达式 11月月更

【Node.js】模块化学习

坚毅的小解同志

node.js 11月月更

眼见为实:ForkJoin的“分而治之”,竟然有隐藏的坑?

KINDLING

Java Linux 多线程 forkjoin ebpf

【jquery Ajax】接口的学习与Postcode插件的使用

坚毅的小解同志

ajax 11月月更

干货|无源元件之——电阻器基础知识(超全)

元器件秋姐

【jquery Ajax 】form表单教学+评论案例

坚毅的小解同志

11月月更

【jquery Ajax】基础概念与使用教学

坚毅的小解同志

ajax 11月月更

Meta force2.0佛萨奇矩阵公排系统开发合约部署搭建

开发微hkkf5566

数据报告 | 新冠疫情对美国民众消费行为的影响

前嗅大数据

疫情 数据分析 爬虫 数据采集 消费

ZETA精彩亮相2022 IOTE展 以全栈式物联生态赋能产业数智化转型

ZETA开发者

物联网 ZETA 物联网技术 LPWAN 技术融合

【jquery Ajax 】art-template模板引擎案例——新闻列表

坚毅的小解同志

ajax 11月月更

【jquery Ajax 】art-template模板引擎的概念与使用

坚毅的小解同志

jquery ajax 11月月更

【Node.js】npm与包【万字教学~超超超详细】

坚毅的小解同志

node.js 11月月更

眼见为实:关于微服务熔断这几个知识点,你可能理解错了

KINDLING

Java 微服务 熔断 SpringCloud ebpf

眼见为实:被误导的Tomcat的工作原理

KINDLING

Java tomcat 多线程 ebpf

大数据的5V特征分别是什么?

好程序员IT教育

大数据 V5

当Kubernetes遇见Macvlan——实现CNI路由插件

陆云

Kubernetes 集群

盘点 | 云原生峰会重磅发布

阿里巴巴云原生

阿里云 云原生

云原生技术中台 CNStack2.0 正式发布

阿里巴巴云原生

阿里云 云原生 CNStack 云原生中台

前端培训和自学的哪种方式更合适?

小谷哥

大数据培训和自学怎么学习

小谷哥

和鲸科技入选2022年度数字化创新服务商丨Digital 36 调研发布

ModelWhale

云计算 大数据 数字化转型 数据智能 企业服务

eBPF程序摄像头——力争解决可观测性领域未来最有价值且最有挑战的难题

KINDLING

Java Linux 监控 可观测 #ebpf

曳影1520已成功运行Anolis OS!详聊平头哥在RISC-V软件生态的探索和实践 | 龙蜥技术

OpenAnolis小助手

操作系统 芯片 risc-v 龙蜥峰会 平头哥

【Node.js】模块的加载机制

坚毅的小解同志

node.js 11月月更

无序和混乱终结者,极狐GitLab Workflow 到底有什么魔力?

极狐GitLab

DevOps Code Review CI/CD workflow 极狐GitLab

【区块链Go】基础语法

坚毅的小解同志

#go 11月月更

规模化时间序列数据存储Part1_语言 & 开发_Netflix_InfoQ精选文章