使用 GitHub Actions 构建 Golang PGO

共 31529字,需浏览 64分钟

 ·

2024-04-24 19:53

今年 2 月,我宣布 Dolt 版本现已构建为配置文件引导优化 (pgo) 二进制文件,利用 Golang 1.20 的强大功能将 Dolt 的读取性能提高 5.3%。

在我宣布这一消息之前,我们的一位常驻 Golang 专家 Zach 试验并测试了 Golang 的 pgo 功能,并写下了他在使用 Dolt 构建后观察到的性能提升,该配置文件首先针对 Dolt 运行我们的 Sysbench 基准测试。从那时起,我们知道我们必须将这些性能提升纳入我们发布的二进制文件中,因此我们重新设计了 Dolt 的发布流程来构建 pgo 版本。

今天,我将介绍 Dolt 的一般发布流程,该流程使用 GitHub Actions,并且我将详细介绍发布流程的所有阶段。我还将回顾一下我们在开始发布 pgo 版本的过程中所做的更改。希望这能让您收集一些可用于将 pgo 构建到您自己的 Golang 版本中的见解!

让我们开始吧。

Dolt 通过 GitHub Actions 发布

Dolt 利用 GitHub Actions 执行许多自动化任务,其中之一是创建和发布版本。

GitHub Actions 使用称为工作流的文件来定义作业,它将执行工作流文件中定义的工作。这些作业部署到运行器或主机上,您可以自行托管或允许 GitHub 为您托管。

自托管运行器由您在 GitHub Actions 外部配置和维护。GitHub 托管的运行器可免费用于公共存储库,全部由 GitHub 托管和维护,但它们具有特定的存储、内存和 CPU 限制,具体取决于您的订阅级别。对于 Dolt,我们使用免费的 GitHub 托管运行器。

从高层次来看,Dolt 发布流程需要实现一些目标。

首先,也是最重要的,该过程需要为新版本的 Dolt 创建标签和发布,并将 Dolt 的预编译二进制文件上传到发布资产。

其次,发布过程需要针对这个新版本的 Dolt 运行我们的 Sysbench 基准测试,并将结果通过电子邮件发送给我们的 DoltHub 团队。

第三,与本博客不太相关,该流程需要启动我们在发布期间需要执行的任何其他辅助任务,例如创建依赖于多个存储库的拉取请求描述的 Dolt 发行说明、将版本发布到各种包管理器这样就可以轻松地从它们安装,将新的 Docker 镜像推送到 DockerHub,或者升级我们拥有的各种存储库中的 Dolt 依赖项。

因此,考虑到这些目标,我们提出了一套 GitHub Actions 工作流程,利用repository_dispatch 事件,以便我们可以实现每个目标。让我们看一个图表,该图表显示了此设计的原理,然后我们将深入了解工作流程的细节。

在上图中,您将看到两个上下文:GitHub Actions 上下文和 Kubernetes (K8s) 上下文。我们首先讨论 GitHub Actions 上下文。

对于 Dolt 的原始发布流程,我们使用了三个工作流程:“发布 Dolt”工作流程、“部署 K8s Sysbench 基准测试作业”工作流程和“电子邮件团队”工作流程。

“发布 Dolt”工作流程启动整个 Dolt 发布流程,并由我们的工程团队在准备发布新版本 Dolt 时手动运行。这是工作流程的简化版本,引用了上图中显示的步骤。

