Wave E/F: 업무흐름 tpcall 오케스트레이션 + 크로스브랜치 교착 제거

477개 서비스를 tpcall 체인으로 연결하고(2 → 477), 그 과정에서 드러난
런타임 결함들을 실행 검증으로 잡아냈다.

체인 오케스트레이션 (Wave E)
- 매입/승인/정산/지급/대사/원장/마감/마스터/전문 흐름을 모듈 간 tpcall 로 연결
- 호출그래프 553노드 494엣지, 순환 0, SELFCALL 0
- 핵심 경로: ACQUIRE→RECONCILE→SETTLE→ST_MDR→LG_SETTLEPOST (6단, XA 2PC)

런타임 결함 수정 (Wave F) — 정적 게이트로는 잡히지 않던 것들
1. 모듈내 체인 자기교착: 모듈 서버 카피가 1이면 SETTLE→ST_MDR 처럼 같은
   서버로 되돌아오는 호출이 자기 요청을 못 꺼내 TPETIME. 카피 4로 상향.
2. 크로스브랜치 핫로우 교착: 한 글로벌 트랜잭션의 형제 XA 브랜치가 같은 행을
   UPDATE 하면 같은 트랜잭션이라 락이 안 풀려 타임아웃까지 블록된다.
   - mg_channel: 비소유자 96개 서비스의 UPDATE 제거, STAN 은 시퀀스 채번으로
     (실제 전문 스위치 방식). 생애주기 소유자 7개만 UPDATE 유지.
   - lg_merch_bal: ACQ_SETTLELINK 의 LG_SETTLEPOST 다이아몬드 호출 제거.
     교착일 뿐 아니라 같은 정산건이 원장에 이중 전기되는 회계 오류였다.
   - cl_close_log / py_payout / lg_account_bal: CHAIN 마커로 쓰기 소유자 일원화.
     py_payout 건은 피호출자가 호출자의 미커밋 승인을 볼 수 없어 기능적으로도
     동작 불능이었다.
3. cm_log PK 충돌: st 131개 파일이 max(log_id)+1 로 채번해 nextval 을 쓰는
   피호출자와 동일 PK 를 INSERT, 미커밋 인덱스 엔트리에 걸려 블록. 전부
   nextval('cm_log_seq') 로 전환.
4. strncpy 스택 파괴: cmdb_get_param 이 strncpy(out, h_val, 127) 로 항상 128
   바이트를 써서, bizdate[16] 을 넘기는 호출부 133곳이 매번 호출자 스택 112
   바이트를 훼손했다. 인접 ecpg 호스트 변수가 오염돼 서버 카피가 세그폴트.
   DB 락 대기가 0인데 60초 블록되는 증상의 원인. cm_code/py_payout_reject 동일.
5. ACQ_CANCEL→LIMIT_RST 입력계약 불일치: 카드번호를 안 실어 보내 항상 거절.
   승인원장에서 카드를 끌어오고, 무승인 매입은 한도원복을 생략하도록 수정.
6. mgdb_channel_bump_stan: 활동 채널 전체를 한 번에 잠그는 다중행 UPDATE
   (호출자 0인 잠복 지뢰). 시퀀스 채번 + 집계 조회로 교체.

화면
- 기본값을 실존 시드 키로 부팅 시 조회해 덮어쓴다. 기존 플레이스홀더
  (card_limit 에 없는 카드, 8자리 영업일)로는 처리성 화면이 전부 거절당했다.
- 게이트웨이가 단일 스레드 직렬 루프라 기본값 조회도 순차로 수행.

검증 (실측)
- 통합 빌드: modules=11 servers=11 batches=506, error 0
- 도메인: 45 프로세스, 1,466 서비스 AVAIL
- 체인 전수 스모크 477/477 응답, TPETIME/TPEABORT 0건, 최대 530ms
- AUTH: 수정 전 60초 블록→TPEABORT, 수정 후 0.215초 ok:true
- 6단 매입 체인 COMMIT OK, 미결 2PC 0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hyeongwoo 2026-07-21 14:55:49 +00:00
parent 4cede852a0
commit 074d244bba
654 changed files with 36008 additions and 2805 deletions

View file

@ -7,14 +7,47 @@
#include <userlog.h>
#include "mg_bump_stan_dbio.h"
/*
* 해당 영업일에 전문이 오간 활동 채널 수를 세고, STAN 은 시퀀스에서 채번한다.
*
* 과거 구현은 mg_channel 을 다중행 UPDATE(last_stan = last_stan + 1) 했었다.
* 그 방식은 크로스브랜치 핫로우 교착의 원인이다: 한 글로벌 트랜잭션 안에서
* 형제 XA 브랜치 둘이 같은 채널 행을 잠그면 같은 트랜잭션이라 락이 풀리지 않아
* 트랜잭션 타임아웃까지 그대로 멈춘다(AUTH→LIMIT_DEC/FRAUD_DETECT→MG_LOG 사례).
* 다중행 UPDATE 라 활동 채널 전체를 한 번에 잠가 파급도 가장 넓었다.
*
* 실제 전문 스위치도 STAN 을 채널 상태행이 아니라 카운터에서 뽑는다.
* 시퀀스 채번은 행락을 잡지 않아 형제 브랜치끼리 충돌하지 않는다.
* mg_channel 쓰기는 채널 생애주기 소유자(MG_CHANOPEN/CLOSE/SWITCH/FAILOVER/
* RESET/SIGNON/SIGNOFF)에게만 남긴다.
*
* 반환: 해당 영업일 활동 채널 수 (실패 시 -1).
*/
long mgdb_channel_bump_stan(const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_chan_cnt = 0;
long h_stan = 0;
EXEC SQL END DECLARE SECTION;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL UPDATE mg_channel SET last_stan = last_stan + 1
WHERE channel IN (SELECT channel FROM mg_msg_log WHERE biz_date = :h_bizdate);
if (sqlca.sqlcode < 0) { userlog("mgdb_channel_bump_stan FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; }
return sqlca.sqlerrd[2];
EXEC SQL SELECT count(DISTINCT channel) INTO :h_chan_cnt
FROM mg_msg_log WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) {
userlog("mgdb_channel_bump_stan 활동채널 집계 FAIL [%d] %s",
sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
EXEC SQL SELECT nextval('mg_stan_seq') INTO :h_stan;
if (sqlca.sqlcode < 0) {
userlog("mgdb_channel_bump_stan STAN 채번 FAIL [%d] %s",
sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
userlog("mgdb_channel_bump_stan 영업일=%s 활동채널=%ld STAN=%ld",
h_bizdate, h_chan_cnt, h_stan);
return h_chan_cnt;
}