应用程序的快速性能对于提供出色的用户体验至关重要!在本文中,我们将探讨在无服务器应用程序中优化冷启动和处理程序性能的陷阱和最佳实践。
目录
引言
通过函数即服务(FaaS)实现的无服务器部署范式,使开发人员能够以可伸缩且经济高效的方式轻松部署其应用程序。然而,这种便利性和灵活性也伴随着一系列需要注意的复杂性。
在早期使用长时间运行服务器的部署模型中,只要您的服务器正常运行,您的执行环境就始终可用。这使得您的应用程序能够立即响应传入的请求。
新的无服务器范式要求我们开发人员寻找方法,以确保您的函数尽快可用并响应请求。
无服务器函数中的性能陷阱
在无服务器环境中,您的函数可以缩减到零。这使您能够将运营成本降至最低,但也会带来技术成本。当您没有可用于响应请求的函数实例时,必须实例化一个新的函数。这被称为冷启动。
注意:有关冷启动的详细解释以及我们如何在使用 Prisma ORM 时尽可能缩短它们,请阅读我们最近的文章:我们如何通过 Prisma 将无服务器冷启动速度提升 9 倍。
缓慢的冷启动会导致用户体验非常糟糕,并最终降低他们对您产品的使用体验。这是问题一。
除了冷启动问题,您的实际处理函数性能也极其重要。无服务器应用程序通常由许多小型、独立的函数组成,它们通过 HTTP、事件总线、队列等协议相互交互...
单个函数之间的这种相互通信在每个请求上创建了一个依赖链。如果其中一个函数非常慢,它将影响链中的其余部分。因此,处理程序性能是问题二。
优化 FaaS 性能的最佳实践
在 Prisma,我们花了几个月的时间深入研究无服务器环境,并优化了 Prisma 在其中的行为方式。在此过程中,我们发现了许多最佳实践,您可以在自己的应用程序中采用这些实践,以尽可能保持高性能。
在本文的其余部分,我们将介绍我们发现的一些最佳实践。
将函数托管在与数据库相同的区域
无论何时您托管需要访问传统关系型数据库的应用程序或函数,您都需要初始化与该数据库的连接。这需要时间和产生延迟。您执行的任何查询也是如此。
您的目标是将该时间和延迟降至最低。目前,实现这一目标的最佳方法是确保您的应用程序或函数部署在与您的数据库服务器相同的地理区域。

您的请求到达数据库服务器的距离越短,连接建立的速度就越快。在部署无服务器应用程序时,这一点非常重要,因为不这样做可能导致的负面影响会很显著。
不这样做会影响以下所需的时间:
- 完成 TLS 握手
- 建立与数据库的安全连接
- 执行您的查询
所有这些因素都在冷启动期间被激活,因此都会影响使用 Prisma 数据库对应用程序冷启动产生的影响。
在研究这对冷启动的影响时,我们惭愧地注意到,我们的测试最初几轮是在 `eu-central-1` 区域的 AWS Lambda 无服务器函数上进行的,而 RDS PostgreSQL 实例则托管在 `us-east-1` 区域。我们迅速纠正了这个问题,而“之后”的测量结果清楚地显示了这会对您的数据库延迟产生巨大影响,无论是在连接创建方面,还是在执行任何查询方面。

之前

之后
使用与函数距离不尽可能近的数据库会直接增加冷启动的持续时间,并且在处理热请求时,每次执行查询也会产生相同的开销。
在处理程序之外运行尽可能多的代码
考虑以下无服务器函数
AWS Lambda 在某些情况下,在函数执行环境的初始启动期间,会为虚拟环境分配更多的内存和 CPU。之后,在您的预热函数调用期间,您的函数可用的内存和 CPU 实际上保证是您函数配置中的设定值——这可能比函数外部的资源少。
注意:如果您好奇,这里有一些资源解释了上述资源分配差异:
这些知识可用于通过将代码移到处理程序范围之外来提高函数的性能。这确保了在环境拥有更多可用资源时执行处理程序外部的代码。
例如,您可能在无服务器函数中执行类似以下操作:
上面的处理程序函数计算斐波那契数列中的第 40 个数字。计算完成后,您的函数将继续处理请求并最终返回响应。
将其移到处理程序外部可以使该计算在环境拥有更多可用资源时进行,并且只运行一次而不是每次调用都运行。
更新后的代码将如下所示:
另一件需要记住的事情是,AWS Lambda 支持顶层 await,这允许您在处理程序之外运行异步代码。
我们发现,在处理程序之外显式运行 Prisma Client 的 `$connect` 函数对您的函数性能有积极影响
让您的函数尽可能简单
无服务器函数旨在成为非常小、独立的代码片段。如果您的函数的 JavaScript 和依赖树庞大、复杂或分散在许多文件中,您会发现运行时读取和解释它需要更长的时间。
以下是您可以做的一些事情来提高启动性能:
- 只包含您的函数实际完成其工作所需的代码
- 不要使用会加载许多您不需要的东西的库和框架
这里的一般观点是:需要解释的代码越少,依赖树越简单,请求处理得就越快。
避免不必要的工作
函数每次调用可能重复使用的任何值计算或开销大的操作都应作为变量缓存到处理程序范围之外。这样做可以避免在函数每次调用时都执行这些开销大的操作。
考虑一种情况,从数据库中获取一个不经常更改的值,例如可配置的重定向
虽然这段代码可以工作,但每次函数调用时都会运行查找重定向的查询。这并不理想,因为它需要每次都访问数据库来查找您在上次调用时已经找到的值。
更好的写法是首先在处理程序外部检查是否存在缓存值。如果未找到,则运行查询并将结果存储以备下次使用
现在,查询只会在函数首次调用时运行。任何后续调用都将使用缓存值。
预置并发
最后一点需要考虑的是,如果您正在使用 AWS Lambda,可以利用预置并发来保持您的 Lambda 函数处于“热”状态。
根据 AWS 文档
注意:预置并发会初始化所需数量的执行环境,使它们准备好立即响应您的函数调用。请注意,配置预置并发会产生 AWS 账户费用。
这允许您维护指定数量的可用执行环境,这些环境可以在没有冷启动的情况下响应请求。
尽管这听起来很棒,但有几点重要事项需要牢记:
- 使用预置并发会产生额外费用
- 您的应用程序将永远不会缩减到 0
这些都是重要的考虑因素,因为增加的成本可能不值得您的特定场景。在采用此措施之前,我们建议您评估它为您的应用程序带来的价值,并考虑增加的成本是否合理。
总结
在本文中,我们探讨了一些我们建议开发人员在使用 Prisma ORM 构建和部署无服务器函数时的最佳实践。本文中提到的增强功能和最佳实践并非详尽列表。
快速回顾一下,我们建议您:
- 将数据库托管在离已部署函数尽可能近的地方
- 在处理程序之外运行尽可能多的代码
- 尽可能缓存可重用值和计算结果
- 让您的函数尽可能简单
- 如果您愿意权衡财务成本,请考虑使用预置并发
感谢您的阅读,希望这些信息对您有所帮助!
不要错过下一篇文章!
订阅 Prisma 新闻邮件