diff --git a/scripts/mma-backport-helper.py b/scripts/mma-backport-helper.py new file mode 100644 index 000000000000..667c0b98219b --- /dev/null +++ b/scripts/mma-backport-helper.py @@ -0,0 +1,620 @@ +#!/usr/bin/env python3 +""" +mma-backport-helper.py + +Identifies MMA-related PRs on upstream/master since the release-26.2 branch +point that have NOT yet been backported to upstream/release-26.2. + +The script works in three passes: + 1. Path-based: finds first-parent (merge) commits on master that touch + known MMA-related directories. + 2. Title-based: finds first-parent commits whose commit message matches + MMA-related keywords (catches PRs that only touch files outside the + canonical directories, e.g. server plumbing, metrics, etc.). + 3. Author-based: finds PRs authored by MMA team members (catches PRs + that don't touch MMA directories or use MMA keywords, e.g. supporting + infrastructure changes). + +It then extracts PR numbers from each commit's subject line and subtracts +the set of PRs already backported to release-26.2. + +Output is in topological merge order (oldest first), which is the order +you'd want to backport in to minimize conflicts. + +Usage: + python3 scripts/mma-backport-helper.py [--fetch] [--upstream NAME] +""" + +import argparse +import re +import subprocess +import sys +from dataclasses import dataclass, field +from typing import Optional + + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- + +# Core MMA packages and their test data. +MMA_DIRS = [ + "pkg/kv/kvserver/allocator/mmaprototype", + "pkg/kv/kvserver/allocator/allocatorimpl", + "pkg/kv/kvserver/mmaintegration", + "pkg/kv/kvserver/mma_store_rebalancer.go", + "pkg/kv/kvserver/asim", + # Supporting packages frequently modified alongside MMA. + "pkg/kv/kvserver/allocator/storepool", +] + +# Perl-compatible regex for git --grep (matches against full commit message). +# \b is needed because "mma" appears as a substring in words like "summary". +TITLE_GREP_PATTERN = ( + r"\bmma\b|mmaprototype|mmaintegration|storeliveness|\basim\b" + r"|allocator.*(mma|rebalanc)" + r"|storepool.*(mma|suspect|liveness)" +) + +# MMA team members: GitHub username -> list of git author emails. +# Used by the author-based pass (Pass 3) to find PRs by team members that +# might not touch MMA directories or use MMA keywords in the title. +MMA_TEAM: dict[str, list[str]] = { + "sumeerbhola": [ + "sumeer@cockroachlabs.com", + "54990988+sumeerbhola@users.noreply.github.com", + ], + "tbg": [ + "tobias.b.grieger@gmail.com", + ], + "wenyihu6": [ + "wenyi@cockroachlabs.com", + "wenyi.hu@cockroachlabs.com", + ], + "angeladietz": [ + "angela.dietz@cockroachlabs.com", + ], + "5hubh4m": [ + "shubham.chaudhary@cockroachlabs.com", + ], +} + +# Derived sets for quick lookups. +MMA_TEAM_GITHUB_USERS: set[str] = set(MMA_TEAM.keys()) +MMA_TEAM_EMAILS: set[str] = { + email for emails in MMA_TEAM.values() for email in emails +} + +# Python regex for filtering individual PR descriptions within multi-PR merges. +MMA_PR_DESC_RE = re.compile( + r"\bmma\b|mmaprototype|mmaintegration|allocator|storepool" + r"|storeliveness|store.liveness|asim", + re.IGNORECASE, +) + +# PRs to ignore (false positives or intentionally skipped). +# Map from PR number to a short reason string. +IGNORED_PRS: dict[int, str] = { + # Author-based matches not specific to MMA. + 165450: "author-based: kvnemesis: add MVCC GC operation to KVNemesis", + 166328: "author-based: replicastats: removed an unnecessary indirection in `replicaStatsRecord`", + 166419: "author-based: roachtest: enable Go execution traces in kv/restart/nodes=12", + 166421: "author-based: roachtest: enable Go execution traces in kv/splits", + 166532: "author-based: investigate: analyze heap profiles on OOM failures", + 166423: "author-based: roachtest: add Datadog Logs link to GitHub issues", + 166560: "author-based: tsdump2duck: add CLI tool to convert tsdump.gob to DuckDB SQL", + 167100: "author-based: engflow-artifacts: rewrite protobuf parsing with proper schema", + 167211: "author-based: CLAUDE.md: improve PR description guidance", + 166156: "author-based: intentresolver: chunk intent resolution to limit in-flight requests", + 166698: "author-based: kvserver: introduce stageDestroyReplica and pendingReplicaDestruction", + 166924: "author-based: server: use liveness cache for high-frequency scan callers", + 167297: "author-based: intentresolver: expose intent budget constants as env vars", + 166760: "author-based: kvcoord: fix a potential persistent failure loop in `DistSender`", + 167289: "author-based: kvserver: stop treating split/merge trigger errors as replica corruption", + # Admission/AC SQL CPU token workstream (separate from MMA). + 166745: "admission: plumb SQL CPU time token admission through SQLCPUProvider", + 166792: "admission: integrate SQL CPU admission with CTT WorkQueue", + 168231: "admission: add local token reservation to SQLCPUHandle", + 168298: "admission: introduce CPU time token AC mode setting", + 168383: "admission: add maxCPU burst qualification for resource groups", + 168438: "admission: rename from tenantID to groupID", + 168439: "admission: add resource group routing and per-group burst refill", + 168829: "admission: add modeStrategy interface with serverlessStrategy", + 169195: "admission: add composite groupKey type", + # SQL/UI/docs/CI unrelated to MMA. + 166529: "sql: add WITH ZONE option to SHOW RANGES", + 167927: "sql: rename zone_config_conformant to zone_config_needs_split", + 167463: "docs: remove unused metrics.html", + 168260: "ui/cluster-ui: fix empty cluster settings page", + 168261: "ui/db-console: add 'Changed only' filter to cluster settings page", + 168121: "roachtest: add CLAUDE.md with basic usage instructions", + 168452: "ci: fix lint_test panic in affected-targets unit test runs", + 168444: "ci: opt T-kv into auto-investigate for test failures", + # Roachtest/test infra not MMA-specific. + 167120: "timeutil: fix timer pool vs synctest incompatibility", + 167989: "roachtest: increase HTTP timeout in follower-reads test", + 168156: "kvserver: deflake TestTenantCtx", + 168243: "roachtest: bump post-test assertion timeout to 20m", + 168248: "roachtest: extract artifact collection to artifactsutil", + 167920: "roachtest: add WaitForFullReplication helper", + 168047: "kvserver: isolate subtests in TestReplicaLatchingOptimisticEvaluation", + 167616: "kvserver: skip TestStoreRangeSplitAndMergeWithGlobalReads under duress", + # Other unrelated kvserver/storage. + 167295: "kvserver: remove ReplicaCorruptionError (follows ignored #167289)", + 165577: "storage: add MultiEngineCompactionScheduler", + 167853: "kvserver: add rate limiting for low-priority bulk read operations like TTL", + # Already backported but invisible to script — backport commits don't reference these PR numbers. + 167051: "already backported via #167468 (release-26.2: asim changes)", + 167110: "already backported via #167468 (release-26.2: asim changes)", + 167897: "already backported (b5f05189765, d3ea3080d22, 5bae83c8aac on release-26.2; cherry-picks lack PR ref)", + 167939: "already backported via #168475 (release-26.2: allocator: disable follow-the-workload when MMA is enabled)", + # Touches non-mma kvserver/load/allocator code, not mma-specific. + 167861: "kvserver: improve store.capacity allocations", + # Test deflake, not mma-relevant. + 167655: "storeliveness: skip flaky multi-store tests under duress", + # Roachtest-only scoring change, no production impact. + 167696: "roachtest/perturbation: simplify scoring to impact ratios", + # Admission/AC SQL CPU token workstream (separate from MMA), batch 2. + 169320: "admission: add rmStrategy for RM-mode burst refill", + 169428: "admission: preserve non-tenant groupWeights across SetTenantWeights swap", + 169708: "admission: retire tenant-weight cluster settings", + # Unrelated to MMA, batch 2. + 169584: "sql: lower the priority of AUTO CREATE STATS job to BulkLowPri", + 169336: "kvserver: plumb ScanStats to obtain pebble iterator statistics during request evaluation", + 169658: "build/teamcity: upload phases.json artifacts for perturbation tests", + 169659: "roachtest/perturbation: enforce 0.8x throughput floor", + # Master-and-onward only — explicit v26.3 default change, not for 26.2. + 169430: "kvserver: enable multi-metric allocator by default in v26.3 (master+ only)", + # Master-and-onward only per intent. + 169669: "allocatorimpl: retire kv.allocator.load_based_lease_rebalancing.enabled (master+ only)", + # Already backported via separate release PRs but invisible to script (cherry-picks lack original PR ref). + 165719: "already backported via #169344 (release-26.2: mmaprototype: dynamically verbose logs for overloaded stores)", + 169446: "already backported via #169590 (release-26.2: kvserver/allocator/mmaprototype: lock advisor methods)", + 169230: "already backported via #169711 (release-26.2: mmaprototype: add structured snapshot of cluster state)", + 169233: "already backported via #169734 (release-26.2: cli: collect mma allocator state in debug zip)", + 169413: "already backported via #169742 (release-26.2: mmaprototype: count overloaded-but-blocked stores in MMA metrics)", + 169741: "already backported via #169761 (release-26.2: mmaprototype: add captured_at to cluster state snapshot)", + 169747: "already backported via #169876 (release-26.2: mmaprototype: materialize per-range constraint analysis in snapshot)", +} + +# Regexes to extract PR numbers from merge commit subjects. +# Bors merges look like "Merge #NNNNN #NNNNN ..." — all numbers are PRs. +BORS_PR_NUM_RE = re.compile(r"#(\d+)") +# Squash/trunk-io merges look like "title (#NNNNN)" — only the trailing +# parenthesized number is the PR; other #refs in the title are issue numbers. +SQUASH_PR_NUM_RE = re.compile(r"\(#(\d+)\)\s*$") + +# Format used to separate subject and body in a single git log call. +# Using a delimiter that won't appear in commit messages. +RECORD_SEP = "------" +FIELD_SEP = "------" + + +# --------------------------------------------------------------------------- +# Data types +# --------------------------------------------------------------------------- + +@dataclass +class PRInfo: + number: int + title: str + merge_sha: str + source: str # "path", "title", "author", or "both" + merge_pos: int = 0 # topological position (0 = oldest) + + +# --------------------------------------------------------------------------- +# Git helpers +# --------------------------------------------------------------------------- + +def git(*args: str, check: bool = True) -> str: + """Run a git command and return stripped stdout.""" + result = subprocess.run( + ["git"] + list(args), + capture_output=True, + text=True, + check=check, + ) + return result.stdout.strip() + + +def git_lines(*args: str) -> list[str]: + """Run a git command and return non-empty output lines.""" + out = git(*args) + return [line for line in out.splitlines() if line] + + +# --------------------------------------------------------------------------- +# Core logic +# --------------------------------------------------------------------------- + +def find_split_point(master: str, release: str) -> str: + return git("merge-base", master, release) + + +def parse_merge_commit(raw: str) -> tuple[str, str, str]: + """Parse a record from our custom git log format into (sha, subject, body).""" + parts = raw.split(FIELD_SEP, 2) + sha = parts[0].strip() + subject = parts[1].strip() if len(parts) > 1 else "" + body = parts[2].strip() if len(parts) > 2 else "" + return sha, subject, body + + +def extract_pr_numbers(subject: str) -> list[int]: + """Extract PR numbers from a merge commit subject. + + Bors subjects ("Merge #NNN #NNN") list only PR numbers, so we extract all. + Squash/trunk-io subjects ("title (#NNN)") may contain issue refs in the + title text; only the trailing "(#NNN)" is the actual PR number. + """ + if subject.startswith("Merge #"): + return [int(m) for m in BORS_PR_NUM_RE.findall(subject)] + m = SQUASH_PR_NUM_RE.search(subject) + return [int(m.group(1))] if m else [] + + +def extract_pr_title(body: str, pr_num: int, subject: str) -> str: + """Get a human-readable title for a PR from the merge commit body. + + Bors merge bodies contain lines like: + 159099: mma: plumbing changes for existing MMA metrics r=user a=user + We extract the title and strip reviewer/author annotations. + """ + for line in body.splitlines(): + if line.startswith(f"{pr_num}:"): + # Strip "r=... a=..." annotations. + title = re.sub(r"\s+r=.*", "", line) + # Strip the leading "NNNNN: " prefix. + title = re.sub(rf"^{pr_num}:\s*", "", title) + return title + # Fallback: for squash-merge style "title (#NNNNN)", strip the PR ref. + title = re.sub(r"\s*\(#\d+\)\s*$", "", subject) + return title + + +def get_first_parent_commits( + master: str, split: str, extra_args: Optional[list[str]] = None +) -> list[tuple[str, str, str]]: + """Get first-parent commits as (sha, subject, body) tuples. + + Returns in git's default order (newest first). + """ + fmt = f"{FIELD_SEP}".join(["%H", "%s", "%b"]) + args = [ + "log", "--first-parent", + f"--format={RECORD_SEP}{fmt}", + master, "--not", split, + ] + if extra_args: + args.extend(extra_args) + raw = git(*args) + if not raw: + return [] + records = raw.split(RECORD_SEP) + results = [] + for rec in records: + rec = rec.strip() + if not rec: + continue + results.append(parse_merge_commit(rec)) + return results + + +def find_path_based_prs( + master: str, split: str, dirs: list[str] +) -> dict[int, PRInfo]: + """Pass 1: find PRs in merge commits that touch MMA directories.""" + commits = get_first_parent_commits(master, split, ["--"] + dirs) + prs: dict[int, PRInfo] = {} + for sha, subject, body in commits: + pr_nums = extract_pr_numbers(subject) + is_multi = len(pr_nums) > 1 + for num in pr_nums: + title = extract_pr_title(body, num, subject) + if is_multi and not MMA_PR_DESC_RE.search(title): + # Multi-PR merge: skip co-merged PRs that aren't MMA-related. + continue + prs[num] = PRInfo( + number=num, + title=title, + merge_sha=sha, + source="path", + ) + return prs + + +def find_title_based_prs( + master: str, split: str +) -> dict[int, PRInfo]: + """Pass 2: find PRs whose commit message matches MMA keywords.""" + commits = get_first_parent_commits( + master, split, + [f"--grep={TITLE_GREP_PATTERN}", "-i", "--perl-regexp"], + ) + prs: dict[int, PRInfo] = {} + for sha, subject, body in commits: + pr_nums = extract_pr_numbers(subject) + is_multi = len(pr_nums) > 1 + for num in pr_nums: + title = extract_pr_title(body, num, subject) + if is_multi and not MMA_PR_DESC_RE.search(title): + continue + prs[num] = PRInfo( + number=num, + title=title, + merge_sha=sha, + source="title", + ) + return prs + + +def find_author_based_prs( + master: str, split: str +) -> dict[int, PRInfo]: + """Pass 3: find PRs authored by MMA team members. + + Uses two sub-strategies: + A) Bors-style merges: grep for 'a=' in commit body. + B) Non-bors merges (trunk-io, etc.): batch-check the author email of + each merged branch tip. + """ + prs: dict[int, PRInfo] = {} + usernames = sorted(MMA_TEAM_GITHUB_USERS) + + # --- Sub-pass A: bors merges with a= in body --- + pattern = "|".join(rf"a={re.escape(u)}\b" for u in usernames) + commits = get_first_parent_commits( + master, split, + [f"--grep={pattern}", "--perl-regexp"], + ) + bors_merge_shas: set[str] = set() + for sha, subject, body in commits: + bors_merge_shas.add(sha) + pr_nums = extract_pr_numbers(subject) + is_multi = len(pr_nums) > 1 + for num in pr_nums: + title = extract_pr_title(body, num, subject) + if is_multi: + # Only include PRs whose line has a=. + matched = False + for line in body.splitlines(): + if line.startswith(f"{num}:"): + if any(f"a={u}" in line for u in usernames): + matched = True + break + if not matched: + continue + prs[num] = PRInfo( + number=num, title=title, merge_sha=sha, source="author", + ) + + # --- Sub-pass B: non-bors merges – batch-check branch tip authors --- + # Get all first-parent merges with their parent SHAs. + lines = git_lines( + "log", "--first-parent", "--format=%H %P", + master, "--not", split, + ) + # Build mapping: branch_tip_sha -> merge_sha (skip merges already handled). + tip_to_merge: dict[str, str] = {} + for line in lines: + parts = line.split() + if len(parts) < 3: + continue # not a merge commit + merge_sha = parts[0] + if merge_sha in bors_merge_shas: + continue + for tip_sha in parts[2:]: + tip_to_merge[tip_sha] = merge_sha + + if tip_to_merge: + # Batch-check author emails of branch tips in a single git call. + tip_shas = list(tip_to_merge.keys()) + raw = git("log", "--no-walk", "--format=%H %ae", *tip_shas) + matched_merge_shas: set[str] = set() + for line in raw.splitlines(): + line = line.strip() + if not line: + continue + parts = line.split(None, 1) + if len(parts) < 2: + continue + tip_sha, email = parts + if email in MMA_TEAM_EMAILS and tip_sha in tip_to_merge: + matched_merge_shas.add(tip_to_merge[tip_sha]) + + # Fetch details for the matched merge commits. + fmt = FIELD_SEP.join(["%H", "%s", "%b"]) + for merge_sha in matched_merge_shas: + raw_detail = git("log", "-1", f"--format={fmt}", merge_sha) + sha, subject, body = parse_merge_commit(raw_detail) + for num in extract_pr_numbers(subject): + if num not in prs: + title = extract_pr_title(body, num, subject) + prs[num] = PRInfo( + number=num, title=title, merge_sha=sha, source="author", + ) + + return prs + + +def get_backported_prs(release: str, split: str) -> set[int]: + """Find PR numbers already referenced on the release branch. + + Backport commits reference original PR numbers in their branch name + or commit body (e.g. "backport-release-26.2-159099"). + """ + raw = git( + "log", "--first-parent", "--format=%s%n%b", + release, "--not", split, + ) + # Match 5-6 digit numbers which are likely PR numbers. + return {int(m) for m in re.findall(r"\d{5,6}", raw)} + + +def assign_merge_positions( + prs: dict[int, PRInfo], master: str, split: str +) -> None: + """Assign topological merge positions to PRs (oldest = 0). + + This determines the backport order: apply in ascending position order. + We only iterate over first-parent subjects (cheap string scan) and only + for the PR numbers we care about, so this is fast. + """ + # Get all first-parent subjects in oldest-first order. + lines = git_lines( + "log", "--first-parent", "--format=%H %s", + "--reverse", master, "--not", split, + ) + remaining = set(prs.keys()) + for pos, line in enumerate(lines): + if not remaining: + break + # Quick check: only parse if any of our PR numbers appear in the line. + matched = set() + for num in remaining: + if f"#{num}" in line: + prs[num].merge_pos = pos + matched.add(num) + remaining -= matched + + +# --------------------------------------------------------------------------- +# Output +# --------------------------------------------------------------------------- + +GITHUB_PR_URL = "https://github.com/cockroachdb/cockroach/pull/{}" + + +def print_report( + missing: list[PRInfo], + ignored: list[tuple[PRInfo, str]], # (pr, reason) + present: list[PRInfo], + total: int, +) -> None: + print("=" * 64) + print("MMA Backport Candidates: master -> release-26.2") + print("=" * 64) + print() + print(f"Total MMA-related PRs on master since branch split: {total}") + print(f"Already backported: {len(present)}") + print(f"Ignored (skipped): {len(ignored)}") + print(f"NOT YET backported: {len(missing)}") + print() + + if missing: + print("-" * 64) + print("PRs NOT YET backported (in cherry-pick order, oldest first)") + print("-" * 64) + print() + print(" Legend: [P]=path-based [T]=title-based [A]=author-based [B]=both(P+T)") + print() + for i, pr in enumerate(missing, 1): + tag = {"path": "P", "title": "T", "author": "A", "both": "B"}.get(pr.source, "?") + short_sha = pr.merge_sha[:11] + url = GITHUB_PR_URL.format(pr.number) + print(f" {i:>2}. [{tag}] #{pr.number:<6} {short_sha} {pr.title}") + print(f" {url}") + print() + + if ignored: + print("-" * 64) + print("PRs ignored (add/remove in IGNORED_PRS dict)") + print("-" * 64) + for pr, reason in ignored: + short_sha = pr.merge_sha[:11] + print(f" #{pr.number:<6} {short_sha} {pr.title}") + print(f" reason: {reason}") + print() + + if present: + print("-" * 64) + print("PRs already backported (for reference)") + print("-" * 64) + for pr in present: + short_sha = pr.merge_sha[:11] + print(f" #{pr.number:<6} {short_sha} {pr.title}") + print() + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def main() -> None: + parser = argparse.ArgumentParser( + description="Identify MMA-related PRs that need backporting to release-26.2.", + ) + parser.add_argument( + "--fetch", action="store_true", + help="Run 'git fetch' before analysis.", + ) + parser.add_argument( + "--upstream", default="origin", + help="Name of the upstream remote (default: origin).", + ) + args = parser.parse_args() + + master = f"{args.upstream}/master" + release = f"{args.upstream}/release-26.2" + + if args.fetch: + print("Fetching upstream...", file=sys.stderr) + git("fetch", args.upstream, "master", "release-26.2") + + # Find the branch split point. + split = find_split_point(master, release) + split_desc = git("log", "--oneline", "-1", split) + print(f"Branch split point: {split_desc}", file=sys.stderr) + print(file=sys.stderr) + + # Pass 1: path-based discovery. + print("Pass 1: path-based (directories touched)...", file=sys.stderr) + path_prs = find_path_based_prs(master, split, MMA_DIRS) + + # Pass 2: title-based discovery. + print("Pass 2: title-based (commit message keywords)...", file=sys.stderr) + title_prs = find_title_based_prs(master, split) + + # Pass 3: author-based discovery. + print("Pass 3: author-based (MMA team members)...", file=sys.stderr) + author_prs = find_author_based_prs(master, split) + + # Merge results: path takes precedence; mark "both" if found in multiple passes. + all_prs: dict[int, PRInfo] = {} + for num, info in path_prs.items(): + all_prs[num] = info + for num, info in title_prs.items(): + if num in all_prs: + all_prs[num].source = "both" + else: + all_prs[num] = info + for num, info in author_prs.items(): + if num not in all_prs: + all_prs[num] = info + + # Assign topological merge positions for ordering. + print("Building merge order index...", file=sys.stderr) + assign_merge_positions(all_prs, master, split) + + # Determine which PRs are already backported. + backported = get_backported_prs(release, split) + + # Split into missing, ignored, and present, sorted by merge position. + by_pos = sorted(all_prs.values(), key=lambda p: p.merge_pos) + missing = [] + ignored = [] + present = [] + for p in by_pos: + if p.number in backported: + present.append(p) + elif p.number in IGNORED_PRS: + ignored.append((p, IGNORED_PRS[p.number])) + else: + missing.append(p) + + print_report(missing, ignored, present, len(all_prs)) + + +if __name__ == "__main__": + main() diff --git a/scripts/mma-backports.txt b/scripts/mma-backports.txt new file mode 100644 index 000000000000..4c9913f4e266 --- /dev/null +++ b/scripts/mma-backports.txt @@ -0,0 +1,165 @@ +Branch split point: 44c386148e7 release: add update-workflow-branches command and TeamCity scripts (#159216) + +Pass 1: path-based (directories touched)... +Pass 2: title-based (commit message keywords)... +Pass 3: author-based (MMA team members)... +Building merge order index... +================================================================ +MMA Backport Candidates: master -> release-26.2 +================================================================ + +Total MMA-related PRs on master since branch split: 76 +Already backported: 10 +Ignored (skipped): 66 +NOT YET backported: 0 + +---------------------------------------------------------------- +PRs ignored (add/remove in IGNORED_PRS dict) +---------------------------------------------------------------- + #165450 4c123b411ff kvnemesis: add MVCC GC operation to KVNemesis + reason: author-based: kvnemesis: add MVCC GC operation to KVNemesis + #166328 9fc2f395971 replicastats: removed an unnecessary indirection in `replicaStatsRecord` + reason: author-based: replicastats: removed an unnecessary indirection in `replicaStatsRecord` + #166419 7b2d2cff0dc roachtest: enable Go execution traces in kv/restart/nodes=12 + reason: author-based: roachtest: enable Go execution traces in kv/restart/nodes=12 + #166421 630fc2d636f roachtest: enable Go execution traces in kv/splits + reason: author-based: roachtest: enable Go execution traces in kv/splits + #166532 0e4c792e661 investigate: analyze heap profiles on OOM failures + reason: author-based: investigate: analyze heap profiles on OOM failures + #166423 7fdcb3c57fa roachtest: add Datadog Logs link to GitHub issues + reason: author-based: roachtest: add Datadog Logs link to GitHub issues + #166560 3b786c5c307 tsdump2duck: add CLI tool to convert tsdump.gob to DuckDB SQL + reason: author-based: tsdump2duck: add CLI tool to convert tsdump.gob to DuckDB SQL + #166745 57c21590cc0 admission: plumb SQL CPU time token admission through SQLCPUProvider + reason: admission: plumb SQL CPU time token admission through SQLCPUProvider + #167051 3122e1b1742 asim: add sma-count and mma-only configs to skewed disk balance test + reason: already backported via #167468 (release-26.2: asim changes) + #166792 5f484ad0d8d admission: integrate SQL CPU admission with CTT WorkQueue + reason: admission: integrate SQL CPU admission with CTT WorkQueue + #167110 72f5c45a5db asim: document thrashing sources in skewed disk balance test + reason: already backported via #167468 (release-26.2: asim changes) + #167100 cc104061783 engflow-artifacts: rewrite protobuf parsing with proper schema + reason: author-based: engflow-artifacts: rewrite protobuf parsing with proper schema + #167211 043bbc66afc CLAUDE.md: improve PR description guidance + reason: author-based: CLAUDE.md: improve PR description guidance + #166156 1d52a673164 intentresolver: chunk intent resolution to limit in-flight requests + reason: author-based: intentresolver: chunk intent resolution to limit in-flight requests + #166698 c2e82219150 kvserver: introduce stageDestroyReplica and pendingReplicaDestruction + reason: author-based: kvserver: introduce stageDestroyReplica and pendingReplicaDestruction + #166924 0c0f9c2bd16 server: use liveness cache for high-frequency scan callers + reason: author-based: server: use liveness cache for high-frequency scan callers + #167297 6394b41096b intentresolver: expose intent budget constants as env vars + reason: author-based: intentresolver: expose intent budget constants as env vars + #166760 c279baa28e2 kvcoord: fix a potential persistent failure loop in `DistSender` + reason: author-based: kvcoord: fix a potential persistent failure loop in `DistSender` + #167289 461753032b1 kvserver: stop treating split/merge trigger errors as replica corruption + reason: author-based: kvserver: stop treating split/merge trigger errors as replica corruption + #167120 de52b5c9a99 timeutil: fix timer pool vs synctest incompatibility + reason: timeutil: fix timer pool vs synctest incompatibility + #167616 de13bf2c301 kvserver: skip TestStoreRangeSplitAndMergeWithGlobalReads under duress + reason: kvserver: skip TestStoreRangeSplitAndMergeWithGlobalReads under duress + #167655 a68374e2719 storeliveness: skip flaky multi-store tests under duress + reason: storeliveness: skip flaky multi-store tests under duress + #167463 57c74456a2b docs: remove unused metrics.html + reason: docs: remove unused metrics.html + #166529 4a011da7d2e sql: add WITH ZONE option to SHOW RANGES + reason: sql: add WITH ZONE option to SHOW RANGES + #167861 8b897c7f9dc kvserver: improve store.capacity allocations + reason: kvserver: improve store.capacity allocations + #167927 42999aff1c1 sql: rename zone_config_conformant to zone_config_needs_split + reason: sql: rename zone_config_conformant to zone_config_needs_split + #167897 839d4a5a7cc kvserverbase: add COCKROACH_DISABLE_MMA env var override + reason: already backported (b5f05189765, d3ea3080d22, 5bae83c8aac on release-26.2; cherry-picks lack PR ref) + #167295 f19780c8f0a kvserver: remove ReplicaCorruptionError + reason: kvserver: remove ReplicaCorruptionError (follows ignored #167289) + #165577 a3ab149c214 storage: add MultiEngineCompactionScheduler + reason: storage: add MultiEngineCompactionScheduler + #167939 5ad75098f1b allocator: disable follow-the-workload when MMA is enabled + reason: already backported via #168475 (release-26.2: allocator: disable follow-the-workload when MMA is enabled) + #168047 2058731abcf kvserver: isolate subtests in TestReplicaLatchingOptimisticEvaluation… + reason: kvserver: isolate subtests in TestReplicaLatchingOptimisticEvaluation + #167989 a5419f09af4 roachtest: increase HTTP timeout in follower-reads test + reason: roachtest: increase HTTP timeout in follower-reads test + #168121 cd06503a6b5 roachtest: add CLAUDE.md with basic usage instructions + reason: roachtest: add CLAUDE.md with basic usage instructions + #168156 67790cf6e02 kvserver: deflake TestTenantCtx + reason: kvserver: deflake TestTenantCtx + #168243 2c3125fb1bc roachtest: bump post-test assertion timeout to 20m + reason: roachtest: bump post-test assertion timeout to 20m + #168248 d721884544f roachtest: extract artifact collection to artifactsutil + reason: roachtest: extract artifact collection to artifactsutil + #167920 e8766fc6d9e roachtest: add WaitForFullReplication helper + reason: roachtest: add WaitForFullReplication helper + #168231 872d1c4c12c admission: add local token reservation to SQLCPUHandle + reason: admission: add local token reservation to SQLCPUHandle + #168260 87c538de686 ui/cluster-ui: fix empty cluster settings page + reason: ui/cluster-ui: fix empty cluster settings page + #168298 eb8b75426e6 admission: introduce CPU time token AC mode setting + reason: admission: introduce CPU time token AC mode setting + #168452 8aca45b68b9 ci: fix lint_test panic in affected-targets unit test runs + reason: ci: fix lint_test panic in affected-targets unit test runs + #168444 17f510cfba0 ci: opt T-kv into auto-investigate for test failures + reason: ci: opt T-kv into auto-investigate for test failures + #168383 7b5db8f7a08 admission: add maxCPU burst qualification for resource groups + reason: admission: add maxCPU burst qualification for resource groups + #168261 79c8e71012c ui/db-console: add "Changed only" filter to cluster settings page + reason: ui/db-console: add 'Changed only' filter to cluster settings page + #168438 8b6d3d2a14f admission: rename from tenantID to groupID + reason: admission: rename from tenantID to groupID + #168439 6bf1e7400bb admission: add resource group routing and per-group burst refill + reason: admission: add resource group routing and per-group burst refill + #168829 f4184f40c73 admission: add modeStrategy interface with serverlessStrategy + reason: admission: add modeStrategy interface with serverlessStrategy + #167696 51c340e4d27 roachtest/perturbation: simplify scoring to impact ratios + reason: roachtest/perturbation: simplify scoring to impact ratios + #167853 3a8907692f6 kvserver: add rate limiting for low-priority bulk read operations like TTL + reason: kvserver: add rate limiting for low-priority bulk read operations like TTL + #169195 0f45d1da541 admission: add composite groupKey type + reason: admission: add composite groupKey type + #165719 b7ceb33b651 mmaprototype: dynamically verbose logs for overloaded stores + reason: already backported via #169344 (release-26.2: mmaprototype: dynamically verbose logs for overloaded stores) + #169320 5da89382df2 admission: add rmStrategy for RM-mode burst refill + reason: admission: add rmStrategy for RM-mode burst refill + #169428 a7275e2a6f3 admission: preserve non-tenant groupWeights across SetTenantWeights swap + reason: admission: preserve non-tenant groupWeights across SetTenantWeights swap + #169446 9b6a9abf017 kvserver/allocator/mmaprototype: lock advisor methods + reason: already backported via #169590 (release-26.2: kvserver/allocator/mmaprototype: lock advisor methods) + #169584 92e3c33d1e1 sql: lower the priority of AUTO CREATE STATS job to BulkLowPri + reason: sql: lower the priority of AUTO CREATE STATS job to BulkLowPri + #169658 48bef1064d2 build/teamcity: upload phases.json artifacts for perturbation tests + reason: build/teamcity: upload phases.json artifacts for perturbation tests + #169659 8b8348c6f59 roachtest/perturbation: enforce 0.8x throughput floor + reason: roachtest/perturbation: enforce 0.8x throughput floor + #169336 3b12b3a7745 kvserver: plumb ScanStats to obtain pebble iterator statistics during request evaluation + reason: kvserver: plumb ScanStats to obtain pebble iterator statistics during request evaluation + #169230 ab29a85d378 mmaprototype: add structured snapshot of cluster state + reason: already backported via #169711 (release-26.2: mmaprototype: add structured snapshot of cluster state) + #169233 f7c159f74b7 cli: collect mma allocator state in debug zip + reason: already backported via #169734 (release-26.2: cli: collect mma allocator state in debug zip) + #169669 516a17d899f allocatorimpl: retire kv.allocator.load_based_lease_rebalancing.enabled + reason: allocatorimpl: retire kv.allocator.load_based_lease_rebalancing.enabled (master+ only) + #169413 9a4e0558458 mmaprototype: count overloaded-but-blocked stores in MMA metrics + reason: already backported via #169742 (release-26.2: mmaprototype: count overloaded-but-blocked stores in MMA metrics) + #169741 7c25eaa9935 mmaprototype: add captured_at to cluster state snapshot + reason: already backported via #169761 (release-26.2: mmaprototype: add captured_at to cluster state snapshot) + #169747 cbc8baea715 mmaprototype: materialize per-range constraint analysis in snapshot + reason: already backported via #169876 (release-26.2: mmaprototype: materialize per-range constraint analysis in snapshot) + #169430 a7b7f3efaa1 kvserver: enable multi-metric allocator by default in v26.3 + reason: kvserver: enable multi-metric allocator by default in v26.3 (master+ only) + #169708 3dc59b8f9bb admission: retire tenant-weight cluster settings + reason: admission: retire tenant-weight cluster settings + +---------------------------------------------------------------- +PRs already backported (for reference) +---------------------------------------------------------------- + #166076 d71ec96bd45 storeliveness: fix flaky TestTransportClusterSettingToggle + #166148 d8f2867dd26 allocator/mmaprototype: add test for non-voter WB rebalancing + #166422 3fecb16a94a roachtest: bump user_login.timeout in follower-reads insufficient-quorum + #162182 7ae2fd949c0 mmaprototype: balance CPU on utilization + #166306 4324c93284f server: add cluster setting to disable GC assist + #166418 a9516dda64e roachtest: fix acceptance/cli/node-status quorum loss + #166531 9e4eb90c76d sql: fix TestReadCommittedReadTimestampNotSteppedOnCommit for write buffering + #166669 3aa8ac691bc singleflight,stop: fix trace.StartRegion crash with oversized strings + #166658 70c832b9804 perturbation: rate-limit backfill pre-fill to avoid OOM on target node + #167307 bcc11cc1ac4 kvserver: fix TestReplicaRemovalDuringCPut hang on failure +