Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .asf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@ github:
main:
required_pull_request_reviews:
required_approving_review_count: 1
required_status_checks:
# `TPC Required Checks` is the aggregator job in
# .github/workflows/tpc.yml. It is deliberately the only entry: the
# TPC-H and TPC-DS jobs are skipped on pull requests that do not carry
# a `run-tpch` / `run-tpcds` label, and a skipped job publishes under a
# different name than a run one, so neither can be required directly.
# On a pull request it passes; in the merge queue it requires both
# suites, which is the point of the queue.
#
# Careful when editing this list. A context that never reports blocks
# every merge to main, including the merge that would fix this file,
# and recovering from that needs an INFRA Jira ticket.
contexts:
- "TPC Required Checks"
branch-53:
required_pull_request_reviews:
required_approving_review_count: 1
Expand All @@ -73,6 +87,49 @@ github:
- root
restrict_deletion: true
restrict_force_push: true
# GitHub's merge queue. Classic `protected_branches` above cannot express
# it, but .asf.yaml rulesets accept a raw Rulesets API payload, so no INFRA
# ticket is needed. Same approach as apache/datafusion-comet.
#
# Only the default branch is queued. Merge queue rules do not support
# wildcard ref patterns, so the release branches keep plain branch
# protection.
- name: Merge Queue
target: branch
enforcement: active
bypass_actors:
# apache/root - ASF Infra, so a wedged queue can always be recovered.
- actor_id: 118420
actor_type: Team
bypass_mode: always
conditions:
ref_name:
include:
- "~DEFAULT_BRANCH"
exclude: []
rules:
- type: merge_queue
parameters:
# Matches enabled_merge_buttons above: squash is the only method
# the repository allows.
merge_method: SQUASH
# Every entry's own build has to be green, not just the last one's.
grouping_strategy: ALLGREEN
# How many merge_group builds run at once. Each entry gets its own
# build of itself plus everything ahead of it, and the merge limits
# below do not combine those builds, so this is the throughput
# dial. Each build is five benchmark jobs on ASF runners.
max_entries_to_build: 2
# These only cap how many already-green entries land in one merge.
# No minimum and no wait, so a lone PR merges the moment its build
# is green.
min_entries_to_merge: 1
min_entries_to_merge_wait_minutes: 0
max_entries_to_merge: 1
# How long the queue waits for a required check to report before
# treating it as failed and evicting the entry. A passing run is
# 10-20 minutes, so this is mostly headroom for runner queue times.
check_response_timeout_minutes: 180
pull_requests:
# enable updating head branches of pull requests
allow_update_branch: true
Expand Down
231 changes: 231 additions & 0 deletions .github/workflows/tpc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

# Decides when the TPC-H and TPC-DS suites run, and reports one verdict for them.
#
# Merging goes through GitHub's merge queue (see `rulesets` in `.asf.yaml`), so
# these suites are tiered:
#
# pull_request they do not run, unless the PR carries `run-tpch` and/or
# `run-tpcds`. A labelled PR re-runs the suite on every push.
# merge_group the authoritative gate. Both suites run against the merge
# result whenever the entry touches something that can affect
# them (see the filter in `changes` below).
# push to main nothing is tested, since the queue already tested the tree
# that landed. Only the build caches are refreshed, because a
# cache saved on the queue's throwaway branch is deleted with it
# and pull requests and the queue restore from main's.
#
# Applying a label to an open PR runs that suite straight away without a new
# push; that goes through tpc_label.yml, see there for why it is separate.
#
# `TPC Required Checks` is the only required status context for these suites.
# The suites cannot be required directly: on a PR that has no label they are
# skipped, and a skipped job reports under a different name than a run one.
# Careful when renaming it: a required context that never reports blocks every
# merge to main, including the merge that would fix `.asf.yaml`.

name: TPC benchmarks

permissions:
contents: read

