# acquire-core-x — 마이그레이션 난이도 브리핑 (IR)

> **한 줄 요약.** 이 저장소는 "Klaro Forge가 어느 정도로 복잡하게 얽힌 레거시까지
> 다룰 수 있는가"를 증명하기 위한 **실동작 예제(Before)** 다. 실제 카드 매입시스템
> (Tuxedo / Pro\*C / XA / Oracle 계열)의 아키텍처를 그대로 재현하되, 오픈소스 스택
> (Enduro/X · ECPG · PostgreSQL)으로 **실제로 빌드·부팅·거래가 되게** 만들었다.
> 아래 수치는 전부 소스코드에서 직접 집계한 값이며 과장이 없다.
> 포털에서 실시간으로 같은 수치를 볼 수 있다: `#mig`(마이그레이션 난이도), `#arch`(호출그래프).

---

## 왜 이게 "어려운 마이그레이션"인가 — 3축

### 축 A. 양적 압도 (규모)

| 지표 | 실측 |
|---|---|
| 소스 파일 (.pgc/.c/.h) | **4,626 개** |
| 총 코드 라인 | **330,642 LOC** |
| 온라인 서비스 | **1,463 본** |
| 배치 잡 | **506 본** |
| 임베디드 SQL (EXEC SQL) 블록 | **23,742** |
| Tuxedo tpcall 호출부 | **989** |
| 전역 트랜잭션 tpbegin | **1,234** |
| UBF 버퍼 필드접근 | **46,904** |
| DB 테이블 | **75** |
| 카피북 헤더(.h) | **2,207** |

BAIS(비씨카드 글로벌 매입시스템) RFI 기준 온라인 1,466 · 배치 503과 **1:1 규모**로 맞췄다.

### 축 B. 질적 난이도 (자동 변환이 실패하는 지점)

단순 문법 치환기·규칙 기반 변환기가 통째로 깨지는 지점들이다. 이게 곧 Forge의 존재 이유다.

