기존 3개월치(~20만행)를 5.5년치 대량 히스토리로 확대. 성장추세·계절성· 가맹점 생애주기 반영. ID 오프셋 대역(1천만+)으로 기존/라이브 채번과 비충돌, 전부 idempotent 가드. - 98-bulk-seed.sql: 가맹점 40→200(MCC·요율·개점일·CLOSED/HOLD 다양) + 매입 135만건(2021-01~2026-04, 성장추세 연 0.5→1.4배·12월 계절피크·주중주말· 다양 채널/발급사/카드종류/금액구간). 27초. - 99-bulk-derive.sql: 승인 135만·원장 2.6M(RECON+SETTLE)·정산 1.29M· 가맹점집계 358k. 시퀀스 정렬. 31초. - 99b-bulk-econ.sql: 정산경제 파생 — 수수료3층분해 1.29M·프레젠트먼트 1.29M· ISO8583 클리어링 1.29M·순액정산(발급사×일) 16k·펀딩 355k. - 99c-bulk-index.sql: 운영 인덱스 14개(biz_date/merchant/status 등) + ANALYZE. 검증 (실측) - 총 1,130만 행, DB 1.8GB. 연도별 성장 2021년 14.5만→2025년 35.3만건. - 3층 항등식 1,293,145/1,293,145, 펀딩 항등식 355,284/355,284 (100% 성립). - 대시보드/정산경제 화면 정상: 활성가맹점 191, 총매입 3,997억, 입금률 95.7%. - 집계쿼리 0.5초(인덱스). 최근 3개월 라이브 데이터 무손상. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.6 KiB
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) 전부 항등 검증.
발견된 실결함 (레거시가 실제로 위험하다는 증거)
예제를 만들며 실행 중에 밟은, 정적 분석으로는 잡히지 않는 결함들 — 이게 곧 마이그레이션 리스크의 실물이다.
- 크로스브랜치 핫로우 교착 — 한 글로벌 트랜잭션의 형제 XA 브랜치가 같은 행을 UPDATE → 60초 블록 후 트랜잭션 타임아웃. (
mg_channel,lg_merch_bal등) - 원장 이중전기 — 같은 정산건이 다이아몬드 호출로 원장에 두 번 기표되던 회계 오류.
- PK 채번 충돌 —
max(log_id)+1과nextval혼용으로 형제 브랜치가 동일 PK INSERT → 인덱스 블록. - 스택 파괴 —
strncpy(out, val, 127)의 NUL 패딩이 호출자 스택 112바이트를 훼손 → 세그폴트. (DB 락이 0인데 60초 블록되는 증상)
모두 실행 검증에서만 드러났고, 소스에서 고쳐 재검증했다. "자동 문법 변환으로 옮기면 이 결함들이 신규 시스템에 그대로 이식된다" 가 핵심 메시지다.
재현 방법
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).
"이 숫자 진짜냐"는 질문에 코드로 답할 수 있다.