[论文阅读]FlightTracker-Facebook跨网在线存储读一致性优化(5)

260 阅读18分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第32天,点击查看活动详情

[论文阅读]FlightTracker-Facebook跨网在线存储读一致性优化(5)

第47篇,终于到论文正文,这个章节继续FlightTracker内部设计信息,image-20211121172057495

Table 2: Client-measured latency of FlightTracker and FT-RI. 请注意上图,看见对于中间件FlightTracker的请求代价很小,我们曾经尝试学习Facebook实现flightTracker,其动力主要来自上面的代价指标,发现可以做到很小,just do it。对了,Facebook开源了Cachelib,这是他们用来做FT的组件吗? 论文见:www.usenix.org/conference/…

7 超越RYW:明确写可见性

所谓超越RYW,也就是session可以不止一个终端用户,而是多个session集合,或者某个region一致性。 image.png

某些应用程序需要超出单个最终用户或跨区域的可见性保证,而我们默认的以用户为中心的RYW一致性在这方面是不够的。为了获得所需的可见性保证,我们使它们能够明确地操作Ticket或定制FlightTracker会话ID。这些应用程序负责明确地识别写入的内容,并防止Ticket增长过大。

7.1 在通知中嵌入Ticket

当publisher发布事件发生时,Facebook的GraphQL Sub- scriptions通知基础设施[48]会向所有订阅者发出通知。为了给每个订阅者提供个性化的通知,GraphQL在订阅者区域查询TAO。这个pub-sub系统与TAO复制有竞争(race condition)。为了确保查询能看到与事件相关的所有写入内容,我们包括并传递了原始发布者的Ticket。在渲染通知时,GraphQL会将其与订阅者的Ticket瞬时连接起来,以查询TAO。这一切都隐藏在产品基础设施层中,不为产品开发者所知。

因为发布、订阅、通知的存储产品都是Tao,而Tao是最终一致性的产品,所以,他们需要FlightTracker来确保发布,订阅是RYW一致性的。

具有高订阅者扇出的订阅可能会导致一致性失误的风暴。虽然TAO只需要为这些数据跨区域一次,但许多请求会被拖延,等待结果。因此,我们在通知扇出前将Ticket中的数据预取到本地区域的TAO中。

7.2 从数据中获取额外session

当一个用户执行包括另一个人的用户ID的写入时,例如当Bob为Alice创建一个TRUSTS边缘时,该写入自然与另一个用户的FlightTracker会话相关。对于某些边缘类型,我们通过让客户端库执行额外的appendWrites调用来解决这个问题,将写入推送到正常的RYW会话和由目标节点确定的会话。这些额外的写入被发送到每个区域。我们不把整个写的用户的Ticket推送到数据派生的额外会话中;只有用户结束的边缘写得到加强的可见性保证。由于用户是高度连接的[7],这种保守的选择避免了写集的超线性增长的可能性。

一种简单的扩展了..

7.3 明确的全局sessions

一些应用程序需要超越单个最终用户或跨区域的可见性保证。我们允许他们定制他们的会话ID,并在FlightTracker中根据不同的使用情况配置quorum和compaction。

Facebook的异步作业调度框架,类似于Celery或Resque[1, 5],使网络请求能够调度后续作业,如发送电子邮件邀请或长期运行的迁移。这些作业可以在任何区域运行,但所有来自原始用户会话的写入内容必须是可见的。为了保证这一点,我们使用job_id作为会话ID,该ID对作为作业一部分的所有任务都是一样的。考虑到相关的读写比率,我们要求写被复制到所有区域的大多数副本,而读则从少数(通常是区域性的)副本读取。我们为作业框架提供了一个实用功能,以收集网络请求所做的写,并以适当的作业ID发送给FlightTracker。当一个作业开始时,它使用其作业ID从FlightTracker获取一个Ticket,此后使用包含Ticket的读取。

我们还对一些TAO对象使用全局会话,作为TAO的临界读取的替代。临界读取通过代理读取对象的数据库主区域来确保写的可见性,代价是增加延迟、降低效率和减少可用性。如果我们在一个全局会话中使用其ID记录对该对象的所有写入,我们就可以取代临界读取:查询区域-本地FlightTracker以获得该会话的Ticket,并使用包含Ticket的读取查询该对象,将返回最新的成功(或较新)写入。这种方法将跨区域的延迟转移到了写入时间,并增加了读取的可用性。

8 评价

FlightTracker允许Facebook获得读取效率、热点容忍度和最终一致性的高可用性,同时以丰富的用户会话概念提供RYW一致性,该概念跨越了许多有状态服务。

