2024年Clojure调查问卷中分享你的想法吧!

欢迎!请参阅关于页面以了解更多有关如何使用本网站的信息。

0投票
Clojure

我们主要的Clojure应用在CircleCI上加载需要大约90秒,在我的MacBook Pro(3.5GHz i7)上。在生产中的Kubernetes Pod中加载时间也类似。

这种时间导致我们在多个方面遇到问题

  • 本地开发中开发者的生产力降低
  • 我们的CI流程缓慢-我们的测试本身大约需要一分钟来运行,但由于测试套件首先加载代码,以及我们有90秒的加载时间,所以运行测试的时间更接近三分钟。
  • 我们的部署时间和平均恢复时间减少-部署应用新版本需要大约20分钟,因为我们对Kubernetes Pod执行滚动重启,并且每组Pod启动需要90秒。

我们主要应用的启动时间比其他应用慢-我们更现代、更精简的Clojure服务仍然需要大约30秒来启动。Clojure缓慢的加载时间影响了我们的大多数团队和服务,但主应用团队的痛苦最为剧烈。它较慢的原因是因为它比其他应用有更多的依赖,因为它在单个Clojure应用中嵌入了许多子系统。

有哪些方法可以改善加载时间?

过去有人建议使用AOT编译,但我发现AOT编译从来就没有按预期工作,而且引入了奇怪的错误。它还要求代码按照“AOT-aware”的方式进行编写,包括所有库。

2 个答案

+2投票
by
精选 by
 
最佳答案

在生产中进行滚动重启时,将AOT编译作为应用程序JAR构建过程的一部分可以加快速度(构建JAR将明显变慢——你必须承担编译时间的“惩罚”)。

我们过去一直是用源代码构建我们的应用uberjars,但我们也遇到了生产中启动时间慢的问题,所以我们改为在uberjar构建时使用AOT编译,这使得我们的生产启动时间从最差应用的几分钟降低到几秒钟。

至于CI,为了运行测试,它们必须被加载和编译,因此你实际上无法避免这种时间 somewhere。那里你唯一真正的选择是转向增量测试,只运行已更改的或不依赖于已更改的代码的代码的测试。对于我们来说,这是转向Polylith的未来的承诺之一,因为其内置的测试运行程序可以根据git标签找出这一点——但这种好处只在你拥有“一切”在Polylith上时才会到来。

Alex提到了关于加快开发加载的文档,我在工作中通过在我的类路径上有一个本地的classes文件夹,并定期为本库中的主要“应用”手动启动一个compile步骤来实现这一点。这意味着自上次编译以来没有更改的任何代码都将使用现有的.class文件,并且不需要在加载时通过Clojure编译过程——但任何更改过的代码仍然在加载时需要编译。

在REPL中工作时,对初始加载时间有很大影响,但随时间推移,“漂移”得越来越严重。然而,在工作中,我们倾向于拥有寿命相当长的REPL,并在修改代码时评估所有代码,所以慢第一加载问题不是很讨厌,我并不总是困扰于classes的事情。当我提到“长远活期REPL”时,我的意思是几周,有时甚至是几个月。我的主要工作REPL现在已经运行了十天,它这么“短”的唯一原因是我遇到了停电,不得不重新启动我的开发机器。

by
谢谢Sean。

你对那些非AOT安全的代码有多少问题?遵循Alex的建议,我尝试在我们的应用程序中做了些AOT,但我遇到了一堆问题。

有些问题相当隐蔽:我们有一个命名空间定义了两个符号,“Identity->Foo”和“identity->foo”(唯一的区别在于大小写)。这两个符号名称被混淆到两个文件中,在大小写不敏感的文件系统中发生冲突(感谢苹果公司)。

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

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

我会尽量忍住不去评论那些只有大小写不同的符号、使用intern或者评估为环境特定值的def形式... :|
+1

每次你加载,你都会编译所有的源代码。AOT编译允许你提前一次性完成这一过程,因此这是这项工作的工具。很多人使用AOT来完成这个目的,并且没有遇到任何问题,所以我认为你只是在这里对恐怖分子做出反应。

你还可以在开发时策略性地使用它——请参阅https://clojure.org/guides/dev_startup_time

我不知道你所说的AOT意识是什么意思。

当我说"AOT意识"时,我的意思是顶层形式在代码编译时被评估,而不是在代码加载时。这意味着,例如,任何从环境中获取值的顶层`def`都是在CI机器上执行的,而不是在生产机器上。
我认为大多数库都不这样做(因为这会影响到许多人使用AOT的情况),因此如果你发现了与此不同的情况,那么提出一个问题是值得的。
你好,Alex,感谢你在这里的反馈。

我今天花了一些时间以新的希望来看待使用AOT。我遇到的一个问题是如何跟踪宏在编译单元之间的变化。

如果我编译一个使用在另一个文件中定义的宏的文件,那么据我所知,我的文件被编译成一个无法跟踪宏依赖项的类文件。

如果稍后我修改了另一个文件中的宏,Clojure运行时没有任何方式来跟踪变化(它会检查源文件和类文件的修改时间戳),并在我加载我的文件时加载过时的类文件。

这可能会导致当从代码库中提取另一个开发者更改的代码时本地开发中出现问题,以及尝试在构建之间缓存编译后的类文件时在持续集成(CI)中出现问题。

由于我遇到了一些与AOT不兼容的代码结构,并且在这些时间范围内无法将其重构为与您的AOT加载系统兼容,所以我还没有计算出AOT可能带来的加载时间增益。
Clojure会在源文件比类文件更新时加载该源文件。所以当你"拉取代码"时,你可能会得到一个比你的缓存更新的文件时间,Clojure运行时会加载这个文件。

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

据我所知,有人看到大型应用程序在加载时间上有显著的提升,减少了10秒甚至更多,或者超过一分钟。你所看到的结果将主要取决于命名空间的数量以及宏的使用强度。
...