#!/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