一行代码价值百万美元:从工程技术角度看云成本优化
没有比现在成为软件开发者更好的时刻,也从来没有哪个时刻可以像现在这样,一个工程师能拥有如此大的影响力,一行代码就能决定一个组织的财务走向。和许多人一样,我一直热衷于开发高效的软件。然而,在以云为中心的世界里,效率不再仅仅关乎性能。我们现在所做的按需计算和基础设施选择都需要实实在在的资金投入,忽视了这一点可能会非常危险。
每一个工程决策都是一个购买决策
每一个工程决策都是一个购买决策。相比在云端的花费,人们可能更关注你今天在晚餐或午餐上的花费。财务部门的某些人会盯着那50美元的午餐,却没有人盯着你的工程师在云计算上花费的10000美元。这很难理解,因为在过去,CTO、CIO 和 CFO 会监督采购流程,而如今,一个初级工程师在采购方面比公司里的任何人都拥有更多的自主权。
当今的世界正处于云计算成本时刻。经过多年的大规模增长,人们的关注点已经从不惜一切代价的增长转向了高效、有利可图的增长。有些人在想,也许云计算是个错误,也许它是个骗局。如果我们迁移到了云端,然后发现需要退出,该怎么办?对被云厂商锁定的恐惧导致许多人一只脚仍然踩在数据中心里,而且许多人发现这种方法成本很高。许多人已经发现,提升(lift)和转移(shift)成本极高(这可能是云计算最大的谎言)。这到底是怎么回事?云计算是个骗局吗?不幸的是,正是对云计算及其成本的恐惧,导致了这种云计算浪费的预言成为现实。
破釜沉舟
在今天和明天的经济环境中生活的我们,都需要明白,要构建伟大的软件,这些软件必须是盈利的。
云计算不只是别人的计算机那么简单,它是一个操作系统,一个全新的平台。就像科尔特斯征服新大陆一样,如果我们想要成功,就必须破釜沉舟,忘掉回家的路。然而,许多人仍在为昨天的大型机编写代码,没有意识到如果要最大限度地利用云计算,就需要重写代码。在 DevOps 运动开始之前,我们会把代码扔给运维人员,然后去解决下一个问题。现在,我们编写代码,然后扔给财务部门,让他们去操心。生活在这些经济体中的所有人都需要明白,我们要构建出色的软件,并且它们必须能够盈利。
许多软件仍在等待部署到云端。据估计,目前仍有4.6万亿美元的 IT 支出用于数据中心运行。尽管云计算规模在不断增长,但仍处于早期阶段。
我们还有很多事情要弄清楚。如果我们要迁移到云端,它必须具有强大的经济意义。有些人坚信这是不可能的,有些人坚信这完全是个错误。我知道这些人是错的。毕竟我已经在云端,我想继续留在那里。但我也希望在有生之年看到这一天的到来。不幸的是,即使云计算每年以50% 的速度增长,如果我们不开始用不同的方式构建软件,恐怕我们谁都活不到看到它到来的那一天。
之所以存在这样的讨论,是因为我们在构建软件时还不太清楚云计算是否具有强大的经济意义。
我们必须改变这种状况。我已经看到了数据,我可以告诉你云计算具有强大的经济意义。我已经看到了这一点,但你必须用不同的方式构建软件,编写不一样的代码,并以不同的方式思考系统设计。你不能只是将在数据中心中有效运行的东西直接搬到云端,然后期望得到一个好的结果,你必须对此有不一样的思考。
工程师在软件盈利能力中所扮演的角色
如今,成本效率常常可以反映出系统的质量。一个架构良好的系统是一个具有成本效益的系统。一行代码就能决定你所工作的公司是否盈利。
我们面临着一个共同的挑战,必须找出衡量成本效率的最佳方法。为此,我想深入代码层面。我本质上是一名工程师,所以我收集了一些价值百万美元的代码示例(在某些情况下甚至价值数百万美元)来展示烧钱是一件多么容易的事情。为了保护隐私,所有这些都已匿名化并转换成了 Python 代码。在这些示例中,仅仅几行代码就烧掉了远比他们应该花费的多得多的钱。
示例1:因调试而导致的高昂费用(即使是 DevOps 也要花钱)
在这个示例中,一个 AWS Lambda 函数的平均月成本为628美元,CloudWatch 的平均月费用为31000美元。究竟发生了什么?不幸的是,AWS CloudWatch 的费用远超实际调用 Lambda 函数的情况太常见了。我不知道有多少人经历过这种情况,但感觉任何一个在 AWS 构建无服务器系统的人最终都会遇到这个问题。
在这个示例中,仅用于写入日志数据的年度总成本就达到了110万美元。造成这种情况的原因是什么?这里有两个导致因素。一些本不应该被发布的代码,却也是曾经非常重要的代码。一行善意的调试代码,当运维团队打开调试日志,并没有多想,然后将大量数据发送到了 CloudWatch。有时候,运维团队与开发团队是脱节的(我知道我们都希望认为 DevOps 总是紧密合作的,但事实并非总是如此),他们假设代码应该按照他们想的那样运行。于是,它运行了很长时间,烧掉了110万美元。
顺便说一句,如果他们还使用 Datadog 来收集日志,这可能会变得更加昂贵。相比之下,110万美元算是一笔划算的交易,但无论如何,这同样是悲剧,也是不必要的。
有什么办法可以解决这个问题?很简单。去掉调试语句,我们知道这就是问题所在。如果我在电脑上编写示例代码,最后会把调试代码删除,因为到最后我们不需要它们。在开发和测试阶段,这些代码很有用,但部署时不要把它们放进去。它们就像是一个漏洞,一颗等待爆炸的定时炸弹。解决办法就是删除它们。
示例2:API 也是要花钱的
在这个示例中,我们有一个最小可行产品(MVP)进入了生产环境。几年后,这个产品向 S3发起数十亿次 API 请求,因为是偷偷平稳地增长,以至于没人注意到。这段代码在一年内就烧掉了130万美元。
这段代码存在许多挑战。作为最小可行产品(MVP),它运行得非常完美。一个想法蹦出来,把它写在纸上,然后实现它,交付它。为什么这些东西会在 for 循环里?为什么在运行过程中调用 S3API?实际上,我们可以把所有这些内容抽离出来,并快速缓存或捕获这些信息。问题是这段代码能正常运行。
在部署好后,它运行得很好。直到多年后,当它达到一定规模时,才开始烧掉那130万美元。我们还发现了一个小细节。也许我不应该把这些文件传递给后续的函数进行进一步处理。这个问题的解决办法是什么?我们可以把它从 for 循环中抽离出来。提前计算或下载这些内容,一次性做完,而不是在函数里运行一百万次。与其通过传递指针方便后续查找文件,不如直接传递实际的数据。一次性使用——多么简单的操作。再次强调,我们都做过这样的事情。我们让代码跑起来,作为原型来说运行得足够好。然后,它们被悄无声息地交付,我们也没有想太多。API 调用是要花钱的。有时候,在 S3中,API 调用的成本可能比存储本身还要高。
示例3:几字节如何让 DynamoDB 写入成本加倍
在这个示例中,一位开发人员被要求添加一些简单的功能。我们写入 DynamoDB 的记录没有时间戳,我们想知道它是什么时候写入的。为什么不添加个字段呢?这应该非常简单。修改代码只需一秒钟,有人测试了,然后部署了,现在已经上线并运行了。
不久之后看看账单,DynamoDB 的成本翻了一番。这个稍微有点难发现。有人知道为什么添加时间戳的代码会让 DynamoDB 的成本比以前翻了一番吗?DynamoDB 按照1K 元素为单元进行收费。它写入的是1000字节,但我们添加了一个时间戳,一个9字节的属性。时间戳是 ISO 格式的,即32个字节,加起来是1041字节,仅一行代码就使成本翻了一倍。
这个真的很难被发现。我们必须用不同的方式思考数据是如何在线上传输的。更重要的是,这对我们的成本有何影响?这个问题的解决办法是什么?我们应该做两件事。我们应该减小属性名称的大小。这是 TCP/IP 协议的一个基本属性。将其改为“ts”而不是时间戳。这样就可以减掉几个字节。我们重新格式化时间戳,这样就减少到了20个字节,还剩余2个字节。我们回到了实际需要的水平——一行代码,成本减半。
示例4:基础设施代码泄漏(Terraform 版)
我们不要忘了基础设施即代码,比如 Terraform 和 CloudFormation。在接下来的示例中,我们有一个 Terraform 模板,用于创建自动伸缩组。它可以同时伸缩具有数百甚至数千个 EC2实例的集群。有人设计了这样一个系统,每24小时就回收一次实例。也许是因为存在内存泄漏,他们认为这是一个很好的解决方法。不幸的是,安全部门的人担心这些数据可能是必要的,所以移除了可删除 EBS 卷的选项。这个系统运行了大约一年,慢慢积累了一堆 EBS 卷。在那一年年底,110万美元付诸东流。这个示例有点冗长,就像大多数基础设施即代码一样,但导致这个问题的是两行代码,分别在两个不同的文件中。
这两行代码组合每隔24小时会为每个创建的 EC2实例创建一个未连接的 EBS 卷。第一行delete_on_termination被设置为 false,阻止 EBS 卷被删除。第二行max_instance_lifetime是回收时间。因为这两行在不同的文件中,所以很容易被忽略。
这两行代码意味着每次 EC2实例启动都会创建一个 EBS 卷,而这个 EBS 卷永远不会被删除(除非手动删除)。由于自动伸缩组的最大大小为1000(在这个示例中,在任何给定时刻,这个环境中有300到600个 EC2实例),未连接的 EBS 卷的数量迅速增加。一年下来,累计费用超过了一百万美元。然而,解决这个问题稍微会复杂一些。对于这个问题,你必须改变流程。
你必须稍微考虑一下你的团队在实现这些东西时是怎么做的。如果创建了资源,就应该知道如何删除它们。这不仅适用于云端的成本,也适用于许多其他的情况。我们中的许多人在过去几年里一直在思考如何扩大规模,但没有足够多地思考如何缩减规模。缩减规模要难得多,也重要得多,它甚至还有可能拯救你的企业。如果你的公司是一家旅游公司,并在经历了新冠疫情后幸存下来,那么一定知道如何缩减规模。我听说过 Expedia 团队的一些了不起的事情,但并非每家公司都那么幸运。要小心那些出于好意的基础设施即代码,特别是当你需要满足来自不同团队的需求时。
示例5:网络传输成本
第五个示例,我把最好的留在最后。我们都喜欢内容分发网络(CDN),它们可以更快地将内容传输给客户,让所有的东西都运行得更快。在最后这个例子中,一家公司在全球部署了230万台设备,他们做了一个小小的改动,而这个小改动被部署到了所有设备上。
大约14小时后,这个改动变成了一个问题。这个问题被修复之前,每小时导致约4500美元的损失。虽然最终造成了数十万美元的损失,但这与它可能造成的真正影响相比,简直是小巫见大巫。如果这个改动持续运行一年而没有人注意到——我会解释为什么可能没有人注意到它——它将成为一条价值3900万美元的代码行。
我确信会有人乞求那笔钱可以回来。希望在第一个月的财务报告中就有人注意到了这个问题。然而,那已经是在损失了64.8万美元之后了。这真是一个让人痛苦的 Bug。值得庆幸的是,这个问题在六天后被发现,相比通常情况下需要几个月才会被发现,这也算是一个小小的成功故事了。问题是,这个故事的成功标准实在太低了。
那是段怎样的代码?它看起来像这样。
在这段代码中,有一个出于好意的更新函数,可能是很久以前的一个实习生写的。它原本每天被调用一次,用来下载和比较一个大文件,这看起来像是一个糟糕的主意,所以有人决定改为下载元数据,认为这会更高效。具有讽刺意味的是,这个改动实际上是为了降低成本。他们部署了代码,并期望一切都朝着正确的方向发展。当他们突然发现事情并没有按预期进行时,他们并不确定接下来发生了什么。
有多少人能发现这段代码中的 Bug?
只是一个字符,这个字符的拼写错误让这段代码的执行切换到了成本更高的路径。同时,他们将调用频率从每天一次提高到每小时一次。问题在于,CloudFront 非常乐意提供内容。它做得很好,不仅扩展服务满足需求,还成功分发了内容。在整个过程中,没有系统受到影响,也没有错误被检测到。所有人都很高兴,因为数据流动顺畅且高效。
他们的客户可能会好奇,为什么他们家的网络会有这么多额外的数据流动,但因为一切工作正常,这个问题难以被发现。运营团队和后台监控工具没有发现错误,也没有任何警报。Datadog 没有报告任何问题,因为 CloudFront 可以轻松地应对流量的增长。唯一的异常指标是他们现在每小时要花掉4500美元,这在之前没有发生过——而这一切都是因为一个字符的拼写错误。
真正的解决之道是重新反思整个问题。尽管这个问题可以被快速修复,但因为涉及到产品的一个关键方面,所以需要更深层次的方案。此外,这类错误不太容易通过测试捕捉到,在未达到实际规模的情况下,许多问题在测试中是无法被注意到的。这种微不足道的字符拼写错误可能会导致3900万美元的账单。
学到的教训
我们从中学到了什么?存储成本依然低廉。我们确实可以认为存储成本依然相当低廉。然而,调用 API 是有成本的,这些成本总是存在的。事实上,我们应该接受这样一个事实:在云端进行的任何操作都是有成本的。可能不多,可能是几美分,可能是几分之一 美分,总之是要花钱的。在调用 API 之前,你最好考虑到这一点。云给了我们几乎无限的规模,问题是,我没有可以无限支付的钱包。
我们有一个系统设计约束,这个约束在设计、开发和部署过程中似乎没有人关注。这个重要的经验教训是什么?我们是否应该在成为云平台软件开发者的基础上,再增加一层对成本的关注?我已经考虑了很多,再增加一件需要担心的事情似乎是雪上加霜。我们希望所有的工程师都去操心代码的成本问题吗?即使在这个新的云时代,Donald Knuth 的这句名言依然有深远的意义。
过早优化是万恶之源 —— Donald Knuth
作为工程师,我们首先需要弄清楚的是,这个该死的东西是否能工作?我能解决这个问题吗?
我分享的所有这些例子在流量达到一定规模之前都不是问题。事实上,只有在你取得成功之后,它们才会成为问题。除非你在正在开发的产品或服务上取得了实质性的进展,否则这些问题不应该成为你的首要关注点。
云工程师应该考虑成本,但这应该是一个渐进持续的过程,而不是一次性的决策。
首先,我们要回答这个问题:这是否可行?然后,作为团队的一员,这样做对团队来说是对的吗?其他人如何维护我的代码?接下来,如果规模增长了,会发生什么?这个时候你应该开始考虑成本问题。
当我开始在云端构建我的第一个系统时,我对成本的概念还相当模糊。我去找 CFO 并说我想用 AWS 做一个项目时,他说,“Erik,你可以做任何你想做的,但你有3000美元的预算,不要一下子花光。”这已经是很久以前的事情了,那时候云计算还是一个新兴领域。我知道如果我能把项目的成本控制在3000美元以内,我就可以在这里尽兴地探索。所以,我开始对如何最大化投资回报产生了浓厚兴趣。这种对效率的追求得到了回报,因为我成功地将成本控制在预算之内,并自此一直在云计算领域深耕。所有人都应该这样吗?我们是否应该给每个工程师一个预算?我们更希望赋予工程师的,不仅仅是一个数字,因为一个数字可能意味着每天花费相当于一辆兰博基尼的价格,这听起来既抽象又难以理解。相反,我更希望每个人都能专注于提高效率。
云效率比率
为此,我想介绍一个叫做云效率比率(Cloud Efficiency Ratio,CER)的概念。这个概念既简单又直观,旨在引导你在恰当的时机才开始考虑成本优化,避免过早地陷入成本削减的泥潭。你可以通过用收入减去云成本再除以收入来计算云效率比率百分比。例如,假设你的公司每年收入1亿美元,云成本是2000万美元,那么你每收入一美元就花掉20美分,你的云效率比率是80%。这个比率很棒,但你不一定需要在一开始就达到这个水平。
你应该将云效率比率视为一种合理化成本的非功能性需求。对于任何云项目,你应该让产品团队或业务部门在项目开始时定义期望的云效率比率,以及在应用程序生命周期的哪个阶段应该做出调整。
记住,在研发阶段,你的主要目标是验证项目是否可行。你的 CER 可能是负的,甚至在某些情况下就应该是负的。如果你在产品发布之前就开始赚钱,那就有问题了。然而,一旦达到了 MVP 阶段,就应该开始努力实现盈亏平衡。这个阶段的 CER 低是可以接受的,它应该在0% 到25% 之间,因为你的目标是试图找到产品与市场的契合点。等到人们对你的产品开始感兴趣,并想要买你的产品,你就可以开始设想如果产品变得流行起来会怎样,这时的 CER 就该达到25% 到50%。
在你扩大产品规模,且人们在抢着购买你的产品时,你需要制定一条明确的路径,以实现健康的盈利水平,你可能想达到50% 到70% 的 CER。随着业务逐步进入稳定状态,如果你想拥有一个健康的业务,并成为组织的利润引擎,那么就要努力达到80% 的 CER。利用云效率率作为目标,你可以将那些抽象的美元成本转化为具体的目标,用以指导你的云项目。这个指标可以贯穿你的整个云平台,或者具体到某个客户、功能、服务或任何新项目。作为经验法则,我建议将目标 CER 设定为80% 左右。
结 论
在为撰写本文做准备的过程中,我意外地发现了计算机科学领域的杰出人物 Tony Hoare。一些人认为他推动了“过早优化是万恶之源”这句名言的传播。我发现的东西让我大吃一惊。事实证明,Tony 在多年前就提出了这个概念。早在2009年的伦敦 QCon 大会上,他就讲述了一个价值十亿美元的教训。在演讲中,他回顾了1965年他发明空引用的决策,并认为这一早期的错误可能导致全球经济损失高达数十亿美元。他的观点可能并无夸张。
所有的工程师都知道,随着时间的推移,他们的代码会自成一体。它会流转到其他人手中,并继续演化,我们对它们的掌控会越来越弱。在编写代码时,我们考虑的成本可能只有几分几毛,但当这些代码在某个地方部署并运行时,每年的运行成本可能高达100万美元。因此,当你重新审视你的代码库时,请认真思考这一问题。愿你在这一过程中不会有任何令人沮丧的发现。
最后,我将以这句话结束:
“每一个工程决策都是一个购买决策。”
Erik Peterson —— CloudZero 联合创始人兼 CTO
在今天,当你敲下每一行代码,都是在做一个购买决策。你在你的组织、这个经济体和这个云驱动的世界中扮演着非常重要的角色。希望你能明智地运用这种力量。
查看英文原文:
https://www.infoq.com/articles/cost-optimization-engineering-perspective/
更新于:4个月前