# 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. - **업무 포털**: 운영 대시보드·매입·정산·가맹점·원장·아키텍처·난이도 화면. ~17.5만 행의 운영 데이터. ## 발견된 실결함 (레거시가 실제로 위험하다는 증거) 예제를 만들며 실행 중에 밟은, **정적 분석으로는 잡히지 않는** 결함들 — 이게 곧 마이그레이션 리스크의 실물이다. 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`). "이 숫자 진짜냐"는 질문에 코드로 답할 수 있다.