name: Release Dolt
on: workflow_dispatch: inputs: version: description: 'SemVer format release tag, i.e. 0.24.5' required: true
jobs: format-version: runs-on: ubuntu-22.04 outputs: version: ${{ steps.format_version.outputs.version }} steps: - name: Format Input id: format_version run: | version="${{ github.event.inputs.version }}" if [[ $version == v* ]]; then version="${version:1}" fi echo "version=$version" >> $GITHUB_OUTPUT
create-release: needs: format-version name: Create release runs-on: ubuntu-22.04 outputs: release_id: ${{ steps.create_release.outputs.id }} steps: - name: Checkout code uses: actions/checkout@v3 - name: Set up Go 1.x uses: actions/setup-go@v3 with: go-version: ^1.21 - name: Update dolt version command run: sed -i -e 's/ Version = ".*"/ Version = "'"$NEW_VERSION"'"/' "$FILE" env: FILE: ${{ format('{0}/go/cmd/dolt/dolt.go', github.workspace) }} NEW_VERSION: ${{ needs.format-version.outputs.version }} - name: Set minver TBD to version run: sed -i -e 's/minver:"TBD"/minver:"'"$NEW_VERSION"'"/' "$FILE" env: FILE: ${{ format('{0}/go/cmd/dolt/commands/sqlserver/yaml_config.go', github.workspace) }} NEW_VERSION: ${{ needs.format-version.outputs.version }} - name: update minver_validation.txt working-directory: ./go run: go run -mod=readonly ./utils/genminver_validation/ $FILE env: FILE: ${{ format('{0}/go/cmd/dolt/commands/sqlserver/testdata/minver_validation.txt', github.workspace) }} - uses: EndBug/add-and-commit@v9.1.1 with: message: ${{ format('[ga-bump-release] Update Dolt version to {0} and release v{0}', needs.format-version.outputs.version) }} add: ${{ format('["{0}/go/cmd/dolt/dolt.go", "{0}/go/cmd/dolt/commands/sqlserver/yaml_config.go", "{0}/go/cmd/dolt/commands/sqlserver/testdata/minver_validation.txt"]', github.workspace) }} cwd: "." pull: "--ff" - name: Build Binaries id: build_binaries run: | latest=$(git rev-parse HEAD) echo "commitish=$latest" >> $GITHUB_OUTPUT GO_BUILD_VERSION=1.21 go/utils/publishrelease/buildbinaries.sh - name: Create Release id: create_release uses: dolthub/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: v${{ needs.format-version.outputs.version }} release_name: ${{ needs.format-version.outputs.version }} draft: false prerelease: false commitish: ${{ steps.build_binaries.outputs.commitish }} - name: Upload Linux AMD64 Distro id: upload-linux-amd64-distro uses: dolthub/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: go/out/dolt-linux-amd64.tar.gz asset_name: dolt-linux-amd64.tar.gz asset_content_type: application/zip... - name: Upload Install Script id: upload-install-script uses: dolthub/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: go/out/install.sh asset_name: install.sh asset_content_type: text/plain
trigger-performance-benchmark-email: needs: [format-version, create-release] runs-on: ubuntu-22.04 steps: - name: Trigger Performance Benchmarks uses: peter-evans/repository-dispatch@v2.0.0 with: token: ${{ secrets.REPO_ACCESS_TOKEN }} event-type: release-dolt client-payload: '{"version": "${{ needs.format-version.outputs.version }}", "actor": "${{ github.actor }}"}'

此工作流是使用workflow_dispatch 事件手动触发的,并且需要新的版本号作为输入。从那里,它对版本输入进行一些快速格式化,然后将此新版本写入并提交到 Dolt 的 main 分支,以便发布的二进制文件将从 dolt version 命令输出此新版本。

在“构建二进制文件”步骤中, create-release 作业运行 buildbinaries.sh 脚本,该脚本使用运行 go build 命令的 Golang Docker 容器从源代码构建 Dolt。

我们使用 Docker 容器来构建 Dolt,以便堆栈跟踪的路径输出是通用 Linux go 路径,而不是引用运行器或我们的一台个人计算机上的 go 安装的路径(这发生在 Dolt 🤠 的早期版本中)。

接下来,“创建版本”步骤创建标签并在 GitHub 上发布版本。它还提供了一个 upload_url ,用于 create-release 作业的所有后续步骤,将已编译的二进制文件上传到新的 GitHub 版本。

此工作流程的最后部分是在所有先前作业完成后运行的另一个作业。该作业称为 trigger-performance-benchmark-email 。它使用我们在其市场上找到的 GitHub Action 来发出 repository_dispatch 事件,该事件启动单独的 Dolt 工作流程。如果我们回顾一下我们的图表,我们就可以看到这一点。

我们的图表显示了“发布 Dolt”工作流程的最后一步,该工作流程指向另一个名为“部署 K8s Sysbench 基准测试作业”的工作流程。这是由 trigger-performance-benchmark-email 工作流作业启动的工作流。

此工作流以及其他类似工作流被设计为部分异步分派,以便它不会仅与“Release Dolt”工作流紧密耦合。

事实上,各种工作流程将使用 repository_dispatch 事件触发此工作流程,因为我们需要在不同时间运行性能基准测试,而不仅仅是在发布期间运行。有趣的是,这个工作流程本身启动了另一个异步流程,我们可以在箭头所示的图中看到它 - 它部署了一个运行我们的 Sysbench 基准测试的 K8s 作业。

事实上,我们已经写了很多关于 Dolt 使用 Sysbench 对 Dolt 和 MySQL 进行基准测试来比较它们的性能的文章,但我认为我们没有讨论具体如何做到这一点的实现细节。这个博客对我来说是一个很好的回顾,所以我会暂时回顾一下。在开始之前,让我们简要了解一下“部署 K8s Sysbench 基准测试作业”工作流程。

