#!/usr/bin/env bash # S2 — 무손실 승계(복원성 축): 부하 중 원장엔진(Hermes)을 강제 종료했다가 재기동해도 # 선저널(write-ahead)+Kafka 오프셋 재생으로 유실 0·전건 완결되는지 검증. # 복원력방안 부록A S2 재현. 실행: bash sim/s2_failover.sh [건수] # ⚠ Hermes를 죽였다 살리므로 수동 기능테스트와 겹치지 않을 때 실행 권장. cd "$(dirname "$0")"; source ./lib.sh N=${1:-40} FAILED=0 BANKS=(1001 1002 1003 1004 1005 1006 1007 1010) declare -a BMIS hr; echo "S2 무손실 승계 검증 — 이체 ${N}건 투입 중 Hermes 강제 종료·재기동"; hr TOTAL_BEFORE=$(q "SELECT sum(balance) FROM account") echo " 이체 전 총액 = $TOTAL_BEFORE" KILL_AT=$((N/3)); RESTARTED=0 for ((i=1;i<=N;i++)); do fi=${BANKS[$((RANDOM % ${#BANKS[@]}))]} ti=${BANKS[$((RANDOM % ${#BANKS[@]}))]} while [ "$ti" = "$fi" ]; do ti=${BANKS[$((RANDOM % ${#BANKS[@]}))]}; done bmi=$(mkbmi "$fi" "$i"); BMIS+=("$bmi") submit "$fi" "$ti" "$(( (RANDOM % 300) + 1 ))" "$bmi" if [ "$i" = "$KILL_AT" ] && [ "$RESTARTED" = "0" ]; then HP=$(pid_of hermes) echo " >> $i건째: Hermes(PID $HP) 강제 종료" [ -n "$HP" ] && taskkill //F //PID "$HP" //T >/dev/null 2>&1 sleep 3 echo " >> Hermes 재기동" start_svc "RTGS Hermes (S2 재기동)" hermes RESTARTED=1 fi sleep 0.2 done echo " 투입 완료. Hermes 복귀·정산 드레인 대기(최대 90s)..." wait_drain 90 && echo " 드레인 완료" || echo " (경고) 드레인 타임아웃 — 아래 잔류 확인" # 투입한 BMI 전건이 종료상태(ACCC/RJCT)에 도달했는지 — 단일 쿼리(IN 리스트)로 확인 INLIST=$(printf "'%s'," "${BMIS[@]}"); INLIST=${INLIST%,} TERM=$(q "SELECT count(*) FROM transfer WHERE bmi IN ($INLIST) AND status IN ('ACCC','RJCT')") STUCK_LIST=$(PSQL -t -A -c "SELECT bmi||'('||status||')' FROM transfer WHERE bmi IN ($INLIST) AND status NOT IN ('ACCC','RJCT')" | tr '\n' ' ') TOTAL_AFTER=$(q "SELECT sum(balance) FROM account") NEG=$(q "SELECT count(*) FROM account WHERE balance < 0") hr; echo "결과"; hr [ "$TERM" = "$N" ] && pass "투입 $N건 전건 완결(ACCC/RJCT) — 유실 0" || fail "$((N-TERM))건 미완결:$STUCK_LIST" [ "$TOTAL_AFTER" = "$TOTAL_BEFORE" ] && pass "총액 보존: $TOTAL_AFTER" || fail "총액 불일치: $TOTAL_BEFORE -> $TOTAL_AFTER" [ "$NEG" = "0" ] && pass "잔액 음수 계좌 0" || fail "음수 계좌 $NEG 건" hr [ "$FAILED" = "0" ] && echo "S2 PASS ✅ (선저널+오프셋 재생으로 무손실 승계)" || echo "S2 FAIL ❌" exit $FAILED