8.1 环境

Facebook每秒钟提供数百万次的用户请求。这些用户请求放大到每秒对我们的在线图形数据存储的100多亿次读取查询,这些数据存储还每秒处理数千万次写入。

我们的每一个有状态的数据存储,如TAO和它的索引,都被部署在超过十个数据中心区域。缓存和索引系统在每个区域都保持异步更新的副本,而数据库副本只存在于一些区域。99%以上的缓存和索引查询是在没有任何跨区域通信的情况下进行的。

image-20211121171057146

Figure 10: The size distribution of FlightTracker read responses in our production environment for different global compaction thresholds

图10:在我们的生产环境中,不同的全局compaction阈值下FlightTracker读取响应的大小分布

8.1 FlightTracker的操作特点

FlightTracker在生产中已经有四年多的时间。它为两种数据库技术、两种缓存类型(包括TAO)和三种索引系统管理RYW一致性。FlightTracker每秒提供超过1亿张Ticket的读取和2000万张Ticket的写入。

我们测量了客户在30天内观察到的FlightTracker的可用性。从客户端测量错误提供了一个端到端的情况,因为它包括由于错误配置、网络问题和其他问题造成的附带损害而导致的不可用性。FlightTracker的总体读取错误率为1.1乘以十的负十七次方。在检查15分钟的可用性数据时,在一个月的数据中,除了8个数据桶外,所有的数据都显示至少有99.9999%的读取可用性。Flight- Tracker的写可用性比基础数据库的写可用性高一个数量级。

8.3 FlightTracker的开销

请求和响应大小。 显式写跟踪技术的祸根是处理大型写集。从FlightTracker获取的Ticket包含了一个用户的所有最近的写,这些写没有被全局压缩(§ 4.2),所以它们往往是我们系统中传递的最大的显式写集。图10显示了生产中不同全局压缩阈值的元数据读取响应的大小分布,以返回的Ticket中的写的数量来衡量。在生产中,我们使用60s作为默认值,但如图所示,将其扩展到2分钟并不会使曲线明显弯曲。

Ticket的序列化包括使用LZ4[25]进行压缩。图11显示,这为有更多单独写的Ticket提供了有益的好处,将编码效率提高了三倍。表1显示,客户端的裁剪是有效的;附在读取查询中的Ticket大小比从FlightTracker中提取的全部写集小得多。

image-20211121172020070

Table 1: Serialized sizes of Tickets attached on various requests and responses, in bytes.

表1:各种请求和响应上附加的Ticket的序列化大小,以字节为单位。

image-20211121172057495

Table 2: Client-measured latency of FlightTracker and FT-RI.

表2:FlightTracker和FT-RI的客户测算延迟。

image-20211121172141330

Table 3: Relative CPU and memory costs of all code paths related to Ticket, FlightTracker, or FT-ReverseIndex.

表3: 所有代码路径的相对CPU和内存成本与Ticket、FlightTracker或FT-ReverseIndex相关的所有代码路径的相对CPU和内存成本

延迟: FlightTracker和FT-RI的读和写的延迟都很低,如表2所示。这两项服务都是只用RAM,并从本地数据中心区域处理所有的读和写。表中不包括定制用例的查询(§7.3)。

签名指纹(Footprint) FlightTracker的足迹包括客户端库中的额外工作和数据,数据存储中的额外工作和空间,以实现包含Ticket的读取,以及专门用于运行FlightTracker和FT-RI服务的服务器。表3显示,与FlightTracker有关的代码路径只消耗了客户端和支持Ticket的查询服务系统中的少量CPU和内存。Flight- Tracker和FT-RI服务使用的服务器数量不到TAO及其索引的2%。

可扩展性。 Ticket抽象被设计成可以扩展,以处理新的数据库和新的写元数据的编码方式,以利于新的预测。自从它被部署到生产中以来,我们已经对Ticket Thrift模式进行了22次修改,对核心Ticket连接逻辑进行了50次修改。扩展Ticket抽象以覆盖新的数据库不会增加序列化的大小,但会增加反序列化的C++对象的堆占用率。当我们增加对第二类数据库的支持时,FlightTracker的RAM消耗增加了5%。

8.4 FlightTracker的效率

缓存的RYW: FlightTracker使我们的缓存不再依赖固定的通信拓扑结构来提供RYW一致性。它提供了一个机会,可以应用额外的技术来提高我们缓存的效率和可靠性。

image-20211121172622947