name: Benchmark Latency
on: repository_dispatch: types: [ benchmark-latency ]
jobs: performance: runs-on: ubuntu-22.04 name: Benchmark Performance strategy: matrix: dolt_fmt: [ "__DOLT__" ] steps: - name: Checkout uses: actions/checkout@v3 - uses: azure/setup-kubectl@v3.0 with: version: 'v1.23.6' - name: Install aws-iam-authenticator run: | curl -o aws-iam-authenticator https://amazon-eks.s3.us-west-2.amazonaws.com/1.18.8/2020-09-18/bin/linux/amd64/aws-iam-authenticator && \ chmod +x ./aws-iam-authenticator && \ sudo cp ./aws-iam-authenticator /usr/local/bin/aws-iam-authenticator aws-iam-authenticator version - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v2.2.0 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-west-2 - name: Create and Auth kubeconfig run: | echo "$CONFIG" > kubeconfig KUBECONFIG=kubeconfig kubectl config set-credentials github-actions-dolt --exec-api-version=client.authentication.k8s.io/v1alpha1 --exec-command=aws-iam-authenticator --exec-arg=token --exec-arg=-i --exec-arg=eks-cluster-1 KUBECONFIG=kubeconfig kubectl config set-context github-actions-dolt-context --cluster=eks-cluster-1 --user=github-actions-dolt --namespace=performance-benchmarking KUBECONFIG=kubeconfig kubectl config use-context github-actions-dolt-context env: CONFIG: ${{ secrets.CORP_KUBECONFIG }} - name: Create Sysbench Performance Benchmarking K8s Job run: ./.github/scripts/performance-benchmarking/run-benchmarks.sh env: FROM_SERVER: ${{ github.event.client_payload.from_server }} FROM_VERSION: ${{ github.event.client_payload.from_version }} TO_SERVER: ${{ github.event.client_payload.to_server }} TO_VERSION: ${{ github.event.client_payload.to_version }} MODE: ${{ github.event.client_payload.mode }} ISSUE_NUMBER: ${{ github.event.client_payload.issue_number }} ACTOR: ${{ github.event.client_payload.actor }} ACTOR_EMAIL: ${{ github.event.client_payload.actor_email }} REPO_ACCESS_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }} KUBECONFIG: "./kubeconfig" INIT_BIG_REPO: ${{ github.event.client_payload.init_big_repo }} NOMS_BIN_FORMAT: ${{ matrix.dolt_fmt }} TEMPLATE_SCRIPT: ${{ github.event.client_payload.template_script }} - name: Create TPCC Performance Benchmarking K8s Job run: ./.github/scripts/performance-benchmarking/run-benchmarks.sh env: FROM_SERVER: ${{ github.event.client_payload.from_server }} FROM_VERSION: ${{ github.event.client_payload.from_version }} TO_SERVER: ${{ github.event.client_payload.to_server }} TO_VERSION: ${{ github.event.client_payload.to_version }} MODE: ${{ github.event.client_payload.mode }} ISSUE_NUMBER: ${{ github.event.client_payload.issue_number }} ACTOR: ${{ github.event.client_payload.actor }} ACTOR_EMAIL: ${{ github.event.client_payload.actor_email }} REPO_ACCESS_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }} KUBECONFIG: "./kubeconfig" INIT_BIG_REPO: ${{ github.event.client_payload.init_big_repo }} NOMS_BIN_FORMAT: ${{ matrix.dolt_fmt }} WITH_TPCC: "true" TEMPLATE_SCRIPT: ${{ github.event.client_payload.template_script }}

这个工作流程虽然简短,但有点忙碌,它根据 K8s 集群对 kubectl 客户端进行身份验证,我们在其中运行 Sysbench 基准测试,并提供运行名为 run-benchmarks.sh 的脚本所需的环境变量。该脚本使用这些变量的值编写 K8s 作业配置文件,然后应用它,从而在我们的 K8s 集群中部署基准测试作业。

此时,您可能想知道为什么我们选择在 K8s 集群中运行 Dolt 基准测试,而不是仅仅使用 GitHub Actions 及其运行程序来对 Dolt 进行基准测试。这有几个原因。

第一,GitHub 托管的运行程序有非常具体的限制,至少对于免费层来说是这样,并且为了对我们的数据库进行基准测试,我们不一定希望受到这些限制。

此外,当我们进行基准测试运行时,我们无法知道或控制 GitHub 托管的运行器上正在运行哪些其他进程或软件,这可能会以不可预测的方式对运行结果产生负面影响。

