* perf(promql): use two pointers for sliding range boundaries Replace the stale cursor heuristic in RangeManipulateStream::calculate_range with monotonic left/right cursors. The old path rescanned each evaluation window (O(E x samples-per-window)) and could lose valid samples after sparse gaps or trailing empty windows. The two pointers keep strict monotonic progress, reducing boundary generation to O(N + E) while preserving (curr-range, curr] semantics, start/end shortening, and empty-window output. Controlled release benchmarks (fixed CPU, ABBA): - Public RangeManipulate wall time: ~28% faster at 1m/15s, ~66% at 5m/15s, ~96% at 1h/15s. - Warmed distributed TQL ANALYZE 1h queries: ~17-21% faster end to end; shorter windows stayed within run-order noise. Signed-off-by: discord9 <discord9@163.com> * perf(promql): specialize changes/resets with adaptive edge counting The generic range_fn macro slices, downcasts, and rescans every overlapping window for changes() and resets(). Replace the macro path for these two functions with hand-written UDF wrappers backed by a shared private edge-count kernel: direct raw-offset scans when requested edges are few, otherwise one global u64 edge prefix so each window is answered by a prefix difference. Behavior is preserved bit-for-bit, including raw null-buffer values, NaN semantics, signed zero, infinities, empty/singleton windows, independent timestamp/value offsets, arbitrary window layouts, and exact DataFusion error messages. The shared proc macro, planner, serializer, and other range functions are untouched. Controlled release benchmarks (fixed CPU, ABBA): - Dense sliding windows (k=4/20/240): 91.7-95.6% less public UDF wall time. - Low-coverage fallback (N=4096, 8 windows): 73.9-74.4% faster. - Warmed distributed TQL ANALYZE 5m/1h changes/resets: 12.1-19.7% client and 12.0-20.9% server latency improvement; controls stayed within drift. Signed-off-by: discord9 <discord9@163.com> * ci(query-regression): include PromQL range boundary case in defaults An audit of historical query-regression runs found zero range-query coverage: all 208 PromQL ANALYZE samples were bare selectors, so range evaluation could regress without CI noticing. Wire the promql_range_boundary case (introduced in #8646) into DEFAULT_CASES so label-triggered runs measure the range path. The case is cheap: a ~0.3s synthetic fixture and about a minute of query execution per base/candidate pass. Signed-off-by: discord9 <discord9@163.com> * chore(promql): address sliding range review nits Move test-only imports into their test modules and remove the unused pre-specialization changes and resets helpers. Signed-off-by: discord9 <discord9@163.com> * style(promql): apply pinned rustfmt Signed-off-by: discord9 <discord9@163.com> * test(promql): cover sparse range results Share the changes and resets test scaffolding while keeping their behavior oracles independent. Add an end-to-end sqlness regression for sparse samples, empty intermediate windows, and a valid trailing sample. Signed-off-by: discord9 <discord9@163.com> --------- Signed-off-by: discord9 <discord9@163.com>
Query regression self-hosted runners
The Query Regression workflow uses the dedicated ARC runner scale set
perf-regression-8-cores. ARC runner Pods run in the target Kubernetes cluster
and connect outbound to GitHub. The live scale set is currently paused:
minRunners=0, maxRunners=0, and no runner Pods. Do not resume it without
explicit approval.
Prerequisites and trust admission
Install the ARC scale set controller if it is not already installed:
helm upgrade --install arc \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller \
--namespace arc-systems \
--create-namespace \
--version 0.14.2
Create the GitHub App secret in the runner namespace. Prefer an App limited to
GreptimeTeam/greptimedb:
kubectl -n arc-runners create secret generic greptimedb-arc-github-app \
--from-literal=github_app_id=<app-id> \
--from-literal=github_app_installation_id=<installation-id> \
--from-file=github_app_private_key=<private-key.pem>
The values files here reference that secret by name.
A maintainer applying the query-regression or heavy-regression label is
trust admission for that exact PR revision. query-regression runs the six
routine default cases; heavy-regression runs only the high-cardinality
prom_remote_write_7913 remote-write case. The admitted job may use this scale
set's dedicated, writable persistent cache. pull_request: labeled is the only
PR trigger: the label event snapshots its merge, head, and base SHAs. A queued
job fetches that immutable event merge SHA directly, verifies it is a two-parent
merge whose parents include the snapshotted head exactly once, and uses its
other parent as the actual base build revision. The snapshotted event base is
retained for audit only, so a difference from the merge's non-head parent is
not a failure. The job never follows a newer mutable PR merge ref. An
unavailable event merge, or one that does not contain exactly one snapshotted
head parent, fails closed. A later PR head change does not retarget an already
queued run: it may execute only its previously trusted event revision if that
revision remains fetchable. To run the new revision, the maintainer must review
it, remove the label, and re-add the desired regression label; cancel the old
run if it is no longer wanted. An existing label does not automatically rerun
the benchmark.
Admission does not relax runner hardening or GitHub permissions. Keep service-account token mounting disabled; do not mount host paths, the Docker socket, kubeconfig, or long-lived credentials. The runner and cache initializer use UID/GID 1001, disallow privilege escalation, drop all capabilities, and use the RuntimeDefault seccomp profile. Keep GitHub tokens least-privilege and review workflow changes before admission. Where the CNI supports it, restrict egress to required GitHub Actions, artifact/cache, Rust/crate/toolchain, DNS, and image-registry endpoints; block unrelated cluster services, private ranges, and metadata endpoints unless a case requires them.
Network routing prerequisite
Required split routing is an external environment-specific prerequisite. The responsible network operator must route GitHub Actions, GitHub content, artifact/cache, crates.io, Rust toolchain, and image-registry traffic through the approved path rather than the VPN where applicable. Neither this repository nor Kubernetes configures that route. Verify it with the responsible network operator before any canary.
Runner image and workflow tools
Build and push the derived runner image; it preserves the official
/home/runner/run.sh entrypoint and supplies CI tools needed at runtime. The
image builds otelgen from
WenyXu/otelgen commit
863a3f395d062c7322cc1de08a38774b7fdaa6c8
so trace cases do not download or compile tools during a benchmark run:
docker build \
--platform linux/amd64 \
-f .github/runner-scale-sets/query-regression/Dockerfile \
-t greptime-registry.cn-hangzhou.cr.aliyuncs.com/greptime/greptimedb-query-regression-runner:latest \
.github/runner-scale-sets/query-regression
docker push greptime-registry.cn-hangzhou.cr.aliyuncs.com/greptime/greptimedb-query-regression-runner:latest
Deploy by digest, not mutable tag, by updating both image references in
values-8-cores.yaml after a rebuild. Update RUNNER_IMAGE_DIGEST and bump
RUNNER_IMAGE_EPOCH in query-regression.yml at the same time. If the registry
is private, use a dedicated read-only pull secret only as imagePullSecrets;
never expose registry credentials to runner containers.
Both digest-pinned init and runner containers use IfNotPresent: the immutable
digest makes a cached image safe and avoids adding a registry dependency to every
runner startup.
The runner optionally imports only the non-sensitive HTTP_PROXY,
HTTPS_PROXY, and NO_PROXY variables from the
query-regression-runner-local-env ConfigMap. Manage that ConfigMap locally in
the target namespace; private endpoint configuration must not be committed, and
credentials or secrets must never be placed in a ConfigMap.
Before builds, the workflow asserts UID/GID 1001 and exact image tool versions:
libprotoc 3.21.12, uv 0.11.26, mold 2.30.0, Python 3.12.3, sccache 0.16.0, otelgen commit 863a3f395d062c7322cc1de08a38774b7fdaa6c8,
root-owned rustup 1.29.0, and the image-baked
nightly-2026-03-21 Rust toolchain. Rustup, Cargo, and Rustc must resolve from
/opt/cargo/bin; the runner cannot write /opt/rustup or /opt/cargo/bin.
Protobuf well-known includes, including google/protobuf/any.proto and
google/protobuf/empty.proto, are an image contract and must compile with
protoc.
actions-rust-lang/setup-rust-toolchain@v1 is intentionally removed. The
workflow sets its warning-denying mold RUSTFLAGS directly, disables automatic
Rustup installation, and performs no runtime toolchain downloads.
The workflow no longer uses GitHub rust-cache, setup-protoc, setup-uv, or
runtime Rust setup: the image establishes immutable executable state and the PVC
supplies only reusable Cargo data. Do not reintroduce those actions unless the
corresponding cache or image contract changes.
Capacity and persistent cache
values-8-cores.yaml is normal operation: minRunners=0, maxRunners=1.
values-paused.yaml is the mandatory pause overlay: minRunners=0,
maxRunners=0. The job uses group query-regression-persistent-cache-v1,
queue: max, and cancel-in-progress: false; admitted jobs queue rather than
replacing older pending jobs. During maintenance, cancel admitted queued runs as
well as pausing ARC. Runner Pods have activeDeadlineSeconds=12600.
The runner requests 6 CPU and limits at 8 CPU to preserve minipc-3
allocatable-capacity scheduling headroom; do not reset the request to 8 CPU
without revalidating scheduling capacity.
The cache claim query-regression-build-cache is a nominal 600Gi local-path
PVC in arc-runners. It is ReadWriteOnce; local-path uses
WaitForFirstConsumer binding and Delete reclaim behavior, produces a
node-affine local PV, is non-expandable, and the 600Gi request is not a hard
storage quota. The runner's minipc-3 selector is its only consumer candidate.
The initializer mounts the PVC root at /cache, creates and write-tests these
versioned subpaths as non-root UID/GID 1001, and the runner mounts them as:
| Persistent state | PVC subpath | Runner mount |
|---|---|---|
| Ephemeral Cargo home | emptyDir |
/home/runner/.cargo |
| Cargo registry data | cargo-registry-v1 |
/home/runner/.cargo/registry |
| Cargo Git data | cargo-git-v1 |
/home/runner/.cargo/git |
| Cargo target | query-regression-target-v1 |
/home/runner/query-regression-target |
| Cache metadata | meta-v1 |
/home/runner/query-regression-cache-meta |
| sccache local disk cache | sccache-v1 |
/home/runner/.cache/sccache |
| Immutable Rust toolchain | image-owned | /opt/rustup, /opt/cargo/bin |
The Pod security context uses UID/GID and fsGroup 1001 with
fsGroupChangePolicy: OnRootMismatch; no privileged chown or raw hostPath
is used. CARGO_HOME is a per-Pod emptyDir; only its nested registry and
git mounts are persistent. RUSTUP_HOME=/opt/rustup and /opt/cargo/bin are
image-owned immutable paths, while CARGO_TARGET_DIR, cache metadata, and
SCCACHE_DIR are persistent absolute paths. The runner sets
RUSTC_WRAPPER=/usr/local/bin/sccache,
SCCACHE_DIR=/home/runner/.cache/sccache, SCCACHE_CACHE_SIZE=40G, and
CARGO_INCREMENTAL=0. sccache uses its local PVC disk backend and self-evicts
at 40G; do not add runtime downloads, object storage, or a shared backend.
The repository's .cargo/config.toml remains a trusted per-revision build input.
In contrast, $CARGO_HOME/config*, credentials, installed bins, and Cargo
metadata outside the persistent registry and git data mounts are ephemeral
and cannot survive to another Pod.
The local disk backend has a one-server constraint. maxRunners=1 and the
unchanged query-regression-persistent-cache-v1 workflow concurrency group
serialize runs; do not increase runner capacity or relax that serialization
while this backend is in use. Base and candidate builds share the target; Cargo
fingerprints invalidate source and dependency changes. The workflow records the
sccache version and relevant environment in the target ABI marker, starts and
zeros sccache after cache and toolchain checks, shows initial/base/candidate
statistics, and resets statistics between base and candidate builds.
Disk preflight and cleanup contract
Before applying or unpausing, verify the backing filesystem on minipc-3 has
at least 900GiB free. The current local-path provisioner source is
/opt/local-path-provisioner; measure the filesystem containing it:
df -PB1G /opt/local-path-provisioner
The workflow reports du, df -P, human-readable free space, and inode
availability before builds and in an always-run report. Its cleanup is narrow
and non-destructive:
- warn at target size 400GiB; at 450GiB clear only the complete target root;
- warn at Cargo registry-plus-Git data size 60GiB; at 80GiB remove only
registry/srcandgit/checkouts, then abort if that persistent data remains at least 80GiB; - below 300GiB backing free space, clear the complete target root first, remeasure, then remove only those Cargo extracted trees and checkouts; abort if free space is still below 300GiB;
- never automatically remove Cargo registry cache/index, Git database, the image-owned Cargo bin or Rustup toolchain, cache metadata, the self-evicting sccache directory, or the PVC.
The target clear uses fixed absolute roots and removes all entries, including
dotfiles. After migration validation, remove obsolete cargo-home-v1 and
rustup-home-v1 only in explicit maintenance while ARC is 0/0 and no runner Pod
exists; they are not mounted by the current configuration.
Deploy and pause safely
First verify the configured external network route and the disk preflight. Apply the PVC; while
the scale set is paused, it is expected to remain Pending because
WaitForFirstConsumer has no scheduled runner:
kubectl apply --dry-run=server \
-f .github/runner-scale-sets/query-regression/cache-pvc.yaml
kubectl apply -f .github/runner-scale-sets/query-regression/cache-pvc.yaml
Render normal and paused configurations. Normal values are always first; the pause overlay is always last:
helm template perf-regression-8-cores \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
--namespace arc-runners --version 0.14.2 \
--set controllerServiceAccount.name=arc-gha-rs-controller \
--set controllerServiceAccount.namespace=arc-systems \
-f .github/runner-scale-sets/query-regression/values-8-cores.yaml
helm template perf-regression-8-cores \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
--namespace arc-runners --version 0.14.2 \
--set controllerServiceAccount.name=arc-gha-rs-controller \
--set controllerServiceAccount.namespace=arc-systems \
-f .github/runner-scale-sets/query-regression/values-8-cores.yaml \
-f .github/runner-scale-sets/query-regression/values-paused.yaml
The first post-merge Helm deployment must reconcile the release in paused mode. Keep the pause overlay last:
# First post-merge deployment and every return to paused mode: 0/0.
helm upgrade --install perf-regression-8-cores \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
--namespace arc-runners --create-namespace --version 0.14.2 \
--reset-values --wait \
-f .github/runner-scale-sets/query-regression/values-8-cores.yaml \
-f .github/runner-scale-sets/query-regression/values-paused.yaml
# Expect 0/0 and no runner resources before considering normal mode.
kubectl -n arc-runners get autoscalingrunnerset perf-regression-8-cores \
-o jsonpath='{.spec.minRunners}{"/"}{.spec.maxRunners}{"\n"}'
kubectl -n arc-runners get ephemeralrunners,pods \
-l actions.github.com/scale-set-name=perf-regression-8-cores
Only after that verification and separate explicit approval, apply normal 0/1 operation without the pause overlay:
helm upgrade --install perf-regression-8-cores \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
--namespace arc-runners --create-namespace --version 0.14.2 \
--reset-values --wait \
-f .github/runner-scale-sets/query-regression/values-8-cores.yaml
Do not use bare helm rollback, --atomic, or --reuse-values: a stored
revision can restore nonzero runner capacity. Inspect rendered manifests for
capacity, the minipc-3 selector, cache claim and mounts, initializer,
security context, and resources. After approved normal mode receives its first
canary, the PVC binds to minipc-3.
For local-PV node loss, cache recovery is intentionally disposable: return to 0/0, recreate the PVC on a healthy node, cold-fill it, and run a new canary.
Canary and rollback
With explicit approval, run two identical workflow_dispatch canaries on
perf-regression-8-cores, using immutable full base and candidate commit SHAs
and cargo_profile=nightly. The first is the cold fill; the second verifies warm
reuse. Record the workflow's base/candidate build elapsed logs and cache
ABI-marker output, initial/base/candidate sccache statistics, and cache report.
Confirm the image tool contract (root-owned Rustup/Cargo paths, baked nightly,
and non-writable /opt roots) and that the ephemeral Cargo home contains only
the mounted registry/Git data before Cargo creates per-Pod state.
Obtain dependency and tool network byte counters from the configured
environment counter source, filtered to minipc-3 and the dependency/tool
destinations.
Accept the canary only when all of the following hold:
- exactly one runner Pod runs on
minipc-3, and both jobs use the same bound PV; - UID/GID 1001 cache mounts are writable; the warm run does not invalidate the target or bulk-redownload crates or toolchains; sccache reports separate base and candidate build statistics without server or cache-path errors; immutable Rustup/Cargo roots remain non-writable and only registry/Git data persists;
- warm base build time is at most 50% of cold base build time;
- warm dependency/tool network bytes are at most 10% of cold fill bytes;
- cache sizes remain below soft watermarks, node free space remains at least 300GiB, and the benchmark is correct without TLS EOFs or timeouts;
- the configured environment counter source confirms this traffic is outside VPN accounting.
Immediately return to 0/0 after either canary unless ongoing normal operation
has been explicitly approved; return immediately on any traffic, cache, disk,
TLS, or correctness failure. To roll back, use the paused Helm upgrade above,
or another explicit helm upgrade --install with known-good values followed by
values-paused.yaml, --reset-values, and --wait. Do not delete the PVC
automatically; preserve it for diagnosis unless intentionally discarding cache.
Future optional phases
The current phase uses a digest-pinned image with sccache 0.16.0 and no shared
cache service. Optional follow-ups are an image additionally seeded with the
exact Rust toolchain and cargo fetch --locked; or an internal read/write
sccache backend or Cargo/Git mirror. Evaluate them only if persistent PVC reuse
is insufficient.