topfans/k8s/jobs/pg-sequence-sync/script.sh
2026-07-06 13:26:46 +08:00

118 lines
4.2 KiB
Bash
Executable File

#!/bin/bash
# ===================================================================
# PostgreSQL 序列同步脚本 (Phase 1 切流量前必跑 — CLAUDE.md 强制规范)
# ===================================================================
# 用途:
# 对所有 BIGSERIAL 主键的表,把 postgres 序列的 last_value 推进到 MAX(id),
# 防止应用代码 INSERT 时报 duplicate key value violates unique constraint
#
# 调用:
# kubectl create job ...
# 或 helm hook (pre-install / pre-upgrade 自动跑, 失败则 helm install 中断)
#
# 退出码:
# 0 = 全部 healthy
# 1 = 有 unhealthy 序列, 切流量会失败, 必须排查
# ===================================================================
set -euo pipefail
PG_HOST="${PG_HOST:-postgres}"
PG_PORT="${PG_PORT:-5432}"
PG_USER="${PG_USER:-postgres}"
PG_DB="${PG_DB:-topfans}"
PG_PASSWORD="${PG_PASSWORD:?PG_PASSWORD 必须通过 envFrom secret 注入}"
# 由 ConfigMap / values 注入
TABLES_CSV="${TABLES_CSV:-assets,asset_registry,users,stars,activity_assets,collection_assets,materials,exhibitions,galleries}"
# 转数组
IFS=',' read -ra TABLES <<< "$TABLES_CSV"
echo "============================================="
echo "Phase 1: PostgreSQL 序列同步"
echo "============================================="
echo "Target: ${PG_USER}@${PG_HOST}:${PG_PORT}/${PG_DB}"
echo "Tables: ${#TABLES[@]} 个 BIGSERIAL 表"
echo ""
# ---------- Step 1: 同步所有表 ----------
echo "📍 Step 1/2: 同步所有表的 _id_seq"
echo ""
for tbl in "${TABLES[@]}"; do
tbl=$(echo "$tbl" | tr -d ' ')
[ -z "$tbl" ] && continue
echo "${tbl}"
# 先看 max(id), 确认表非空
max_id=$(PGPASSWORD="$PG_PASSWORD" psql \
-h "$PG_HOST" -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" \
-tAc "SELECT COALESCE(MAX(id), 0) FROM ${tbl};")
if [ "$max_id" = "0" ]; then
echo " (空表, 不需要同步序列)"
continue
fi
# setval(_id_seq, max(id), true) — true 表示下一次 nextval 直接返回 max(id)+1
PGPASSWORD="$PG_PASSWORD" psql \
-h "$PG_HOST" -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" \
-c "SELECT setval('${tbl}_id_seq', ${max_id}, true);"
echo " ✅ 已同步到 ${max_id}"
done
echo ""
# ---------- Step 2: 验证 (硬性 PASS 条件) ----------
echo "📍 Step 2/2: 验证所有序列 healthy"
echo ""
unhealthy_file=$(mktemp)
for tbl in "${TABLES[@]}"; do
tbl=$(echo "$tbl" | tr -d ' ')
[ -z "$tbl" ] && continue
seq="${tbl}_id_seq"
# pg_sequences 自 PG10 起可用 — last_value >= max_id 视为 healthy
result=$(PGPASSWORD="$PG_PASSWORD" psql \
-h "$PG_HOST" -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" \
-tAc "
SELECT
seq.schemaname,
seq.sequencename,
seq.last_value,
(SELECT COALESCE(MAX(id), 0) FROM ONLY ${tbl}) AS table_max_id,
seq.last_value >= (SELECT COALESCE(MAX(id), 0) FROM ONLY ${tbl}) AS is_healthy
FROM pg_sequences seq
WHERE seq.sequencename = '${seq}';
")
is_healthy=$(echo "$result" | awk -F'|' '{gsub(/^ +| +$/,"",$5); print $5}')
last_value=$(echo "$result" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
table_max=$(echo "$result" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
if [ "$is_healthy" = "t" ]; then
echo "${seq}: last_value=${last_value} >= max(id)=${table_max}"
else
echo "${seq}: last_value=${last_value} < max(id)=${table_max} ← 不健康!"
echo "${seq}|${last_value}|${table_max}" >> "$unhealthy_file"
fi
done
echo ""
# ---------- 收尾 ----------
unhealthy_count=$(wc -l < "$unhealthy_file" 2>/dev/null || echo 0)
rm -f "$unhealthy_file"
if [ "$unhealthy_count" -gt 0 ]; then
echo "============================================="
echo "❌ 同步失败: ${unhealthy_count} 个序列不健康"
echo "切流量会触发 duplicate key 错误, 启动 abort!"
echo "============================================="
exit 1
fi
echo "============================================="
echo "✅ 全部 ${#TABLES[@]} 个序列 healthy, 可以切流量"
echo "============================================="
exit 0