虽然当然可以在 GitHub Actions 中使用自托管运行器来规避这两个问题,在这种情况下,我们可以仅使用 GitHub Actions 对 Dolt 进行基准测试,但我们已经在 K8s 集群中提供了可轻松配置的主机,因此我们选择只需使用它们即可。

事实上, applying 我们的 K8s 基准测试作业将使用 K8s 集群自动缩放器配置一个新的基准测试主机,这非常酷。

无论如何,暂时回到我们的图表,我们看到在验证 kubectl 客户端之后,“部署 Sysbench 基准测试作业”工作流程部署 K8s 作业,并且该流程移动到 K8s 上下文,并且“K8s Sysbench 基准测试作业”正在运行。

现在从技术上讲,原始 Dolt 发布过程的这一部分更多的是发布后步骤。在 GitHub 上创建新的 Dolt 版本不需要运行基准测试作业,它只是为我们的团队提供有关该版本延迟的报告。但重要的是要了解我们最初发布过程的这一部分,这样我们对 Dolt 发布过程的 pgo 更新才会更有意义,但稍后会详细介绍。

在该图的 K8s 上下文中,我们可以看到基准测试作业执行了几个步骤。它根据提供的提交 SHA 构建 Dolt 二进制文件。在本例中,它是来自 Dolt main HEAD 的 SHA。

接下来,它针对已编译的 Dolt 版本运行 Sysbench 测试,然后将 Sysbench 运行结果上传到 AWS S3 存储桶。最后,它触发位于 Dolt 存储库中的不同 GitHub Actions 工作流程,称为“电子邮件团队”工作流程。

为了执行所有这些基准测试以及上传和触发,我们编写了一个内部工具,可用于针对 MySQL 版本对 Dolt 版本进行基准测试。

该工具使用我们在 Dolt 存储库中维护的一些库代码,但我将提供内部工具和库代码中的一些相关片段,以便您了解我们如何实现这些代码来运行基准测试。

我们的内部基准测试工具代码本质上是以下 go 函数:

func compare(ctx context.Context,  fromServer,  toServer runner.ServerType,  fromVersion,  toVersion,  fromProfile,  toProfile,  dir,  doltCommand,  doltgresCommand,  mysqlExec,  mysqlProtocol,  mysqlSocketPath,  postgresExec,  initDbExec,  nomsBinFormat,  resultsDir,  resultsPrefix,  resultsFilename,  scriptDir,  schema,  outputFormat string,  defaultRuns int,  initBigRepo,  useDoltHubLuaScriptsRepo,  writeResultsToFile bool,  queries []string) (string, error) {  config := benchmark.NewComparisonBenchmarkingConfig(    fromServer,    toServer,    fromVersion,    toVersion,    fromProfile,    toProfile,    dir,    doltCommand,    doltgresCommand,    mysqlExec,    mysqlProtocol,    mysqlSocketPath,    postgresExec,    initDbExec,    nomsBinFormat,    scriptDir,    defaultRuns,    initBigRepo,    useDoltHubLuaScriptsRepo)
sr := benchmark.NewSysbenchComparer(config)
err := sr.Run(ctx) if err != nil { return "", err }
fromServerConfig, err := config.GetFromServerConfig(ctx) if err != nil { return "", err }
toServerConfig, err := config.GetToServerConfig(ctx) if err != nil { return "", err }
resultsDbName := fmt.Sprintf("sysbench-%s", benchmark.ComparisonDbFilename)
db := benchmark.NewSqlite3ResultsDb(fromServerConfig, toServerConfig, dir, schema, resultsDir, resultsPrefix, resultsFilename, resultsDbName, outputFormat, queries, writeResultsToFile) uploadDir, err := db.QueryResults(ctx) if err != nil { return "", err }
return uploadDir, nil}

compare 用于将一个数据库版本与另一个版本的 Sysbench 结果进行比较。从函数的参数可以看出,这个工具不仅用于Dolt和MySQL,还用于对我们最新产品DoltgreSQL及其竞争对手PostgreSQL进行基准测试。

compare 函数引用 fromServerConfig ,这是“from”数据库服务器的配置,并引用 toServerConfig ,这是“from”数据库服务器的配置“到”数据库服务器。从语义上讲,这里该工具将并排比较“源”数据库和“目标”数据库,以便于分析。在 Dolt 发布过程中,MySQL 将是“源”服务器,Dolt 将是“目标”服务器。

您可能还注意到,我们在此工具中使用 sqlite3,如 benchmark.NewSqlite3ResultsDb 所引用,它是 Dolt v1.0.0 之前的遗留工件,但它在这里仍然具有一些独特的价值。

