一个代码托管平台的可用性跌到 86%,意味着每个月有超过 4 天无法正常工作。更致命的是,2025 年 4 月 23 日的一次 squash merge bug 直接损坏了 2,092 个 PR 的提交记录——这不是"服务慢了一点",而是数据本身出了问题。资深工程师选择离职,信号已经很明确:当基础设施的信任基础动摇时,留在上面的人每天都在赌。
可用性 86% 到底有多糟糕
业界常说的"五个九"(99.999%)意味着全年停机不超过 5 分钟。99.9% 也还算能接受,全年约 8.7 小时。而 86% 意味着全年有超过 51 天处于不可用状态。对任何以 CI/CD 为命脉的团队来说,这不是 inconvenience,是 production outage。
GitHub 官方状态页过去几个月的红色条带密度明显增加,从 API 限流到 git push 失败再到 webhook 延迟,几乎每周都有新事件。The Pragmatic Engineer 的报道把根因指向了 AI 驱动的流量暴增——Copilot、AI agent、自动化工具链的调用量在短时间内翻了 3.5 倍,GitHub 的内部服务根本没做好这个量级的容量规划。
squash merge bug:数据完整性事故的细节
4 月 23 日的事故是最值得技术团队深挖的部分。squash merge 是 GitHub PR 合并的常见策略:把一个分支上的多个 commit 压成一个 commit 合入主分支,保持历史线性。但这次 bug 导致 squash 后生成的 commit 与原始 PR 的元数据关联断裂——2,092 个 PR 的提交记录被损坏,部分 commit SHA 失效,部分 PR 状态异常。
受影响方包括 Modal 等知名公司。这类事故的可怕之处在于:它不是"你看不到页面了",而是"你以为代码还在,但引用关系已经断了"。对于依赖 commit SHA 做 reproducible build、依赖 PR number 做 audit trail 的团队,这是根本性的信任破坏。
AI 流量 3.5 倍:基础设施没跟上需求曲线
Copilot 的代码补全、AI agent 的批量操作、CI 系统中嵌入的 LLM 调用——这些场景都在通过 GitHub API 产生请求。3.5 倍的增幅不是渐进式的增长,是阶跃函数。GitHub 的服务架构(大量依赖内部 monolith + MySQL + Redis 缓存层)在面对这种请求模式变化时,暴露了几个结构性问题:
- 限流策略滞后:对 AI agent 的识别和分级限流没有及时上线,导致正常开发者流量被挤占。
- 缓存命中率下降:AI 请求的模式和人类开发者不同(更频繁的批量读取、更少的重复路径),原有缓存策略失效。
- 数据库连接池耗尽:squash merge 等写操作在排队,读请求又在暴增,连接池成了瓶颈。
这不是 GitHub 独有的问题。任何提供 API 的平台在 AI 时代都会面临类似的流量结构变化。
实操:排查你的仓库是否受影响 + 建立可用性监控
下面给出两个可以直接运行的脚本。第一个检查你的仓库在 4 月 23 日前后是否有异常的 squash merge 记录;第二个用 GitHub API 建立一个轻量的可用性监控。
检查受影响 PR 的脚本
#!/usr/bin/env bash
# check_squash_integrity.sh
# 检查指定仓库在 2025-04-20 ~ 2025-04-25 期间合并的 PR 是否存在异常
# 使用前需要设置 GH_TOKEN 和 REPO
set -euo pipefail
REPO="${REPO:-owner/repo}" # 改成你的仓库,格式 owner/repo
START_DATE="2025-04-20"
END_DATE="2025-04-25"
GH_TOKEN="${GH_TOKEN:-}" # 设置你的 GitHub token
if [ -z "$GH_TOKEN" ]; then
echo "请设置 GH_TOKEN 环境变量"
exit 1
fi
API="https://api.github.com/repos/${REPO}/pulls"
# 拉取指定时间范围内已合并的 PR
echo "=== 检查 ${REPO} 在 ${START_DATE} ~ ${END_DATE} 期间合并的 PR ==="
PR_LIST=$(curl -s -H "Authorization: token ${GH_TOKEN}" \
"${API}?state=closed&per_page=100&sort=updated&direction=desc" \
| jq -r --arg start "$START_DATE" --arg end "$END_DATE" \
'.[] | select(.merged_at != null
and .merged_at >= $start
and .merged_at <= $end)
| .number')
SUSPECT_COUNT=0
for pr_num in $PR_LIST; do
# 获取 PR 详情,检查 merge_commit_sha 是否有效
pr_detail=$(curl -s -H "Authorization: token ${GH_TOKEN}" \
"${API}/${pr_num}")
merge_sha=$(echo "$pr_detail" | jq -r '.merge_commit_sha')
merged_via=$(echo "$pr_detail" | jq -r '.merged_via // "unknown"')
# 尝试在本地验证该 SHA 是否存在于主分支
# 如果 merge_commit_sha 为 null 或在远程找不到,标记为可疑
if [ "$merge_sha" = "null" ] || [ -z "$merge_sha" ]; then
echo "[可疑] PR #${pr_num}: merge_commit_sha 为空,合并方式=${merged_via}"
SUSPECT_COUNT=$((SUSPECT_COUNT + 1))
else
# 在远程检查该 commit 是否存在
commit_check=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${REPO}/commits/${merge_sha}")
if [ "$commit_check" != "200" ]; then
echo "[可疑] PR #${pr_num}: SHA ${merge_sha} 在远程不存在 (HTTP ${commit_check}),合并方式=${merged_via}"
SUSPECT_COUNT=$((SUSPECT_COUNT + 1))
fi
fi
done
echo ""
echo "=== 总结:发现 ${SUSPECT_COUNT} 个可疑 PR ==="
if [ "$SUSPECT_COUNT" -gt 0 ]; then
echo "建议:对这些 PR 重新检查代码完整性,必要时重新 squash merge"
fi
运行方式:
export GH_TOKEN="ghp_你的token"
export REPO="your-org/your-repo"
bash check_squash_integrity.sh
需要安装 jq 和 curl。如果 PR 数量超过 100,需要加分页逻辑或改用 gh CLI。
轻量可用性监控
"""
github_availability_monitor.py
每 5 分钟探测 GitHub API,记录可用性数据到本地 CSV。
适合小团队快速建立基线,不依赖外部监控服务。
"""
import csv, time, requests, datetime, os
# ---- 配置 ----
GITHUB_TOKEN = os.getenv("GH_TOKEN", "") # 可选,有 token 限流更宽松
INTERVAL_SECONDS = 300 # 5 分钟
CSV_FILE = "github_availability.csv"
ENDPOINTS = [
("api_root", "https://api.github.com"),
("user_api", "https://api.github.com/user"),
("git_push", "https://github.com"), # 基础可达性
]
def probe(name: str, url: str) -> dict:
headers = {"Authorization": f"token {GITHUB_TOKEN}"} if GITHUB_TOKEN else {}
try:
r = requests.get(url, headers=headers, timeout=10)
return {
"timestamp": datetime.datetime.utcnow().isoformat(),
"endpoint": name,
"status_code": r.status_code,
"latency_ms": round(r.elapsed.total_seconds() * 1000),
"ok": r.status_code < 500,
}
except requests.RequestException as e:
return {
"timestamp": datetime.datetime.utcnow().isoformat(),
"endpoint": name,
"status_code": 0,
"latency_ms": -1,
"ok": False,
"error": str(e),
}
def main():
with open(CSV_FILE, "a", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["timestamp", "endpoint", "status_code", "latency_ms", "ok", "error"])
if f.tell() == 0:
writer.writeheader()
while True:
for name, url in ENDPOINTS:
row = probe(name, url)
writer.writerow(row)
f.flush()
status = "✓" if row["ok"] else "✗"
print(f"{row['timestamp']} {name}: {status} {row['latency_ms']}ms")
print(f"--- 等待 {INTERVAL_SECONDS}s ---")
time.sleep(INTERVAL_SECONDS)
if __name__ == "__main__":
main()
运行后会在本地生成 github_availability.csv,你可以用任何工具做可视化。跑一周就能拿到自己的可用性基线数据,不再依赖 GitHub 自己的状态页。
这件事对依赖 GitHub 的团队意味着什么
短期:如果你在 4 月 20–25 日间有 squash merge 的 PR,跑一遍上面的检查脚本。发现可疑记录时,最安全的做法是重新创建分支、重新 squash merge,确保主分支上的 commit SHA 是有效的。对于依赖 commit SHA 做 hash 校验的构建系统(如 Go module、Docker layer cache),损坏的 SHA 会导致构建失败或缓存污染。
中期:建立自己的可用性监控。GitHub 状态页的更新往往滞后,而且不区分"API 可用但限流严重"和"完全不可用"。自己的探针数据才是真实 SLA。
架构层面:这次事故再次提醒——把所有关键路径绑在一个平台上是单点风险。考虑:
- git 镜像:用
git remote add mirror把仓库同步到 GitLab 或自建 Git 服务器,CI 可以 fallback 到镜像源。 - CI 多后端:GitHub Actions 出问题时,关键 pipeline 能切换到其他 runner。
- 数据完整性校验:在 CI 中加入
git fsck步步,定期检查仓库对象完整性。
GitHub 仍然是最大的代码托管平台,但"大"不等于"稳"。AI 流量的阶跃增长还会继续,这次事故不是最后一次。做好准备,比抱怨更有价值。