延迟和窗户破碎的故事

2020-07-01 21:36:48

所有的文章,即使不是准确的和最新的2020年,它的主旨是明确的。有些操作(磁盘、网络)比其他操作更昂贵。在寻求最大限度地减少延迟时,请从更可能耗时较长的操作开始,因为由于硬件和光速的限制,这些操作自然会较慢。

延迟的重要性将取决于您正在编写的软件。对于某些软件延迟可能是业务关键型的:

当网站运行缓慢时亏损的电子商务(参见这篇关于亚马逊的很棒的博客文章)。

您可能还需要编写“足够快”或“不必要的慢”的软件。

虽然获得关键情况的具体延迟保证需要大量的工作和投资,但除非您注意,否则延迟也是很容易被忽视的事情之一。随着时间的推移,小决策可能会堆积起来,最终得到的产品可能会比应有的速度慢得多,这主要是因为偶然的复杂性,而不是本质的复杂性。

“破窗”理论很好地提出了这个问题(出自维基百科):破窗理论是一种犯罪学理论,它认为明显的犯罪迹象、反社会行为和公民骚乱创造了一个鼓励进一步犯罪和混乱的城市环境,包括严重的犯罪。该理论认为,针对破坏公物、游荡、公共饮酒、乱穿马路和逃票等轻微犯罪的警察方法有助于创造有序和合法的氛围,从而防止更严重的犯罪。

无论理论是否正确(在“魔鬼经济学”一书中有一个关于这一点的有趣章节),这都是谈论软件的一个有用的背景。在软件行业,实用程序员一书中写道“不要与破碎的窗口共存”。如果我们将这一点应用于延迟,很容易得出这样的结论:如果您让小的决策/更改增加了延迟,随着时间的推移,系统的延迟将随着时间的推移而不断增加。在某些情况下,回去解决问题会过于复杂,有时团队会决定寻找复杂的解决方案来解决这个问题。我们想要避免这种情况。

我们在Auth0做的一件事是迭代交付。代码的初始版本可以工作并经过测试等,但在我们与客户验证功能之前,我们会避免投资于优化。

但是,有些事情可以在初始实现中完成,因此我们不需要在以后进行优化,因为作为开发人员,您可以在那时了解您正在做的事情。几周后重新思考许多小决定并不理想。在这种情况下,明智地考虑“昂贵的操作”延迟是有用的。

作为项目的一部分,我们正在开发一个新服务(new-service),并从现有服务(客户端)添加一些调用。当我们与团队一起检查我们对客户所做的更改时,我们发现了几个改进的机会。

下面的代码片段是使用Javascript编写的示例,但是这些想法与语言无关。

当I/O操作不相互依赖并且您需要所有数据时,如果可能,请使用并行查询。

const a=等待callExistingService(.);+const b=等待callNewService(.);+//对a和b执行某些操作。

getDataFromNewService(.)。Call执行网络I/O。因为我们正在开发这个新服务,所以我们知道它还会执行更多的网络I/O操作来与数据库对话(可能需要执行一些磁盘I/O操作)。所有这些操作自然都属于本文开头表格中的慢端。

如果我们无法避免这些操作,那么并行执行这些操作会带来一些明显的好处。如果生成的代码如下所示:

如果我们将这些操作的时间称为T1(现有服务)和T2(新服务),则在上一个示例中,我们将延迟从sum(t1,t2)更改为max(t1,t2)。这甚至意味着,如果我们不想使延迟比原始延迟更高,我们所需要做的就是确保t2<;=t1!

当I/O操作OP2仅在OP1可能成功时才运行时,如果OP1在大多数情况下可能成功,请考虑并行执行OP2(预取)。

这个案例与前一个案例类似,但可能更容易遗漏。假设更改后的代码如下所示:

Const Param=.。常量EXISTS=等待callExistingService(参数);如果(!EXISTS){//处理边缘情况}+const b=等待callNewService(参数);+//对b执行某些操作。

这两个函数调用都依赖于param,该参数在调用callExistingService(Param)之前可用。如果eXist为false的可能性很低,那么并行调用callNewService(Param)可能是值得的。如果存在=false,您将不会向新服务添加更多负载,但是您将获得前面提到的并行I/O的好处。

Const Param=.。const[存在,b]=等待承诺。all([callExistingService(Param),callNewService(Param)]);IF(!EXISTS){//处理边缘大小写}//处理b。

最佳性能/延迟优化是“无操作(no-op)”,即找出如何防止调用发生。实现这一点的一种简单方法是缓存。但是,重要的是要考虑到缓存不需要过于复杂,并且需要每个开发人员最喜欢的锤子Redis。它只意味着存储计算值,以防止再次执行计算。

在这种情况下,客户端有一种方法可以存储上一次调用返回相同值的new-service的结果。如果结果相对较小且没有更改,则可以将其与上下文一起存储,并将其作为hyateContext()的一部分加载。这样,我们就可以简单地避免调用callNewService(context.param);。

在处理代码时,不要过早优化。然而,我建议你在提交公关前添加一份“心理清单”,重点关注延迟,并将其划掉。这会帮你找到唐纳德·努斯的3%。

很可能仅仅是这样做并专注于10分钟的延迟可能会表明您可以做一些小但有价值的改进,并随着时间的推移积累延迟。