Figure 11: LZ4 improves serialized encoding efficiency by up to a factor of three for Tickets with more individual writes.

图11:LZ4对有更多单独写入的Ticket来说,序列化编码的效率提高了三倍之多。

今天,0.2%的TAO读数有一个非空的Ticket,其中3%的读数是针对尚未通过每分片复制流复制的更新。我们为每个TAO实例分配了大约40MB的空间,用于缓存包含Ticket的读取结果,根据查询的类型,命中率在30%到80%之间。这个命中率并不是平均分布的:频繁读取的热门对象占了大部分的命中率。只有不到3%的TAO读数是由于一致性缺失而最终跨区域的。

在需要更强一致性保证的用例中,包含Ticket的读取取代了仅有主数据库的查询,从而减少了跨区域流量。在一个极端的用例中,缓存只跟踪每个分片的复制进度,包含Ticket的读取将上游查询的百分比从20-40%减少到接近0%。

索引的RYW: FlightTracker发现,0.01%到0.4%的索引读取可以从读取修复或其他滞后性处理策略中受益。

明确的用例。 在事件交付用例中(§7.1),3%的订阅者读取的是发布者最近写的数据。如果没有出版商的Ticket,这些读取中的0.5%会返回陈旧的数据。有六个用例受益于全局会话(§7.3),每秒总共有46000次写入和700000次读取。他们经常设置大的写入量,以优化读取的可用性和延迟。

8.4 经验和教训

早期的一个教训是,为一个网络请求确定适当的用户比我们最初预期的要困难得多。请求端点可能在登录前或注销后被调用;内部应用程序可能使用定制的机制跟踪用户上下文;应用程序可能涉及多个身份,例如当一个用户管理一个商业账户时。要获得较高的查询覆盖率,需要大量的手工工作来发现备用的用户上下文,并识别那些预计不会与用户关联的端点。

全局会话往往用于存储在TAO中的元数据,例如作为状态机建模的产品流。将全局会话添加到代码库中往往是在产品开发周期的后期进行的,以解决在最初设计中被忽视的问题。我们有能力在不进行数据迁移或模式改变的情况下,加强此类调用站点的写入可见性,这是使RYW成为合理的默认值的重要部分。

在操作上造成最大挑战的应用程序最不需要RYW一致性。这些往往是执行批处理或涉及大量扇出的内部应用。他们经常在数据存储中造成写的热点,但很少在事后阅读他们写的东西。

填补FlightTracker的一致性漏洞表明,底层系统实际上最终并不一致。我们已经发现了低概率的错误,这些错误导致了TAO、图索引、甚至数据库复制中的永久不一致。这些bug以前是很难被注意到的,因为它们的数量超过了瞬时不一致。包含Ticket的读取不应该返回旧数据,所以现在我们有了FlightTracker,即使出现一个陈旧的结果也是可以操作的。导致永久性不一致的缺陷包括协议缺陷、错误条件的不正确处理,以及依赖所有历史数据都不遵守的数据不变性。

9 局限性和未来的工作

我们的方法仍然依赖于区域粘性的用户路由。我们可以通过像第7.3节中那样始终使用全局性的四元组来避免这一限制,但这将增加延迟。我们计划通过在FlightTracker中维护一个从用户到区域的映射,最终使用户的RYW会话成为全局性的,当映射发生变化时,重新归属会话。

我们的解决方案的相对效率取决于在许多TAO查询中摊销元数据读取的成本,并取决于写的集合相对较小。读取次数较少的环境有一套不同的权衡方法。这种限制不太适用于索引查询,因为这些查询往往在每个操作中做更多的工作。

FlightTracker没有为 "未确认的成功 "提供一致性。如前文第5.3节所述,当数据写入出现超时等客户端错误或元数据写入失败时,就会出现未确认的成功。在实践中,我们没有看到这是一个问题,可能是因为即使没有FlightTracker,这个问题也存在。

有些查询是很难修复的。对大型边缘列表的TAO top-N查询会导致不必要的一致性缺失;对物化聚合(如计数)的索引查询可以被FT-RI检测为过时,但过时的结果不能用读修复来修复;涉及两个以上列表的列表相交查询也难以修复。

FlightTracker服务将Ticket压缩成一个tamp边界,因此,如果复制超过60s的全局压缩边界(§4.2),我们将采取一致性错误。全局compaction约束在发布时还没有完全展开,所以复制管道中的尾部延迟事件可能会导致违反RYW。这在实践中不是一个大问题,因为它要求写的第一次读发生在全局compaction间隔之后,但在复制之前。