| 레거시 요소 | 실측 | Java·Spring 이전이 어려운 이유 |
|---|---|---|
| Tuxedo ATMI (tpcall/tpservice/tpreturn) | 989 호출 | Java·Spring에 1:1 대응 API가 없다. 서비스 호출 모델 자체를 REST/메시지로 **재설계**해야 한다. |
| XA 2PC 전역 트랜잭션 (최대 **6단 중첩**) | 1,234 tpbegin | 형제 XA 브랜치 격리·2단계 커밋 의미론이 Spring `@Transactional` 경계와 다르다. 잘못 옮기면 **교착·이중전기**가 그대로 재현된다. |
| ECPG 임베디드 SQL (호스트변수·커서·sqlca) | 23,742 블록 | 블록마다 호스트변수 바인딩·`sqlcode` 분기·커서 수명을 JPA/MyBatis 의미로 옮겨야 한다. 자동 변환기가 가장 많이 깨지는 지점. |
| UBF(FML32) 타입리스 버퍼 | 46,904 접근 | 필드ID 기반 동적 버퍼라 타입·필수여부가 컴파일타임에 없다. Java DTO로 옮기려면 4.6만 접근점의 계약을 **사람이 복원**해야 한다. |
| 카피북 공유(#include) 팬아웃 | 6,892 참조 | 한 카피북을 수십 프로그램이 공유해 구조체 변경이 전방위로 파급된다. 모듈 단위 절단이 어렵다. |
| 전처리 매크로(#define) | 2,247 정의 | 매크로가 코드 생성·분기를 숨겨 정적 분석을 방해한다. 전개 후에야 실제 로직이 드러난다. |

**거래 하나의 파급** — "매입 1건이 몇 개 프로그램·테이블·트랜잭션 경계를 건드리나":

| 대표 거래 | 연쇄 프로그램 | XA 브랜치(2PC) | 기록 테이블 | 체인 |
|---|---|---|---|---|
| `ACQUIRE` (매입 접수) | 6 | 6 | 3 | ACQUIRE→RECONCILE→SETTLE→ST_MDR→LG_SETTLEPOST→LG_BALCHK |
| `PAY_APPROVE` (지급 승인) | 5 | 5 | 3 | PAY_APPROVE→PAY_FILEGEN→PAY_ACCTCHK→LG_POST→LG_BALCHK |
| `CL_DAILY` (일마감) | 6 | 6 | 5 | CL_DAILY→VL_SALES→RC_EXCEPTION→CM_LOG / →LG_CLOSE_LINK→CL_LOCK |

하나의 `tpbegin` 아래 여러 XA 브랜치가 서로 다른 테이블을 잠근 채 2단계 커밋으로 원자성을
보장한다. 이 경계를 잘못 옮기면 형제 브랜치 교착이 재현된다 — **실제로 이 저장소를 만들며
그 교착을 밟고 고쳤다**(아래 "발견된 실결함" 참조).

### 축 C. 리얼리티 (실전 레거시임)

"깔끔하게 만든 예제"가 아니라 실전 레거시 특유의 냄새가 실측된다.

| 레거시 특성 | 실측 |
|---|---|
| 가장 많이 공유되는 카피북 | `acq_common.h` — **139개** 프로그램이 #include (구조체 1줄 바꾸면 139곳 파급) |
| 전처리 매크로(#define) | 2,247 개 |
| `sqlca.sqlcode` 수동 에러분기 | **18,719 곳** (예외 대신 리턴코드 분기 — 전부 개별 이관 필요) |
| 임베디드 SQL 밀도 | 파일당 평균 **9.8 블록** |
| 운영 `userlog` 호출 | 17,122 곳 |
| `goto` 잔존 | 64 곳 |

---

## 실동작 증거 (예제가 "그림"이 아님)

- **빌드**: `docker compose up` → Enduro/X 소스빌드 + ECPG 프리컴파일 + 11 모듈서버 + 506 배치, 오류 0.
- **부팅**: 45 프로세스, 1,466 서비스 AVAIL.
- **실거래**: 6단 XA 체인 `ACQUIRE→…→LG_BALCHK` 실행 → `COMMIT OK`, 미결 2PC 0.
- **전수 스모크**: 체인 보유 477 서비스 100% 응답, 교착/타임아웃 0, 최대 530ms.
- **업무 포털**: 운영 대시보드·매입·정산·가맹점·원장·아키텍처·난이도 화면.
- **운영 데이터**: 매입 **135만건**(2021~2026, 5.5년치) 포함 **~1,130만 행**(DB ~1.8GB).
  성장추세·계절성·가맹점 생애주기 반영, 파생(원장 2.6M·정산 1.3M·수수료분해 1.3M) 전부 항등 검증.

## 발견된 실결함 (레거시가 실제로 위험하다는 증거)

예제를 만들며 실행 중에 밟은, **정적 분석으로는 잡히지 않는** 결함들 — 이게 곧 마이그레이션 리스크의 실물이다.

1. **크로스브랜치 핫로우 교착** — 한 글로벌 트랜잭션의 형제 XA 브랜치가 같은 행을 UPDATE → 60초 블록 후 트랜잭션 타임아웃. (`mg_channel`, `lg_merch_bal` 등)
2. **원장 이중전기** — 같은 정산건이 다이아몬드 호출로 원장에 두 번 기표되던 회계 오류.
3. **PK 채번 충돌** — `max(log_id)+1` 과 `nextval` 혼용으로 형제 브랜치가 동일 PK INSERT → 인덱스 블록.
4. **스택 파괴** — `strncpy(out, val, 127)` 의 NUL 패딩이 호출자 스택 112바이트를 훼손 → 세그폴트. (DB 락이 0인데 60초 블록되는 증상)

모두 실행 검증에서만 드러났고, 소스에서 고쳐 재검증했다. **"자동 문법 변환으로 옮기면 이 결함들이 신규 시스템에 그대로 이식된다"** 가 핵심 메시지다.

---

## 재현 방법

```bash
docker compose -f docker/docker-compose.yml up -d --build     # 빌드+부팅
# 포털: http://localhost:8090  (#arch 호출그래프 · #mig 마이그레이션 난이도)
python3 tools/gen_callgraph.py     # 호출그래프 재추출 (소스 기준)
python3 tools/gen_metrics.py       # 난이도 지표 재집계 (소스 기준)
```

지표는 전부 소스에서 자동 집계된다(`tools/gen_metrics.py`, `tools/gen_callgraph.py`).
"이 숫자 진짜냐"는 질문에 코드로 답할 수 있다.
