Spiral在Facebook通过实时机器学习自动调节服务
为了更有效地优化众多服务,可以灵活地适应不断变化、相互联系的内部服务,我们开发了Spiral。Spiral这种系统充分利用实时机器学习的技术,在Facebook这等规模的环境下自动调节高性能的基础设施服务。由于用Spiral取代了手工调节的启发法,我们可以在短短几分钟内而不是几周内优化更新后的服务。
应对规模挑战的新方法
在Facebook,变化的步伐很快。Facebook代码库每隔几个小时被推送到生产环境DD比如前端的新版本,这是我们持续部署过程的一部分。当下变化万千,试图手动微调服务以保持峰值效率不切实际。手动重写缓存/许可/驱逐(caching/admission/eviction)策略及其他手动调节的启发法实在太难了。我们必须从根本上改变原有的软件维护观念。
为了有效地克服这个难题,系统需要变成自动调节,而不是依赖手动硬编码的启发法和参数。这种转变促使Facebook的工程师们以新的眼光看待工作:工程师们现在不再查看系统生成的图表和日志以验证正确高效的运行,而是用代码表达系统怎样才算正确高效地运行。今天,我们的工程师通过编程实现向自我调节系统提供反馈的方法,而不是指定如何计算请求的正确响应。
传统的缓存策略看起来像是带有分支的树,考虑到了对象的大小、类型及其他元数据,从而决定要不要缓存它。自动调节缓存将以不同的方式来实现。这种系统可以检查某对象的访问历史记录:如果之前从未访问过该对象,缓存它可能是个坏主意。在机器学习语言中,使用元数据(特征)和相关反馈(标签)来区分对象的系统将是“分类器”(classifier)。该分类器将用于针对进入缓存的对象做出决策,系统将持续被重新训练。这种持续的重新训练让系统得以在即使环境发生了变化也能保持时效性。
从概念上来讲,这种方法类似声明性编程。SQL就是这种方法的一个典型案例:工程师不用指定如何计算复杂查询的结果,只要指定需要计算的内容,然后引擎就会找出最佳查询并执行查询。
对系统使用声明性编程的挑战在于,确保正确完整地指定目标。与上述的自动调节图像缓存策略一样,如果什么对象应该缓存、什么不应缓存方面的反馈不准确或不完整,系统会很快学会提供不正确的缓存决策,这会降低性能。
几位谷歌工程师撰写的这篇论文(https://storage.googleapis.com/pub-tools-public-publication-data/pdf/43146.pdf)详细介绍了这个问题以及与使用闭环机器学习有关的其他问题。根据我们的经验,精准定义自动调节所需的结果是入手Spiral方面最棘手的部分之一。然而我们还发现,几次迭代之后,工程师往往在清晰正确的定义上观点一致。
Spiral:易于集成和实时预测
为了让Facebook的系统工程师能够跟上越来越快的变化步伐,Facebook波士顿办事处的工程师们构建了Spiral,这是一个小巧的嵌入式C++库,依赖项极少。Spiral使用机器学习为资源受限的实时服务创建数据驱动的反应式启发法。与手工编码的替代方法相比,该系统大大加快了开发和自动维护这些服务的速度。
与Spiral集成包括只需为代码添加两个调用点(call site):一个用于预测,一个用于反馈。预测调用点是用于做出决策的智能启发法的输出,比如“应该许可该条目进入缓存吗?”预测调用作为快速本地计算来实现,旨在针对每个决策来执行。
图1
反馈调用点用于提供偶尔的反馈,比如“该条目因根本未命中而失效退出缓存,所以我们可能不应该缓存这样的条目。”
图2
库可以在完全嵌入的模式下运行,或者可以向Spiral后端服务发送反馈和统计数字,该后端服务可显示用于调试的实用信息,将数据记录到长期存储供以后分析,以及执行训练和选择模型方面的繁重任务,这些模型在嵌入模式下训练起来太耗费资源,但运行起来并不太耗费资源。
图3
发送到服务器的数据采用反偏差采样,避免类别不平衡偏差渗入到样本中。比如说,如果一段时间内我们收到的负样本比正样本多1000倍,我们只要将1000个负样本中的1个记录到服务器,同时表示它的权重为1000。服务器深入了解数据的全局分布,通常带来比任何一个节点的本地模型更好的模型。除了链接到库和使用上面两个函数外,这一切都不需要任何设置。
在Spiral中,一旦反馈进入,学习就开始。随着生成的反馈越多,预测质量会逐渐提高。在大多数服务中,可在几秒钟到几分钟内获得反馈,所以开发周期很短。主题专家可以添加新的特征,在几分钟内看看它是否有助于改进预测质量。
与硬编码的启发法不同,基于Spiral的启发法可以适应不断变化的条件。以缓存许可策略为例,如果某些类型的条目不太频繁地请求,反馈将重新训练分类器,以减小在不需要人为干预的情况下许可这类条目的可能性。
案例研究:反应式缓存启发法实现自动化
Spiral的第一个生产级用例恰好吻合Phil Karlton的著名引言:“计算机科学界只有两个难题:缓存失效和命名。”(我们已为自己的项目取了贴切的名称,所以我们实际上使用Spiral立即解决了缓存失效。)
在Facebook,我们推出了一个反应式缓存,以便Spiral的“用户”(我们的其他内部系统)订阅查询结果。从用户的角度来看,该系统提供查询结果和针对该结果的订阅。只要外部事件影响查询,它自动将更新后的结果发送到客户端。这为客户端减轻了轮询的负担,并减轻了计算查询结果的Web前端服务的负载。
用户提交查询时,反应式缓存先将查询发送到Web前端,然后创建订阅,缓存并返回结果。除了原始结果外,缓存还收到计算结果时涉及的对象和关联列表。然后,它开始监控针对查询访问的任何对象或关联源源不断的数据库更新。每当它看到可能影响其中一个活跃订阅的更新,反应式缓存会重新执行查询,并将结果与缓存内容进行比较。如果结果确实发生变化,它将新结果发送到客户端,并更新自己的缓存。
该系统面临的一个问题是,有大量的数据库更新,但只有很小一部分影响查询的输出。如果某个查询想知道“我的哪些朋友赞过此帖?”,没必要获得“该帖最近一次查看”方面的持续更新。
这个问题类似垃圾邮件过滤:面对一封邮件,系统应该将它分类成垃圾邮件(不影响查询结果)还是非垃圾邮件的正常邮件(确实影响查询结果)?第一个解决方法是,手动创建静态黑名单。这是可行的,因为反应式缓存工程团队认识到超过99%的负载来自一小组查询。对于低容量查询而言,他们只是假设所有更新都是正常邮件,重新执行查询,以便对象的每次更新被查询引用。针对一小组高容量查询,他们创建了黑名单,为此精心观察查询执行,确定每个对象中的哪些字段实际上影响了查询的输出。对于每份黑名单,这个过程通常需要工程师花几周时间。使情况进一步复杂化的是,这一组高容量查询在不断变化,因此黑名单很快过时了。每当使用缓存的服务改变它所执行的查询,系统就要改变垃圾邮件过滤策略,这需要工程团队更多的工作量。
一种更好的解决方案:Spiral垃圾邮件过滤
重新执行查询后,只要将新的查询结果与旧的查询结果进行比较,就很容易确定观察到的更新是垃圾邮件还是正常邮件。该机制用于向Spiral提供反馈,以便它为更新创建分类器。
为了确保无偏差采样,反应式缓存维护并只提供来自一小组订阅的反馈。缓存并不过滤这些订阅的更新;只要修改了相关对象或关联,就重新执行查询。它将新的查询输出与缓存版本进行比较,然后用它来向Spiral提供反馈DD比如说,告诉它更新“上一次查看”并不影响“点赞数”。
Spiral从所有反应式缓存服务器收集该反馈,并用它为每种不同类型的查询训练分类器。这些分类器定期推送到缓存服务器。为新查询创建过滤器或更新过滤器以响应Web层不断变化的行为不再需要工程团队的任何手动干预。新查询的反馈到来后,Spiral会自动为那些过滤器创建新的分类器。
Spiral:更快的部署和更多的机会
使用一种基于Spiral的缓存失效机制,反应式缓存中支持新查询所需的时间从几周缩短到几分钟。在Spiral之前,反应式缓存工程师必须通过运行实验和手动收集数据来检查每个新查询的副作用。但是有了Spiral,大多数用例(映射到查询)可由本地模型在几分钟内自动学习,所以可以立即获得本地推断。
对于大多数用例而言,服务器能够在10到20分钟内使用来自多台服务器的数据训练模型。一旦发布到所有单台服务器,这个更高质量的模型可用于精准度改进的推断。查询更改后,服务器能够适应变更,一旦收到更新后的查询,就重新学习新的重要性模式。