#!/usr/bin/env bash # S4 — 순서오류 탐지·교정(기능성 축): 저널(rtgs.journal)에 전역순번을 역전시켜 주입해도 # 원장엔진(Hermes)이 gap을 버퍼링→순서대로 재정렬 적용하는지 검증. # 복원력방안 부록A S4 재현. 실행: bash sim/s4_order.sh # ⚠ 순번기(Sequencer)를 잠시 멈추고 저널에 직접 주입한다(2-1 순번 영속화 덕에 재기동 시 안전 복원). # 수동 기능테스트와 겹치지 않을 때 실행 권장. cd "$(dirname "$0")"; source ./lib.sh export KAFKA_HEAP_OPTS="-Xmx256M -Xms256M" # kafka .bat의 wmic 분기 회피(포터블) FAILED=0 hr; echo "S4 순서오류 탐지·교정 검증 — 저널에 seq 역전 주입"; hr # 0) 드레인(잔여 없게) + 순번기 정지(경쟁 발번 차단) wait_drain 30 >/dev/null SP=$(pid_of sequencer) echo " Sequencer(PID $SP) 정지 — 주입 동안 경쟁 발번 차단" [ -n "$SP" ] && taskkill //F //PID "$SP" //T >/dev/null 2>&1 sleep 3 TOTAL_BEFORE=$(q "SELECT sum(balance) FROM account") M=$(q "SELECT COALESCE(MAX(global_seq),0) FROM journal_log") echo " 현재 최대 순번 M=$M → 주입 순번 $((M+1)),$((M+2)),$((M+3)) (전달 순서는 역전)" TS="$(date +%s)000" B1="20260710S4$(printf '%010d' $((M+1)))" # seq M+1 1002->1003 B2="20260710S4$(printf '%010d' $((M+2)))" # seq M+2 1004->1005 B3="20260710S4$(printf '%010d' $((M+3)))" # seq M+3 1006->1007 entry() { # seq bmi from to amt echo "{\"globalSeq\":$1,\"seqEpochMillis\":$TS,\"originCenter\":\"DC1\",\"core\":{\"bmi\":\"$2\",\"msgType\":\"pacs.008.001.08\",\"senderCode\":\"$3\",\"receiverCode\":\"$4\",\"debtorAcct\":\"ACC-$3\",\"creditorAcct\":\"ACC-$4\",\"amount\":$5,\"currency\":\"KRW\",\"origHash\":\"simS4\"}}" } # 전달 순서: M+2, M+3, M+1 (역전/gap 주입) { entry $((M+2)) "$B2" 1004 1005 22 entry $((M+3)) "$B3" 1006 1007 33 entry $((M+1)) "$B1" 1002 1003 11 } > /tmp/s4_inject.txt echo " 주입 순서: seq $((M+2)) → $((M+3)) → $((M+1))" "$KAFKA_PROD" --topic rtgs.journal --bootstrap-server localhost:9092 < /tmp/s4_inject.txt 2>/dev/null echo " Hermes 재정렬·적용 대기..." wait_drain 40 >/dev/null; sleep 2 s1=$(q "SELECT status FROM transfer WHERE bmi='$B1'") s2=$(q "SELECT status FROM transfer WHERE bmi='$B2'") s3=$(q "SELECT status FROM transfer WHERE bmi='$B3'") # journal_log에 순번이 순서대로 기록되었는지 ORDER=$(q "SELECT string_agg(global_seq::text, ',' ORDER BY global_seq) FROM journal_log WHERE global_seq IN ($((M+1)),$((M+2)),$((M+3)))") TOTAL_AFTER=$(q "SELECT sum(balance) FROM account") hr; echo "결과"; hr [ "$s1" = "ACCC" ] && [ "$s2" = "ACCC" ] && [ "$s3" = "ACCC" ] \ && pass "역전 주입 3건 전건 정상 완결(ACCC) — 재정렬 성공" \ || fail "완결 실패: seq$((M+1))=$s1 seq$((M+2))=$s2 seq$((M+3))=$s3" [ "$ORDER" = "$((M+1)),$((M+2)),$((M+3))" ] \ && pass "저널 순번 순서대로 기록: $ORDER" || fail "순번 기록 이상: $ORDER" [ "$TOTAL_AFTER" = "$TOTAL_BEFORE" ] && pass "총액 보존: $TOTAL_AFTER" || fail "총액 불일치: $TOTAL_BEFORE -> $TOTAL_AFTER" # 순번기 재기동(2-1 영속화: MAX(global_seq) 복원해 안전하게 이어서 발번) echo " Sequencer 재기동(순번 $((M+3)) 이후부터 복원 발번)" start_svc "RTGS Sequencer :8090 (S4 복귀)" sequencer hr [ "$FAILED" = "0" ] && echo "S4 PASS ✅ (gap 버퍼링→순서 재정렬)" || echo "S4 FAIL ❌" exit $FAILED