虽然我们的一些动机是针对Face- book的工作负载的,但我们提供以用户为中心的会话的愿望是广泛认同的[42,55],正如我们希望将一致性保证扩展到全局索引一样[3, 12, 30, 34]。缓存失效也是所有规模的系统的一个长期挑战。

我们的FlightTracker方法是可推广的:为异构数据存储而设计,Ticket可以很容易地扩展到其他数据存储,而没有太多的开销(§4.1);数据存储需要实现的API扩展(图3)不需要改变核心复制协议,开销相对较小(§8.3);FlightTracker的大量逻辑所在的客户端库可以逐步实现和推出。在试图改造索引系统时,我们的方法尤其有利,因为它允许我们将反向元数据索引分离成自己的组件。

10 相关工作

更强的一致性在最终一致的存储上面。 许多最终一致的系统提供了选择更强一致性水平的选项。Cassandra[37]、Riak[6]和RedBlue[39]等系统通过将读取请求路由到领导者或调整其提交协议来提供强一致性。为了提供有限的滞后性,Azure CosmosDB [21]可以对写进行反压。相比之下,Flight- Tracker从一个单一的本地副本中提供大部分的读取。

索引的一致性。 这些系统中的大多数,包括Ama- zon的DynamoDB[11, 12]和Google AppEngine Datas- tore[31],都没有将更强的一致性水平扩展到全局二级索引。Twitter的Manhattan[49]将RYW扩展到全局二级索引,将其纳入跨分片的事务性写入,延迟加倍[34, 55]。Couchbase[3]支持使用客户端会话中的时间戳对其全局索引进行读取的RYW。它是通过确定地合并所有分片的更新来实现的,限制了分片数量的可扩展性。

Bailis等人[15]证明了索引一致性的实现可以比在通用事务中包含索引的方法具有更好的可用性特征。

实现RYW会话: 会话RYW[51]是直观的,可以用低开销实现[14, 27]。Bayou[28]和Pileus[52]通过在其客户端库中管理会话状态,提供跨越多个服务器的会话保证。Bermbach等人[19]同样注意到,以客户为中心的一致性应该关注终端用户;但他们的方法还是假设一个会话是粘在一个应用服务器上的。PathStore[42]通过在每个副本切换中使用会话迁移协议,解决了客户端与多个数据存储副本互动的相同挑战。相比之下,FlightTracker在客户端和数据服务器之间的一个中间层管理会话状态。

写入集跟踪以获得更强的一致性。 像COPS[40]和SwiftCloud[56]这样的系统追踪依赖性写集以提供因果一致性。他们还提供客户端上下文类似于FlightTracker会话。BoltOn[16]通过一个垫片在最终一致的存储上分层保证因果一致性。它的设计与FlightTracker的原则相似,旨在保留最终一致存储的理想属性。对于这些系统,依赖集需要存储在数据库中并在客户端进行缓存。

TxCache[46]为应用级缓存提供了事务性的(但可能是陈旧的)一致性,并使用术语滞后性缺失一致性缺失(两者都包含在我们使用的术语一致性缺失中)。它的设计侧重于单个数据中心,并将物化视图视为来自用户指定函数的可缓存结果,这对我们的应用来说是不够的。

为了减少元数据大小,像Occult[41]和Wukong+S[57]这样的系统使用结构或时间属性来压缩写集和矢量时间戳。FlightTracker使用CRDT膨胀来压缩Ticket,并修剪不相关的写入以减少网络开销,但主要是通过提供一个较弱的一致性水平来避免元数据大小的爆炸。

缓存命中率和一致性之间的权衡。 Zanz- ibar[44]是建立在Google的可线性化的Spanner[26]之上的,但是它选择了一个较弱的一致性模型暴露给客户,以提高其读取效率和延迟。它的zookies扮演了与FlightTracker Tickets类似的角色,封装了一致性信息,但它们只被Zanzibar自己使用。

CDRTquorum协议: 单轮Flight- Tracker协议的核心是CRDT[50],使用quorum代表[29]。Gryff[22]和CURP[45]类似地利用了写的交换性。因为FlightTracker不需要原子性,所以一个回合就足够了。

11 总结

本文介绍了FlightTracker,我们为Facebook的社交图提供RYW一致性的方法。Flight- Tracker在一个由异步复制的缓存、数据库副本和索引组成的读取优化的生态系统中运行。它保留了最终一致性的读取效率、热点容忍度和松散耦合的优势,并使我们能够规避在使用写入式缓存实现一致性时所遇到的扩展挑战。