在后台,使用 sr.Run() 运行基准测试后,我们将结果加载到 sqlite3 数据库中,并对其运行一些查询以获得每个数据库服务器的比较结果。与 Dolt 相比,使用 sqlite3 的一个好处是, sqlite3 使用简单的标志以多种格式返回查询输出,如 --html ,这使我们不必编写查询结果转换逻辑。

从 db.QueryResults() 返回的 uploadDir 包含比较查询的结果以及要上传到 S3 的 sqlite3 数据库的副本。这些结果很快就会被“电子邮件团队”工作流程下载,我们很快就会看到。

当实际运行 Sysbench 基准测试时, benchmark.NewSysbenchComparer(config) 只是我们在 Dolt 存储库中维护的一些基准测试库代码中的 Run 函数的包装结构。

func Run(config *Config) error {  err := config.Validate()  if err != nil {    return err  }
ctx := context.Background()
err = sysbenchVersion(ctx) if err != nil { return err }
cwd, err := os.Getwd() if err != nil { return err }
for _, serverConfig := range config.Servers { var results Results var b Benchmarker switch serverConfig.Server { case Dolt: fmt.Println("Running dolt sysbench tests") b = NewDoltBenchmarker(cwd, config, serverConfig) case Doltgres: fmt.Println("Running doltgres sysbench tests") b = NewDoltgresBenchmarker(cwd, config, serverConfig) case MySql: fmt.Println("Running mysql sysbench tests") b = NewMysqlBenchmarker(cwd, config, serverConfig) case Postgres: fmt.Println("Running postgres sysbench tests") b = NewPostgresBenchmarker(cwd, config, serverConfig) default: panic(fmt.Sprintf("unexpected server type: %s", serverConfig.Server)) }
results, err = b.Benchmark(ctx) if err != nil { return err }
fmt.Printf("Successfuly finished %s\n", serverConfig.Server)
err = WriteResults(serverConfig, results) if err != nil { return err }
fmt.Printf("Successfuly wrote results for %s\n", serverConfig.Server) } return nil}

此函数根据它看到的服务器类型创建一个 Benchmarker ,然后调用 Benchmark() ,后者针对该服务器运行 Sysbench 测试。下面是 Dolt Benchmarker 的 Benchmark() 实现的示例:

func (b *doltBenchmarkerImpl) Benchmark(ctx context.Context) (Results, error) {  err := b.checkInstallation(ctx)  if err != nil {    return nil, err  }
err = b.updateGlobalConfig(ctx) if err != nil { return nil, err }
testRepo, err := b.initDoltRepo(ctx) if err != nil { return nil, err }
serverParams, err := b.serverConfig.GetServerArgs() if err != nil { return nil, err }
server := NewServer(ctx, testRepo, b.serverConfig, syscall.SIGTERM, serverParams) err = server.Start(ctx) if err != nil { return nil, err }
tests, err := GetTests(b.config, b.serverConfig, nil) if err != nil { return nil, err }
results := make(Results, 0) for i := 0; i < b.config.Runs; i++ { for _, test := range tests { tester := NewSysbenchTester(b.config, b.serverConfig, test, stampFunc) r, err := tester.Test(ctx) if err != nil { server.Stop(ctx) return nil, err } results = append(results, r) } }
err = server.Stop(ctx) if err != nil { return nil, err }
return results, os.RemoveAll(testRepo)}

在 Benchmark() 调用期间,此实现将检查 Dolt 安装,更新一些全局 Dolt 配置,获取用于启动 Dolt SQL 服务器的参数,启动服务器,获取将要运行的 Sysbench 测试,然后通过调用 tester.Test() 运行这些测试。

完成后,它返回 results 并清除写入磁盘的内容。

而且,正如我们在内部工具的 compare 功能中所看到的,这些结果被加载到 sqlite3 中并上传到 S3,以便可以通过电子邮件发送给 DoltHub 团队。但是,我们仍然缺少一步,即在内部基准测试工具完成上传结果后,通过 repository_dispatch 事件触发“电子邮件团队”工作流程。

因此,我们内部工具的最后一部分包括:

err := d.DispatchEmailReportEvent(ctx, *toVersion, *nomsBinFormat, *bucket, key)if err != nil {    log.Fatal(err)}

DispatchEmailReportEvent() 方法位于我们编写的 Dispatcher 接口上。它只是向 GitHub Actions Workflow REST API 发出 HTTP 请求,该 API 发出 repository_dispatch 事件,触发“电子邮件团队”工作流运行。那么让我们接下来看看。