# Label runs reach this file through tpc_label.yml, so `github.workflow`
# already separates them from the commit run; keying on the label name as well
# keeps two different labels from cancelling each other. Every other run is
# keyed on its event name, so a new push still supersedes its predecessor.
concurrency:
group: ${{ github.repository }}-${{ github.head_ref || github.sha }}-${{ github.workflow }}-${{ github.event.action == 'labeled' && github.event.label.name || github.event_name }}
cancel-in-progress: true

# No `labeled` type here: tpc_label.yml handles it and calls this file through
# `workflow_call`. When one workflow runs twice at the same commit, GitHub
# evaluates the required check against only one of the two runs, and a
# cancelled or skipped one can leave `TPC Required Checks` unreported.
on:
pull_request:
types: [opened, synchronize, reopened]
workflow_call:
merge_group:
push:
branches: [ "main" ]
workflow_dispatch:

jobs:
changes:
name: Detect changes
runs-on: ubuntu-latest
outputs:
tpch: ${{ steps.compute.outputs.tpch }}
tpcds: ${{ steps.compute.outputs.tpcds }}
warm_cache: ${{ steps.compute.outputs.warm_cache }}
steps:
- uses: actions/checkout@v7.0.1
with:
# Need history to diff the queue entry and the push against their base.
fetch-depth: 0

- name: Compute outputs
id: compute
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
EVENT_ACTION: ${{ github.event.action }}
LABEL_NAME: ${{ github.event.label.name }}
PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
MQ_BASE_SHA: ${{ github.event.merge_group.base_sha }}
MQ_HEAD_SHA: ${{ github.event.merge_group.head_sha }}
PUSH_BEFORE: ${{ github.event.before }}
PUSH_AFTER: ${{ github.sha }}
run: |
set -euo pipefail

# Reads changed file names on stdin. Keep in step with the inputs of
# the suites: the crates, the benchmark runner and its cluster
# script, the build setup, and these workflow files. Docs-only
# changes cannot affect them.
relevant() {
grep -v '\.md$' | grep -Eq '^(ballista/|benchmarks/|Cargo\.toml$|Cargo\.lock$|rust-toolchain\.toml$|ci/scripts/ballista_cluster\.sh$|\.github/actions/setup-(builder|benchmark-builder)/|\.github/workflows/(tpc|tpc_label|tpch|tpcds)\.yml$)'
}

tpch=false
tpcds=false
warm_cache=false

case "$EVENT_NAME" in
workflow_dispatch)
tpch=true
tpcds=true
;;
pull_request)
if [[ "$EVENT_ACTION" == "labeled" ]]; then
# Only the suite the new label gates. The other label, if
# present, already had its own run when it was applied.
[[ "$LABEL_NAME" == "run-tpch" ]] && tpch=true
[[ "$LABEL_NAME" == "run-tpcds" ]] && tpcds=true
else
# A label is an explicit request, so it ignores the path filter.
jq -e 'index("run-tpch")' <<<"$PR_LABELS" >/dev/null && tpch=true
jq -e 'index("run-tpcds")' <<<"$PR_LABELS" >/dev/null && tpcds=true
fi
;;
merge_group)
# base_sha is the queue commit of the entry ahead of this one, and
# main only for the entry at the front, so this covers this
# entry's own changes. The entries ahead were diffed by their own
# runs; ALLGREEN in the ruleset is what makes that enough.
if git diff --name-only "$MQ_BASE_SHA"..."$MQ_HEAD_SHA" | relevant; then
tpch=true
tpcds=true
fi
;;
push)
# First push to a branch has an all-zero before sha.
if [[ "$PUSH_BEFORE" =~ ^0+$ ]] || git diff --name-only "$PUSH_BEFORE".."$PUSH_AFTER" | relevant; then
warm_cache=true
fi
;;
esac

{
echo "tpch=$tpch"
echo "tpcds=$tpcds"
echo "warm_cache=$warm_cache"
} | tee -a "$GITHUB_OUTPUT"

tpch:
name: TPC-H
needs: changes
if: needs.changes.outputs.tpch == 'true'
uses: ./.github/workflows/tpch.yml

tpcds:
name: TPC-DS
needs: changes
if: needs.changes.outputs.tpcds == 'true'
uses: ./.github/workflows/tpcds.yml

