2024 年 Clojure 状态调查中分享您的想法!

欢迎!有关如何操作的更多信息,请参阅关于页面。

0 投票
Clojure

我们主要的 Clojure 应用程序在 CircleCI 上加载需要大约 90 秒(我的 MacBook Pro 3.5gz i7)。在生产中的 Kubernetes 容器组中加载也需要相似的时间。

这个时间在我们多个领域造成问题

  • 本地开发中开发者的生产力降低
  • 我们的 CI 流程缓慢 - 我们自己的测试本身需要大约一分钟,但由于首先加载测试套件,实际运行测试的时间接近三分钟,再加上那 90 秒的加载时间。
  • 我们的部署时间和平均恢复时间减少 - 部署应用程序的新版本大约需要 20 分钟,因为我们需要滚动重启 Kubernetes 容器组,每个容器组的启动时间需要 90 秒。

我们主要应用程序的启动时间比其他应用程序慢 - 我们更现代、更精简的 Clojure 服务仍然需要大约 30 秒才能启动。Clojure 的慢速加载时间影响了我们的大多数团队和服务,但主要应用程序的团队感受到的痛苦更为明显。它运行得较慢的原因是它有比其他应用程序更多的依赖关系,因为它有多个子系统在单个 Clojure 应用程序中嵌入。

存在哪些改进加载时间的策略?

过去有人建议进行 AOT 编译,但我发现 AOT 编译从未像广告中那样成功,并引入了奇怪的错误。它还要求以“AOT-aware”的方式编写代码,包括所有库。

2 个答案

+2 投票
 
最佳答案

谢谢Sean。



有一些问题比较隐晦:我们有一个命名空间,其中定义了两个符号,分别是 Identity->Foo 和 identity->foo(两者唯一的区别在于大写字母)。这两个符号的名称在不同的文件中被处理后,在忽略大小写的文件系统中发生冲突(多亏了苹果)。

我们还有一个宏,它可以扩展为调用 `intern` 以向命名空间添加新变量的形式。这与AOT代码不兼容——当编译类加载时,变量不存在。

在应用程序生命周期的这个点上启用AOT将是一个风险——它需要对大量代码进行审计。
by
我们没有任何代码会导致AOT出现问题的。

我将尽量抵制对只有大小写不同的符号做出评论,使用 intern,或定义在编译时评估为特定环境值的……:|
+1 投票
by

每次您加载时,都会编译所有的源代码。AOT编译让您能够事先一次性完成,所以这就是这项工作的工具。许多人使用AOT进行此项操作并且没有任何问题,所以我认为你只是在对此前的恐惧做出反应。

您也可以在开发时间有策略地使用它——请参阅https://clojure.org/guides/dev_startup_time

我不知道您说的AOT-aware是什么意思。

by
当我提到“AOT-aware”时,意思是在代码编译时评价顶级形式,而不是在代码加载时。这意味着,例如,任何从环境获取值的顶级 `def` 都是在CI机器上完成的,而不是在生产机器上。
by
我认为大多数库不会这么做(因为它对AOT的大多数人来说是不利的)并且当你发现有什么可以做到这样的库时,这是一个值得提出的问题。
嗨Alex,感谢你在此提供的意见。

我今天花了一些时间以新的乐观态度去研究使用AOT。我遇到的一个问题是宏是如何在编译单元之间跟踪的。

如果我编译一个使用在其他文件中定义的宏的文件,那么据我所知,我的文件被编译为一个没有方法跟踪宏依赖关系的类文件。

如果我之后修改了其他文件中的宏,Clojure运行时没有办法跟踪到这种变化(它会查看源文件和类文件的修改时间戳),当我在加载我的文件时将加载过时的类文件。

这可能会导致在本地开发中,如果我获取到另一个开发人员更改的代码时出现问题,以及在CI中,如果我试图在构建之间缓存编译的类文件时出现CI问题。

我还没有能够计算出AOT可以得到的加载时间提升,因为我遇到了一些与AOT不兼容的代码结构,我没有在可用时间内将这些重构为与您的AOT加载系统兼容。
Clojure将在源文件与新于类文件时加载源文件。所以当你“拉取代码”时,你大概会得到一个比你在缓存中的文件更新的文件时间,Clojure运行时会加载这个文件。

因此,确实有可能有时候你会有一个过期的缓存,需要重新编译或进行一定程度的缓存失效。如果你更新了外部依赖,你也会遇到同样的问题。然而,大多数时候,大多数缓存应该保持稳定。

据我所知,我见过一些大型应用程序的加载时间改善,缩短了数十秒甚至超过一分钟。你所看到的将主要取决于命名空间的数量以及宏的使用强度。
...