与“部署 K8s Sysbench 基准测试作业”工作流程一样,“电子邮件团队”工作流程除了 Dolt 发布流程之外还被多个流程使用,因此这就是我们使用 repository_dispatch 事件触发它的原因。工作流程文件如下:

name: Email Team Members
on: repository_dispatch: types: [ email-report ]
jobs: email-team: runs-on: ubuntu-22.04 name: Email Team Members steps: - uses: actions/checkout@v3 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v2.2.0 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-west-2 - name: Get Results id: get-results run: aws s3api get-object --bucket="$BUCKET" --key="$KEY" results.log env: KEY: ${{ github.event.client_payload.key }} BUCKET: ${{ github.event.client_payload.bucket }} - name: Get Addresses id: get-addresses run: | addresses="$TEAM" if [ ! -z "$RECIPIENT" ]; then addresses="[\"$RECIPIENT\"]" fi echo "addresses=$addresses" >> $GITHUB_OUTPUT env: RECIPIENT: ${{ github.event.client_payload.email_recipient }} TEAM: '["${{ secrets.PERF_REPORTS_EMAIL_ADDRESS }}"]' - name: Send Email uses: ./.github/actions/ses-email-action with: template: ${{ github.event.client_payload.template }} region: us-west-2 version: ${{ github.event.client_payload.version }} format: ${{ github.event.client_payload.noms_bin_format }} toAddresses: ${{ steps.get-addresses.outputs.addresses }} dataFile: ${{ format('{0}/results.log', github.workspace) }}

如图所示,此工作流程的摘要是下载 Dolt 版本的 Sysbench 结果,然后通过电子邮件将其发送给我们的团队;没什么疯狂的。

这就是 Dolt 的发布过程。或者这就是 Dolt 的发布过程。现在我将回顾一下我们如何更新此流程以在发布时开始构建 Dolt 的 pgo 二进制文件。

PGO 通过 GitHub Actions 发布

对于那些不熟悉 pgo 构建的人,他们需要 -pgo 标志以及在 go build 命令期间提供的 Golang 配置文件的路径。这部分其实很简单。但在此之前,您需要创建要用于优化构建的配置文件,这需要我们更新一些基准库代码和内部工具代码,以便它们既可以生成配置文件并接受配置文件作为输入。让我更详细地解释一下。

在我们的基准测试库代码中,我们使用另一个名为 dolt_builder 的 Dolt 实用程序来实际从源代码构建 Dolt 二进制文件。要使用此工具,您只需提供要从中构建 Dolt 的提交 SHA 或标记,它就会为您构建它。因此,我们在很多地方使用这个工具来轻松地同时构建多个版本的 Dolt。

因此,我们做的第一件事就是更新这个工具以接受可用于构建 Dolt 的 Golang 配置文件:

// goBuild builds the dolt binary and returns the path to the binaryfunc goBuild(ctx context.Context, source, dest, profilePath string) (string, error) {  goDir := filepath.Join(source, "go")  doltFileName := "dolt"  if runtime.GOOS == "windows" {    doltFileName = "dolt.exe"  }
args := make([]string, 0) args = append(args, "build")
if profilePath != "" { args = append(args, fmt.Sprintf("-pgo=%s", profilePath)) }
toBuild := filepath.Join(dest, doltFileName) args = append(args, "-o", toBuild, filepath.Join(goDir, "cmd", "dolt"))
build := ExecCommand(ctx, "go", args...) build.Dir = goDir err := build.Run() if err != nil { return "", err } return toBuild, nil}

我们做的下一件事是更新基准库代码以在“分析”模式下运行。在默认模式下,如上所述,此代码调用 Benchmark() 并返回结果。在新的“分析”模式中,代码在 Profiler 接口上调用 Profile() :

...    case Dolt:      // handle a profiling run      sc, ok := serverConfig.(ProfilingServerConfig)      if ok {        if string(sc.GetServerProfile()) != "" {          fmt.Println("Profiling dolt while running sysbench tests")          p := NewDoltProfiler(cwd, config, sc)          return p.Profile(ctx)        }      }...

Profile() 的工作方式与 Benchmark() 类似,但会创建一个在运行 Sysbench 基准测试时获取的 golang 配置文件。这使我们能够轻松生成可在新发布流程中使用的 Dolt 配置文件。

我们还更新了此库代码以接受配置文件作为输入。这样我们就可以向它提供一个配置文件,它又将其提供给 dolt_builder 以创建 pgo 二进制文件,然后运行 Sysbench 并输出这些结果。

