云服务器价格_云数据库_云主机【优惠】最新活动-搜集站云资讯

华为云_服务器免费试用_免费1年

小七 141 0

使用Bazel在Databricks上构建Speedy Scala

数据块从标准的Scala构建工具(SBT)迁移到使用Bazel来构建、测试和部署Scala代码。通过对构建基础设施的改进,Scala编译工作流以前需要几分钟到几十分钟,现在只需几秒钟就可以完成。这篇文章将带您了解我们为实现这一目标所做的改进,并演示每个更改对在Databricks上使用Scala和Bazel带来的平滑体验所起的具体加速作用。数据库代码库Databricks代码库是一个Monorepo,包含支持我们大多数服务的Scala代码、用于前端UI的Javascript、用于编写脚本的Python、用于配置基础设施的Jsonnet等等。Scala部分是目前为止最大的,在撰写本文时包括:1000000行Scala代码拆分超过5500个源文件在600个模块中(也称为Bazel中的目标)部署为几十个不同的服务。虽然我们很欣赏Scala作为一种编程语言,但每个人都知道Scala编译器的速度很慢。尽管最近有所改进,但一些基准测试在编译等效代码时比Java编译器慢6倍左右。与大多数编程语言相比,Scala编译时间容易失控。我们内部调查的反馈也证明了这一点,特别指出我们的Scala代码编译时间太长:构建+应用构建花费了太多时间。每天都要花费1-2分钟的时间片段等待构建/同步在Bazel建立scala项目(约10-15分钟)。正在编译应用程序(约30秒)。编译应用程序测试(约90秒)我们工程师的反馈启动了一系列项目来处理这些长的迭代周期,我们将在下面介绍这些。作为讨论的基准,我们将比较在Scala代码库中使用两个模块的经验://通用:一个通用的实用程序模块,不依赖太多(相对而言),但在我们的代码库中使用。//app:一个通用的webservice,它依赖于大量的其他模块(包括//common),但是其他Scala模块没有直接使用它(我们最终将它部署在docker容器中)。统计如下:目标#源文件#源行#可传递文件#传递线#传递模//普通17622000个50050000个15//应用程序4813000个1100个18万112每个Scala模块都有其依赖的多个上游模块。使用Bazel(和sed清理一些与遗留问题相关的噪音)可以很容易地可视化依赖关系图的结构:bazel query"kind(generic_scala_worker,deps(//common))"--output=graph | tred | dot-Tsvg>;common-图形.svg查看//common的依赖关系图的结构,我们可以看到一些模块,比如//common/客户:客户,//通用/选项:选项,和//utils:过程\utils是独立的:它们不以任何方式相互依赖,因此Bazel将并行编译它们。另一方面,其他模块//普通:普通,//通用/选项:选项,和//db/实用程序:db utils do相互依赖,因此Bazel被迫按顺序编译它们。//app的依赖关系图类似,只是大得多,太乱了,不能在这里显示。我们将使用这两个模块来演示我们的各种性能相关工作如何影响两个主要的Scala编译工作流:干净的构建:这涉及到重新编译代码库中的每个Scala模块,或者至少重新编译所需模块所依赖的模块。当您设置一台新的笔记本电脑,或者使用Git更改分支并最终修改大量文件时,通常会发生这种情况。Delta构建:这涉及到在进行一个小的更改后,重新编译少量Scala模块,通常是单个模块,通常是一个文件。这通常发生在迭代开发过程中,可能添加一个println并查看它输出的内容。Bazel的工作原理在我们讨论具体的性能数字之前,先对Bazel有一个基本的了解。Bazel是一个灵活的、通用的Google构建工具。Bazel帮助您跟踪代码库部分之间的依赖关系:当您要求Bazel编译一个模块(也称为目标)时,Bazel尽可能重用已经编译的代码,并并行编译互不依赖的模块。最终目标是尽快生成您要求的编译输出。Bazel的核心是围绕构建文件中的目标概念构建的,构建文件本身位于项目文件夹的子文件夹中。目标实际上是一个shell命令,它获取输入文件并生成输出文件,例如:#spark/conf/nightly/构建通用规则(name="gen_testing_mode_jsonnet",srcs=测试_模式.py", "main.jsonnet公司"],outs=主要测试_模式.jsonnet"],cmd="python$(位置:测试_模式.py)$(位置:main.jsonnet公司) >> $@",)上面的构建文件是用Python语言的一个子集Starlark编写的。上面的构建文件使用genrule函数(Bazel函数也称为rules)定义了一个目标:该目标接受两个输入文件的测试_模式.py以及main.jsonnet公司,并在沙盒中运行shell命令,以生成单个输出文件main\u测试_模式.jsonnet:这与Makefile的工作方式类似,只是用Python而不是定制的Makefile语法编写。上面的目标可以使用bazel build构建,它返回主测试_模式.jsonnetouts数组中列出的文件:$bazel build//spark/配置/夜间:发电机测试模式目标//spark/conf/夜间:发电机测试模式最新:bazel genfiles/spark/conf/nightly/main_测试_模式.jsonnet第一次在这个目标上运行bazel build时,它将运行给定的cmd来调用给定输入上的python。后续的运行只会返回已经构建的输出文件,除非这个目标的输入之一(测试_模式.py或者main.jsonnet公司)上次运行后发生了变化。这类似于久负盛名的Make-build工具。Bazel和Make之间的一个区别是Bazel允许您定义自己的helper函数:如果您反复定义相同的几个目标(例如,可能每个Scala编译目标也有一个可以运行的linter和一个可以打开的REPL),或者一些看起来或多或少相同的目标(例如,它们都是向Scala编译器输出的)以同样的方式),将它们包装在一个函数中,以保持构建配置的干燥。例如,下面的目标定义了一个jsonnet_to_json函数,该函数在内部调用genrule来转换主测试_模式.jsonnet文件(我们在上面构建)到主测试中_模式.json可在其他地方使用的文件:def jsonnet_to_json(名称、src、deps、outs):通用规则(name=名称,srcs=[…],输出=[…],cmd="…")#spark/conf/nightly/构建jsonnet_到\u json(name="NightlyBuild-json_测试模式",src="主设备测试_模式.jsonnet",deps=//jenkins/conf图:詹金斯就业基地"],outs=主要测试_模式.json"],)虽然像jsonnet_to_json这样的帮助器函数使事情变得更加方便,但是如果深入研究实现,您最终会发现一个类似于我们上面看到的shell命令,它实际运行在输入srcs文件上以生成输出文件。以上两个目标和两个输入文件定义了以下依赖关系图:Bazel本身并不在乎每个目标做什么。只要每个目标使用shell命令将输入文件转换为输出文件,Bazel就需要这样做。作为交换,Bazel将做其他的事情:如果输入没有改变,缓存生成的文件,当发生变化时使缓存失效,并行化互不依赖的shell命令,等等。NaiveBazel Scala集成默认情况下,Bazel对Scala一无所知,但是rules_Scala中有一个开源的Bazel Scala集成,它包含一些helper函数(类似于上面的jsonnet_to_json),通过向Scala编译器编译Scala代码,帮助您编译Scala代码。本质上,它提供了一个scala_library helper函数,可以如下使用:scala_库(name="jsonutil",#目标名称srcs=glob(src/main/**/*.scala"]),\deps=//jsonutil/jars"],#编译器和运行时可用的依赖项)您给scala_库目标一个srcs中的源文件列表,deps中的任何上游scala_库目标,它将向scala编译器发送编译源代码所需的命令行标志,并给您一个编译的jar文件,每个模块生成一个编译子进程:虽然这确实有效,但性能是个大问题。由于各种原因,一次又一次地生成新的编译器子进程效率低下:我们为编译的每个目标付出了JVM启动时间的沉重代价。短暂的子进程永远不会从JVM预热后发生的JIT编译中获益。最终,多个编译器进程同时运行,占用了笔记本电脑的RAM。使用这种幼稚的子进程生成编译策略,编译//app和//common、Clean和Delta构建所需的时间如下:行动天真的//应用程序(干净)1065秒//应用程序(增量)26秒//普通(干净)186年代//公共(三角形)38秒大约20分钟用于清理重建//应用程序目标,3分钟清理重建//公共。注意,这包括一定程度的并行性,如上所述。即使是Delta构建,可能添加一个println,也需要大约30秒。这相当慢,而且不包括在工作流中做其他事情(运行测试、打包docker容器、重新启动服务等)所需的时间。对于两个目标和两个工作流,这是一个非常缓慢的体验,对于我们日复一日地修改、重新编译和测试代码的工程师来说是不可接受的。长寿命工作进程我们对Scala编译基础设施的第一个重大改进是利用Bazel对长期工作流程的支持。文学士