118 lines
4.2 KiB
Bash
Executable File
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
|