为了澄清,我们基本上更新了这个库代码,以便我们可以在一种模式下运行它来生成 Golang 配置文件,然后在默认模式下运行它以获得正常的基准测试结果,但它也接受 Golang 配置文件作为输入,并用它来构建 Dolt 和 go build -pgo 。希望这对你来说有意义,因为对我来说描述有点棘手🤠。

接下来,我们需要更新使用所有这些库代码的内部工具,以拥有“分析”模式并接受 Golang 配置文件作为输入。我们对新发布流程的计划是在分析模式下运行内部工具一次,以创建 Golang 配置文件。然后,在默认模式下再次运行内部工具,但向其提供 Golang 配置文件,这将针对 pgo 构建的 Dolt 生成基准测试结果。

因此,与 compare 函数一样,我们能够将 profile 函数添加到生成 Dolt 版本的 Golang cpu 配置文件的内部工具中。

func profile(ctx context.Context, dir, profileDir, resultsDir, resultsPrefix, version, profile, doltCommand, scriptsDir string, useDoltHubLuaScriptsRepo bool) (string, error) {  config := benchmark.NewProfilingConfig(    dir,    profileDir,    version,    profile,    doltCommand,    scriptsDir,    useDoltHubLuaScriptsRepo)  toUpload := filepath.Join(resultsDir, resultsPrefix)  sr := benchmark.NewSysbenchProfiler(config, toUpload, profileDir)  return toUpload, sr.Run(ctx)}

此函数像 compare 一样返回其 toUpload 目录,但这次它包含要上传到 S3 的配置文件。

对代码进行这些更改后,我们准备更新 GitHub Actions 工作流程以开始创建 Dolt 的 pgo 版本。下面的图表显示了使用 GitHub Actions 的新 Dolt 发布流程。

从新的发布工作流程图中可以看到,我们添加了一些新的 GitHub Actions 工作流程,但它们与原始工作流程类似。让我们更仔细地看看它们。

对于新的 Dolt 发布流程,我们运行的第一个工作流程称为“发布 Dolt(配置文件)”,实际上并不创建 GitHub 版本或构建任何 Dolt 二进制文件。

相反,它的唯一功能是触发第二个工作流程,称为“部署 K8s Sysbench 分析作业”。

name: Release Dolt (Profile)
on: workflow_dispatch: inputs: version: description: 'SemVer format release tag, i.e. 0.24.5' required: true
jobs: format-version: runs-on: ubuntu-22.04 outputs: version: ${{ steps.format_version.outputs.version }} steps: - name: Format Input id: format_version run: | version="${{ github.event.inputs.version }}" if [[ $version == v* ]]; then version="${version:1}" fi echo "version=$version" >> $GITHUB_OUTPUT
profile-benchmark-dolt: runs-on: ubuntu-22.04 needs: format-version name: Trigger Benchmark Profile K8s Workflows steps: - uses: actions/checkout@v4 with: ref: main - name: Get sha id: get_sha run: | sha=$(git rev-parse --short HEAD) echo "sha=$sha" >> $GITHUB_OUTPUT - uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.REPO_ACCESS_TOKEN }} event-type: profile-dolt client-payload: '{"from_version": "${{ steps.get_sha.outputs.sha }}", "future_version": "${{ needs.format-version.outputs.version }}", "mode": "release", "actor": "${{ github.actor }}", "actor_email": "dustin@dolthub.com", "template_script": "./.github/scripts/performance-benchmarking/get-dolt-profile-job-json.sh"}'

“部署 K8s Sysbench 分析作业”的工作原理与“部署 K8s Sysbench 基准测试作业”几乎相同,不同之处在于它将以“分析”模式运行的基准测试作业部署到 K8s 集群,以便我们使用 HEAD 的 。

