■ Oracle 계보 — "원본은 Oracle 19c + Pro*C 였다" 현행 코드(ECPG/PostgreSQL)가 Oracle 시스템의 1차 이관본임을 콘크리트하게 보존. - legacy-oracle/proc/*.pc (7본): ACQUIRE/AUTH_APPROVE/ST_MDR/SETTLE/RECONCILE/ CL_DAILY/PAY_FILEGEN. 순수 Oracle Pro*C 방언 — sqlca/oraca, VARCHAR, WHENEVER SQLERROR GOTO, NVL/DECODE/SYSDATE/DUAL/ROWNUM/(+)/CONNECT BY/ .NEXTVAL/TO_DATE, PL/SQL 패키지 호출, COMMIT WORK. 컴파일 대상 아님(As-Is 원본). - legacy-oracle/plsql/*.sql (2본): PKG_SETTLE(정산), PKG_LEDGER(복식부기 기표). VARCHAR2/%TYPE/%ROWTYPE/CURSOR/EXCEPTION/RAISE_APPLICATION_ERROR. - docs/ORACLE_PROVENANCE.md: As-Is(Oracle)→현행(ECPG)→To-Be(Spring Boot) 3단 계보 + Oracle→PostgreSQL 방언 이관 매핑 15종 + "왜 DB는 PostgreSQL인가". - gen_metrics.py: legacy-oracle 실측(원본 9파일 1,827LOC, 방언 12종 히트) → 마이그레이션 난이도 화면에 "④ Oracle 계보" 패널 추가. ■ 시스템 조망 재편 — 실제 매입시스템 운영 콘솔 "마이그레이션 난이도"(Forge 관점 메타)를 시스템 조망에서 분리하고, 시스템 자체의 상태·아키텍처를 보여주는 운영 화면 신설. - 신규 "시스템 현황"(#sys): 헬스 KPI(서버11/서비스1466/미결2PC/당일매입/채널UP/ 큐적체/오류) + 아키텍처 토폴로지(채널·카드망→전문GW→승인·매입→정산·지급→ 원장·마감·대사·검증→마스터·공통 계층도) + 채널상태 + 큐적체 + 처리량·오류율 + 정산사이클(마감·순액정산·클리어링·프레젠트먼트). 전부 실측 조회. - nav 재편: [시스템 조망]=시스템현황·아키텍처토폴로지·정산경제, [전환 분석(Forge)]=마이그레이션난이도. - 토폴로지 모듈박스 렌더 버그(return ASI) 수정. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
14 KiB
acquire-core-x
실제로 동작하는 카드 매입·정산(acquiring & settlement) 레거시 시스템 —
오픈소스 TP 모니터 Enduro/X(open Tuxedo/ATMI) + ECPG/PostgreSQL + XA 2PC 위에서
tpcall 서비스와 배치가 진짜로 기동·트랜잭션 처리된다.
목적 — Klaro Forge 자율 마이그레이션 도구의 대형 전환 대상 픽스처. 이 C/ATMI 시스템(2,000+본)을 Java Spring Boot로 전환하는 시나리오를 검증한다. 컴파일만 되는 스텁이 아니라 상용급 TP 미들웨어에서 실제로 도는 시스템이라는 점이 핵심이다.
IR·데모용 자료 —
docs/MIGRATION_COMPLEXITY.md: "Forge가 얼마나 복잡한 레거시를 다룰 수 있는가"를 3축(양적 규모·자동변환 실패지점· 실전 레거시성)으로 전량 실측해 정리. 포털에서도 실시간 확인:#arch(호출그래프 552노드·41 모듈간 경로),#mig(마이그레이션 난이도 브리핑). 지표는 전부 소스에서 자동 집계된다(tools/gen_callgraph.py,tools/gen_metrics.py).Oracle 계보(As-Is) —
docs/ORACLE_PROVENANCE.md: 원본은 Oracle 19c + Pro*C 였고, 현행 코드는 그 1차 이관본(ECPG/PostgreSQL)이다.legacy-oracle/에 As-Is Pro*C/PL-SQL 역사 원본을 보존(컴파일 제외)하고, Oracle→ PostgreSQL 방언 이관 매핑을 문서화한다.포털 화면 구성 —
#sys(시스템 현황: 상태·아키텍처 토폴로지·채널·큐·정산사이클),#arch(아키텍처 토폴로지),#econ(정산 경제: 수수료 3층·펀딩·순액정산·MCC·ISO8583),#mig(마이그레이션 난이도, Forge 관점).
1. 스택
| 계층 | 실체 | 비고 |
|---|---|---|
| TP 모니터 | Enduro/X 7.0.12 (open Tuxedo/ATMI) | tpservice/tpcall/tpadvertise/tpbegin/tpcommit, 소스빌드 |
| 전문 버퍼 | UBF (≈ Tuxedo FML32) | Bget/Bchg, 필드테이블 mkfldhdr |
| DB 접근 | ECPG (EXEC SQL) → PostgreSQL 15 |
Pro*C 대응 오픈소스 |
| 분산 트랜잭션 | XA 2PC — libndrxxaecpg.so ECPG XA 스위치 |
tmsrv가 prepare/commit/rollback 조율 |
| 오케스트레이션 | ndrxd + ndrxconfig.xml + app.ini(CCONFIG) |
서버 그룹·서비스·RM 설정 |
| 컨테이너 | docker compose (postgres + endurox app) |
재현 가능한 단일 스택 |
주의(라이선스): Enduro/X 런타임은 AGPLv3(Mavimax). 내부 테스트 자산으로 사용. 상용 Tuxedo(Oracle)·Tmax ProFrame의 오픈소스 대응이며, 진짜 Tuxedo/Tmax는 사용하지 않는다.
2. 아키텍처
2.1 런타임 토폴로지
┌──────────────────────── app 컨테이너 (Enduro/X) ─────────────────────────┐
acqdrv ─────▶ │ ndrxd (TP 모니터) │
(client) │ ├─ tmsrv (RM1 = PostgreSQL, XA 조율: prepare/commit/rollback) │
tpinit │ ├─ cconfsrv / tpevsrv / cpmsrv (시스템 서버) │
tpbegin │ ├─ ac_svr … 매입 30 서비스 (ACQUIRE, ACQ_DDC, ACQ_CANCEL …) │
tpcall ─────▶│ ├─ au_svr … 승인/한도 30 │ 11개 "모듈 서버" │
tpcommit │ ├─ rc_svr … 대사 30 │ 각 서버가 자기 모듈의 │
│ ├─ st_svr … 정산/수수료 30 │ ~30 서비스를 tpadvertise │
│ └─ … lg cl mm vl mg cm py │ (총 333 서비스) │
│ │ EXEC SQL (ECPG, XA 스위치 libndrxxaecpg) │
└────────┼───────────────────────────────────────────────────────────────┘
▼
PostgreSQL (db 컨테이너, max_prepared_transactions=100)
merchant · purchase · approval · settlement · ledger · … (75 테이블)
2.2 "모듈 서버" 패턴 (실제 Tuxedo 방식)
서비스 하나당 프로세스 하나가 아니라, 모듈 서버 1개가 그 모듈의 다수 서비스를 advertise한다.
서비스 로직은 파일 하나에 하나씩(svc/<SVCNAME>.pgc + 카피북 <SVCNAME>.h), 서버(<mod>_svr.pgc)는
얇은 디스패처(tpsvrinit에서 tpadvertise 테이블만). DB 접근은 dbio/*.pgc(정적 라이브러리로 링크),
배치는 독립 실행파일(batch/*.pgc).
2.3 XA 2PC 트랜잭션 흐름 (핵심)
client: tpbegin() # 글로벌 트랜잭션 시작
└▶ tpcall("ACQUIRE") # ac_svr: EXEC SQL INSERT purchase (XA 브랜치 A)
└▶ tpcall("RECONCILE") # rc_svr: EXEC SQL INSERT ledger (XA 브랜치 B)
└▶ tpcall("SETTLE") # st_svr: EXEC SQL INSERT settlement(XA 브랜치 C)
client: tpcommit()
└▶ tmsrv: xa_prepare(A,B,C) → xa_commit(A,B,C) # 2단계 커밋, 원자적 확정
tpabort() 시 tmsrv가 세 브랜치를 모두 xa_rollback → DB에 아무것도 남지 않는다.
ECPG XA 스위치가 tpopen()에서 연결을 열므로 EXEC SQL CONNECT가 없다. XA 브랜치 간에는
서로의 미커밋 행이 안 보이므로 각 서비스는 자기 테이블만 쓰는(owner-writes) 규율을 지킨다.
3. 디렉터리 구조
acquire-core-x/
├─ docker/
│ ├─ endurox.Dockerfile # Enduro/X 소스빌드 (-DENABLE_POSTGRES=ON → libndrxxaecpg)
│ └─ docker-compose.yml # postgres + endurox app (sysctl/ulimit 포함)
├─ db/
│ └─ schema.d/ # 00-base.sql + 모듈별 NN-<mod>.sql (75 테이블)
├─ app/
│ ├─ conf/ # app.ini(CCONFIG), ndrxconfig.xml(생성), setapp.sh(XA env)
│ ├─ ubftab/acq.fd # UBF 필드테이블 (전 모듈 공용)
│ ├─ build.sh # 모듈 자동발견 빌드 + ndrxconfig 생성
│ ├─ entrypoint.sh # 빌드 → ndrxd 기동 (+ 업무화면 게이트웨이 :8090)
│ ├─ ui/ # 업무화면 포털 (index.html + screens.js — 330 화면 매니페스트)
│ └─ src/<mod>/ # 11개 모듈 (ac au rc st py lg cl mm vl mg cm)
│ ├─ <mod>_svr.pgc # 얇은 모듈 서버(디스패처)
│ ├─ svc/*.pgc + *.h # 1서비스=1파일 + 카피북 (모듈당 30)
│ ├─ dbio/*.pgc + *.h # DB 접근 계층 (모듈당 ~40)
│ ├─ batch/*.pgc # XA 배치 (모듈당 ~22)
│ └─ run/*.sh # 운영 기동 스크립트 (모듈당 ~17)
├─ tools/
│ └─ gen_screens.py # svc 소스 → 화면 매니페스트(screens.js) 재생성기
└─ docs/
├─ architecture.md # 상세 아키텍처
└─ service-catalog.md # 서비스 카탈로그
4. 빌드 & 실행
docker compose -f docker/docker-compose.yml up -d --build # postgres + endurox app 기동
빌드(전 모듈 ecpg→buildserver, ~240 배치 컴파일)는 수 분 걸린다. 완료되면 ndrxd가
11개 모듈 서버 + tmsrv를 부팅하고, 업무화면 포털이 http://localhost:8090/ 에 뜬다.
4.0 업무화면 (프론트 — RFI의 Xplatform 내부화면 포지션)
브라우저 업무화면(app/ui, Xplatform풍) → acq_httpgw(C, Webtier 포지션) → tpcall → 모듈서버 → PostgreSQL
app/src/clients/acq_httpgw.c— C 경량 HTTP 게이트웨이(:8090, libatmiclt만 링크).GET /api/call?svc=<SVC>&_tx=1&T_필드=값...→ UBF 구성(CBchg) → (옵션) 글로벌 XAtpbegin/tpcommit→tpcall→ 응답 UBF 전체를 JSON 직렬화.app/ui/index.html+screens.js— 한국어 업무포털. 330개 전 서비스가 화면으로 노출 (모듈별 메뉴트리·검색·주요업무 즐겨찾기 9종). 각 화면의 입력폼은 해당 tpservice 소스에서 추출한 실제 입력 필드(getl/gets_)로 자동 구성되며(tools/gen_screens.py), 실행 시 실제 tpcall 이 돌고 결과 그리드·거래 저널에 표시된다.
4.1 필수 런타임 요건 (없으면 ndrxd 부팅 실패 — 실측으로 규명)
POSIX 메시지큐/세마포어 한도 때문에 app 컨테이너에 반드시 필요 (compose에 이미 반영):
sysctls:
fs.mqueue.msg_max: "512"
fs.mqueue.msgsize_max: "65536"
fs.mqueue.queues_max: "8192" # 333 서비스 = 333+ 큐, 기본 256 초과
ulimits:
msgqueue: 2147483648 # 큐 1개 = MSGMAX×MSGSIZEMAX, 다수 큐 → RLIMIT_MSGQUEUE 상향
nofile: 65536
app.ini: NDRX_MSGMAX=50, NDRX_MSGSIZEMAX=16000 (큐당 메모리 축소).
PostgreSQL: -c max_prepared_transactions=100 (XA 2단계 커밋용).
5. 검증 (실행 중인 시스템 확인)
C="docker compose -f docker/docker-compose.yml"
# 서버 프로세스 — 12개 전부 runok 이어야 함
$C exec app bash -lc '. /app/conf/setapp.sh; xadmin ppm'
# advertise된 서비스 — 333 AVAIL
$C exec app bash -lc '. /app/conf/setapp.sh; xadmin psc'
# 매입 거래 실행 (tpbegin → ACQUIRE→RECONCILE→SETTLE → tpcommit)
$C exec app bash -lc '. /app/conf/setapp.sh; /app/bin/acqdrv M0001 1000000'
# >>> COMMIT OK: purchase_id=… fee=2500 net=997500 status=S
# DB 반영 + XA 정합성
$C exec db psql -U acq -d acq -c "select * from purchase; select * from settlement;"
$C exec db psql -U acq -d acq -c "select count(*) from pg_prepared_xacts;" # 0 = 2PC 정상
검증 완료 상태: 12 서버 runok · 333 서비스 AVAIL · 매입체인 XA 원자 커밋(status=S) ·
pg_prepared_xacts=0(롤백 시 무잔존) · 전 .pgc 정규화-고유(클론 0).
6. 모듈 & 도메인
| 코드 | 모듈 | 대표 서비스 |
|---|---|---|
| ac | 매입(acquiring) | ACQUIRE, ACQ_DDC/EDI/EDC, CANCEL/CORRECT, PARTIAL/INSTALL/FOREIGN, DUPCHK, WHT, TAXINV |
| au | 승인/한도 | AUTH, LIMIT_CHK/DEC/RST, STANDIN, FRAUD_DETECT, PREAUTH, DCC_QUOTE |
| rc | 대사(reconcile) | RECONCILE, RC_3WAY(승인·매입·입금), RC_TOLERANCE, RC_AMTDIFF, RC_AUTOFIX |
| st | 정산/수수료 | SETTLE, ST_MDR, ST_VANFEE, ST_NETTING, ST_VAT, ST_WHT, ST_PAYDATE(T+n) |
| py | 지급(payment) | PAY_FILEGEN(고정길이 전문), PAY_RESULT, PAY_SPLIT, PAY_XFERLINK |
| lg | 원장(ledger) | LG_POST, LG_DOUBLE(복식부기 차/대변), LG_BALCHK, LG_REVERSE, LG_TRIALBAL |
| cl | 마감(closing) | CL_DAILY/MONTHLY/QUARTER, CL_SNAPSHOT, CL_RECLOSE, CL_YEAREND |
| mm | 마스터 | MM_MERCH_REG/UPD, MM_FEERATE, MM_BIN, MM_LIMIT, MM_GRADE |
| vl | 정합성검증 | VL_SALES, VL_CARD, VL_AMOUNT, VL_ANOMALY, VL_REFINTEG |
| mg | 전문게이트웨이 | MG_ISO8583, MG_BITMAP, MG_STAN(채번), MG_ROUTE, MG_MAC |
| cm | 공통 | CM_CODE, CM_BIZDAY, CM_LUHN, CM_CRC, CM_FX, CM_SEQ |
대표 tpcall 체인: ACQUIRE → RECONCILE → SETTLE(글로벌 XA, 매입→대사→정산→원장 반영).
7. 파일 통계
| 종류 | 수 |
|---|---|
| 전체 (git 추적) | 2,052 |
프로그램 .pgc (svc 330 + dbio ~440 + batch ~240) |
1,015 |
카피북/헤더 .h |
817 |
기동/배치 스크립트 .sh |
191 |
스키마 .sql |
11 |
| — 모듈 서버 11 · 서비스 330 · 배치 바이너리 242 · DB 테이블 75 |
전 .pgc는 정규화(식별자→X, 숫자→N, 공백제거) 후 md5 고유(클론 0) — 파일마다 실제 다른 로직.
8. 규명·해결한 실런타임 이슈 (재현 노트)
- gpgme 빌드의존 — Enduro/X
tpbridge가gpgme.h요구 →libgpgme-dev추가. - atmitest 빌드깨짐 —
DEFINE_DISABLETEST가 제외 안 함 → CMakeLists에서add_subdirectory(atmitest)제거. - mqueue 한도 —
fs.mqueue.queues_max(기본 256 < 333 서비스),RLIMIT_MSGQUEUE(큐당 5.6MB→0.8MB로 축소 + ulimit 상향). - XA 브랜치 격리 — 형제 브랜치는 서로의 미커밋 행 불가시 → owner-writes 규율 + 서비스별 분리 서버.
9. 마이그레이션 타깃 매핑 (→ Spring Boot)
| 레거시 (Enduro/X) | Spring Boot |
|---|---|
tpservice / tpadvertise |
@Service 빈 + 메서드 |
tpcall(SVC) |
빈 주입 호출(동기) |
UBF Bget/Bchg (전문버퍼) |
DTO / record |
tpbegin/tpcommit (XA) |
@Transactional (JTA 또는 단일 DB) |
EXEC SQL / ECPG dbio |
MyBatis / JPA repository |
| 커서 배치 | Spring Batch |
| 고정길이 전문 | 코덱(fixed-length) |
ndrxconfig 서비스 등록 |
컴포넌트 스캔 / 라우팅 |
db/schema.d/*.sql |
Flyway |
10. 검증 체크리스트
- Enduro/X 소스빌드 (PostgreSQL XA 포함) → 이미지
acquire-x/endurox:7.0.12 ndrxd부팅 (11 모듈서버 + tmsrv 전부 runok)- 커스텀
tpserviceadvertise + 실제tpcall왕복 - ECPG XA 서비스 체인(매입→대사→정산) + 커밋 row psql 검증 + 롤백(
prepared_xacts=0) - 2,000본+ 실동작 시스템 (2,052 파일 /
.pgc1,015본, 전 모듈 dbio·batch 세분화) - 전
.pgc정규화-고유 (클론 0) - 업무화면 포털(:8090) — 330 화면, HTTP→tpcall 게이트웨이, 화면에서 XA 체인 실행 검증
- Klaro Forge 마이그레이션 → Spring Boot (Phase 3, 예정)