# Only builds, so the cache the suites restore from main stays warm. The keys
# are the `shared-key` each suite passes to the same action.
warm-cache:
name: Refresh build cache (${{ matrix.shared-key }})
needs: changes
if: needs.changes.outputs.warm_cache == 'true'
runs-on: ubuntu-latest
container:
image: amd64/rust
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
shared-key: [ "tpch-sf10", "tpcds-sf1" ]
steps:
- uses: actions/checkout@v7.0.1
with:
fetch-depth: 1

- name: Setup toolchain and build Ballista binaries
uses: ./.github/actions/setup-benchmark-builder
with:
shared-key: ${{ matrix.shared-key }}

# The one required status check. Always runs and always reports, so a pull
# request that skips the suites still gets a verdict.
required:
name: TPC Required Checks
needs: [ changes, tpch, tpcds, warm-cache ]
if: always()
runs-on: ubuntu-latest
steps:
- name: Check results
env:
CHANGES_RESULT: ${{ needs.changes.result }}
TPCH_EXPECTED: ${{ needs.changes.outputs.tpch }}
TPCH_RESULT: ${{ needs.tpch.result }}
TPCDS_EXPECTED: ${{ needs.changes.outputs.tpcds }}
TPCDS_RESULT: ${{ needs.tpcds.result }}
WARM_CACHE_EXPECTED: ${{ needs.changes.outputs.warm_cache }}
WARM_CACHE_RESULT: ${{ needs.warm-cache.result }}
run: |
set -euo pipefail
status=0

if [[ "$CHANGES_RESULT" != "success" ]]; then
echo "::error::Detect changes: $CHANGES_RESULT"
exit 1
fi

# A job that should have run must have succeeded. One that should not
# have run must not have run at all. `skipped` would otherwise pass
# for a job that was meant to gate.
check() {
local name=$1 expected=$2 result=$3
if [[ "$expected" == "true" && "$result" != "success" ]]; then
echo "::error::$name was required but finished as: $result"
status=1
elif [[ "$expected" != "true" && "$result" != "skipped" ]]; then
echo "::error::$name was not required but finished as: $result"
status=1
else
echo "$name: $result"
fi
}

check "TPC-H" "$TPCH_EXPECTED" "$TPCH_RESULT"
check "TPC-DS" "$TPCDS_EXPECTED" "$TPCDS_RESULT"
check "Refresh build cache" "$WARM_CACHE_EXPECTED" "$WARM_CACHE_RESULT"
exit "$status"
54 changes: 54 additions & 0 deletions .github/workflows/tpc_label.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

# Runs a suite the moment its label is applied to an open pull request, without
# a new push: `run-tpch` runs TPC-H and `run-tpcds` runs TPC-DS. The pipeline
# is tpc.yml itself; it reads the `labeled` action from the event context,
# which a called workflow shares with its caller, and runs only what the new
# label gates.
#
# This is a separate workflow, and not a `labeled` type on tpc.yml's own
# `pull_request` trigger, because of how GitHub treats two runs of one
# workflow at the same commit: it evaluates the pull request's required checks
# against only one of them. A pull request opened with a label already applied
# fires `opened` and `labeled` together, and if the label run is the one
# picked, `TPC Required Checks` can show as "Expected" forever and the merge
# queue never accepts the pull request. Runs of this workflow get their own
# check suite, and every check in it is nested under the calling job's name, so
# none can publish the required name or hide the commit run's verdict.
#
# The consequence is that a label run is early feedback and not a gate: the
# merge queue is what enforces the suites.

name: TPC benchmarks (label run)

permissions:
contents: read

# No `concurrency` here. tpc.yml declares its own group, keyed on this
# workflow's name and the label, and the same group on the caller as well
# would deadlock the two against each other.
on:
pull_request:
types: [labeled]

jobs:
tpc:
name: Label run
# Any other label (a bot or a committer tagging the PR) is not for us.
if: github.event.label.name == 'run-tpch' || github.event.label.name == 'run-tpcds'
uses: ./.github/workflows/tpc.yml
Loading
Loading