name: Profile Dolt while Benchmarking
on: repository_dispatch: types: [ profile-dolt ]
jobs: performance: runs-on: ubuntu-22.04 name: Profile Dolt while Benchmarking steps: - name: Checkout uses: actions/checkout@v4 - uses: azure/setup-kubectl@v4 with: version: 'v1.23.6' - name: Install aws-iam-authenticator run: | curl -o aws-iam-authenticator https://amazon-eks.s3.us-west-2.amazonaws.com/1.18.8/2020-09-18/bin/linux/amd64/aws-iam-authenticator && \ chmod +x ./aws-iam-authenticator && \ sudo cp ./aws-iam-authenticator /usr/local/bin/aws-iam-authenticator aws-iam-authenticator version - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-west-2 - name: Create and Auth kubeconfig run: | echo "$CONFIG" > kubeconfig KUBECONFIG=kubeconfig kubectl config set-credentials github-actions-dolt --exec-api-version=client.authentication.k8s.io/v1alpha1 --exec-command=aws-iam-authenticator --exec-arg=token --exec-arg=-i --exec-arg=eks-cluster-1 KUBECONFIG=kubeconfig kubectl config set-context github-actions-dolt-context --cluster=eks-cluster-1 --user=github-actions-dolt --namespace=performance-benchmarking KUBECONFIG=kubeconfig kubectl config use-context github-actions-dolt-context env: CONFIG: ${{ secrets.CORP_KUBECONFIG }} - name: Create Profile Benchmarking K8s Job run: ./.github/scripts/performance-benchmarking/run-benchmarks.sh env: PROFILE: "true" FUTURE_VERSION: ${{ github.event.client_payload.future_version }} FROM_VERSION: ${{ github.event.client_payload.from_version }} MODE: ${{ github.event.client_payload.mode }} ACTOR: ${{ github.event.client_payload.actor }} ACTOR_EMAIL: ${{ github.event.client_payload.actor_email }} REPO_ACCESS_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }} KUBECONFIG: "./kubeconfig" INIT_BIG_REPO: ${{ github.event.client_payload.init_big_repo }} NOMS_BIN_FORMAT: "__DOLT__" TEMPLATE_SCRIPT: ${{ github.event.client_payload.template_script }}

一旦基准测试 K8s 作业在“分析”模式下运行,我们就可以在更新的图表中看到它执行的步骤。我们还看到此作业的输出是一个新的 Golang 配置文件,已上传到 S3,可供我们流程的其余步骤用于创建 pgo 构建。

在分析 K8s 作业结束时,上传配置文件后,它会触发“Release Dolt”工作流程。这个工作流程的工作原理与原来的“Release Dolt”工作流程基本相同,只是它的第一步是下载分析作业上传的 Golang 配置文件。

...  create-pgo-release:    needs: format-version    runs-on: ubuntu-22.04    name: Release PGO Dolt    outputs:      release_id: ${{ steps.create_release.outputs.id }}    steps:      - uses: actions/checkout@v4        with:          ref: main      - name: Set up Go 1.x        uses: actions/setup-go@v5        with:          go-version-file: go/go.mod      - name: Configure AWS Credentials        uses: aws-actions/configure-aws-credentials@v4        with:          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}          aws-region: us-west-2      - name: Get Results        id: get-results        run: aws s3api get-object --bucket="$BUCKET" --key="$KEY" dolt-cpu-profile.pprof        env:          KEY: ${{ github.event.inputs.profile_key || github.event.client_payload.profile_key }}          BUCKET: ${{ github.event.inputs.profile_bucket || github.event.client_payload.bucket }}...

然后,它将下载的配置文件(此处称为 dolt-cpu-profile.pprof )提供给 buildbinaries.sh 脚本,该脚本运行 go build -pgo=./dolt-cpu-profile.pprof ,编译新的 Dolt 二进制文件。然后,与工作流程的原始版本一样,它创建一个 GitHub 版本并将这些二进制文件作为版本资产上传。

在完成之前,此工作流程中的最后一项作业将启动另一项基准测试 K8s 作业,只是这次为该作业提供用于构建 Dolt 二进制文件的 Golang 配置文件的 S3 密钥。

...  trigger-performance-benchmark-email:    needs: [format-version, create-pgo-release]    runs-on: ubuntu-22.04    steps:      - name: Trigger Performance Benchmarks        uses: peter-evans/repository-dispatch@v3        with:          token: ${{ secrets.REPO_ACCESS_TOKEN }}          event-type: release-dolt          client-payload: '{"version": "${{ needs.format-version.outputs.version }}", "actor": "${{ github.actor }}", "profile_key": "${{ github.event.inputs.profile_key || github.event.client_payload.profile_key }}"}'

这会再次将基准测试作业部署到我们的 K8s 集群,但现在该作业将从 S3 下载 Golang 配置文件,并使用它构建 Dolt 的 pgo 二进制文件,以用于基准测试和生成结果。

从 K8s 上下文中的图表中我们可以看到,第二个基准测试作业的最后一步启动了“电子邮件团队”工作流程,以便我们的团队获得现在 pgo 的 Dolt 的基准测试结果。

我们已经做到了!我们现在正在发布 Dolt 的 pgo 版本。

结论

正如您所看到的,更新发布过程以生成 pgo 二进制文件涉及一些复杂性,至少对我们来说是这样。但我们所看到的性能提升绝对值得付出努力。

我希望您发现这对您自己的努力有帮助,我们鼓励您也尝试更新您的版本。如果您愿意,我们很想听听。过来并在我们的 Discord 上分享您的经验。


浏览 145
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报