- 외부 DB id 비교를 strict === 에서 toString() 기반 string 비교로 변경 — number/string 어느 쪽으로 오든 매칭. find 실패로 toConnection=null 되면 auto-select useEffect 가 "내부 DB" 로 강제 복귀시키던 문제 해소 - 연결 변경 시 toTables/fromTables 즉시 초기화 — fetch 실패해도 직전 DB 의 테이블이 잔존하지 않도록 - 배치 파이프라인 / 외부커넥션 멀티 DB 작업 핸드오프 노트 함께 추가 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
15 KiB
배치 파이프라인 작업 핸드오프 (2026-05-13, 2026-05-14 업데이트)
작성자: hjjeong
관련 시작 노트: notes/hjjeong/2026-05-12-batch-pipeline-current-state.md
다음 새 세션에서 이 노트만 읽으면 즉시 컨텍스트 잡고 이어갈 수 있게 정리.
한 줄 상태
vexplor_rps → INVYONE 배치 파이프라인 이식 Phase 0~5 코드 완료 + 푸시 완료 + 정적 검증 완료 + PR #12 머지 완료 (2026-05-13). Phase 3 conditional 룰 종단 런타임 검증은 첫 사용자 시점으로 deferred (사용자 결정 2026-05-14). JUnit @SpringBootTest 종단 시도 (2026-05-14) → SELECT path OK, INSERT path 는 transaction context 차이로 검증 불가 → 옵션 B (보류) 결정.
2026-05-14 세션 업데이트 (정적 점검 + 운영 DB read-only 확인)
이번 세션에서 한 일:
- 코드 ↔ XML namespace.id 매칭 정합성 확인 —
batchExecutionLog.{insert,update}BatchExecutionLog둘 다 존재. 매핑 OK batch_execution_logs실 컬럼 타입 vs Service INSERT 값 정합성 — 운영 4개 DB (invyone,siflex_invyone,test01_invyone,test02_invyone) read-only 조회.id/batch_config_id/duration_ms/*_records전부 VARCHAR. Service 가String.valueOf명시 송출 → 정합 OKuseGeneratedKeys="true" keyProperty="id"동작 가능성 —id가 VARCHAR +nextval('batch_execution_logs_id_seq')디폴트. PG JDBC 가 String 으로 회수 → mapper UPDATEWHERE id = #{id}그대로 사용 가능. OK- tenant routing 정합성 (정적) —
executeBatchConfig가@Transactional아님 → 매sqlSession.getConnection()마다TenantRoutingDataSource.determineCurrentLookupKey()호출 →TenantHolder통과. OK - 과거 dev 실행 흔적 발견 — 메타 DB
invyone.batch_execution_logs에서:id=1027 (2026-02-08, total=1, success=1, server_name='unknown', process_id=54848)— 신 코드safeHostName()"unknown" fallback 지문 + 높은 PID = 로컬 dev JVM. Phase 4 ETL 1행 처리 성공 흔적id=14 (2026-04-03, total=0, server_name='unknown', process_id=54454)— Phase 5 INSERT/UPDATE 성공 흔적 (전체 코드 경로 통과 후 SUCCESS 마무리)
- Phase 3 conditional 룰은 0건 사용 중 — 4개 DB 통틀어
batch_mappings.mapping_config채워진 행 0건. UI 는 살아있지만 사용자가 입력한 적 없음 → 운영 첫 사용 시점에 종단 검증 필요
결론: 정적 점검 + dev 흔적 기준으로 Phase 4/5 골격은 동작 확인. PR 올리고 종결, Phase 3 conditional 종단은 첫 운영 사용자 시점에 함께 검증.
2026-05-14 후반 세션 — JUnit @SpringBootTest 종단 시도 → 옵션 B 보류
PR #12 머지 후, 사용자가 종단 검증을 자동화로 시도하길 요청. @SpringBootTest 통합 테스트를 만들어 운영 invyone 메타 DB 에 임시 __phase3_* 테이블 + BatchExecutor.execute 직접 호출로 검증 시도.
시도한 것
backend-spring/src/test/java/com/erp/batch/BatchExecutorIntegrationTest.java(이 파일은 검증 후 삭제 — 운영 DB 가리키는 위험한 테스트라 PR 에 남기지 않음)- 3개 테스트:
conditional_endToEnd,upsert_secondRunUpdates,rowFailureIsolation @Transactional @Commit으로 transaction context 부여,DataSource직접 주입으로 setup/teardown- Cleanup:
@BeforeAllsetupSchema (DROP/CREATE),@AfterAllteardown (DROP),@BeforeEachTRUNCATE
발견한 것
| 단계 | 결과 |
|---|---|
| Spring 컨텍스트 로딩 | ✅ OK (JWT_SECRET 더미 env 만 필요) |
| 운영 invyone 메타 connection | ✅ OK (TenantRoutingDataSource 가 META fallback) |
| 임시 테이블 setup/teardown | ✅ OK (잔여 0건) |
BatchExecutor.readFromInternal (SELECT) |
✅ totalRecords=4 정확히 통과 |
BatchExecutor.writeToInternal (INSERT) |
❌ "Connection is closed" — 전 row INSERT fail |
진단
운영 HTTP 요청 흐름 (Tomcat thread + Spring transaction-aware connection 관리) 에서는 try (Connection c = sqlSession.getConnection()) { ... c.prepareStatement(...) } 패턴이 잘 동작 (dev 흔적 id=1027 가 증거).
JUnit @SpringBootTest 는 @Transactional 붙여도 SqlSessionTemplate 의 transaction binding 이 완벽히 작동 안 함 → 외부 try (Connection c = ...) 의 c 가 body 진입 시점에 release 되어 다음 c.prepareStatement(...) 가 "Connection is closed" 던짐.
흥미로운 점: SELECT path 는 try (Connection c = ...; PreparedStatement ps = c.prepareStatement(...); ResultSet rs = ...) 처럼 한 줄에서 resource 전부 init → 시간차 없어 OK. INSERT path 는 외부 try body 에서 별도 prepareStatement 호출이라 fail.
결정 (사용자, 2026-05-14)
옵션 B (보류) 선택. 이유:
- BatchExecutor 의 핵심 변환 로직 (MappingTransformer) 은 이미 단위 테스트 18건으로 검증됨
- INSERT 흐름의 connection 관리는 production HTTP 컨텍스트에서 dev 흔적 (
id=14, 1027) 으로 검증됨 - JUnit 으로 종단 검증하려면 ① backend 띄우고 HTTP curl (셋업 ~30분) 또는 ② BatchExecutor 코드 수정 (머지된 코드라 위험) 둘 다 비용 vs 가치 불균형
- 원래 2026-05-14 초반 결정: 종단은 운영 첫 사용자 시점에 6개 검증 항목 (A~F) 으로 흡수
다음 세션 참고
만약 종단 검증을 꼭 자동화하려면 backend 띄우고 HTTP curl 이 정공법:
docker compose -f docker/dev/docker-compose.backend.mac.yml up -d또는./gradlew bootRun(운영 DB 가리킴)- 인증 토큰 발급 (
POST /api/auth/login같은 endpoint — admin 계정 필요) - 메타 DB 에 batch_config + batch_mappings INSERT (SQL)
POST /api/batch-management/batch-configs/{id}/execute호출batch_execution_logs확인 + cleanup
BatchExecutor 코드 자체 수정은 PR #12 머지된 코드라 의도적 회피.
Git 상태 (세션 끝 기준)
- 현재 브랜치:
hjjeong - 푸시:
origin/hjjeong에 22 커밋 (7315603f..f53307a7) origin/main과 무충돌 머지 완료 (f53307a7)- PR 미생성 — 사용자가 직접 https://git.junggomoa.com/gbpark/invyone/pulls/new/hjjeong 에서 생성 예정
완료된 커밋 (12개, 가독성 순)
54a8f97f fix(batch): 미리보기 → 매핑 카드 표시 흐름 정상화 + 매핑 카드 컴팩트화
cbf94dc9 feat(batch): TO DB 자동 선택 (internal) + Select 컴포넌트 controlled 화
b752de23 fix(batch): previewRestApiData convertCamelToSnake (실은 no-op, 54a8f97f 에서 정정)
6fcb101f style(batch): API 파라미터 설정을 collapsible 로 변경 — 기본 접힘
47eed680 fix(external-rest-api): WHERE ID = #{id} 에 ::varchar 캐스팅 추가
d8f606ab style(batch): 기본 정보를 모드 토글과 한 행으로 통합
e8f517ed fix(batch): batch-management-new 도 풀폭 적용
d02bc38f style(batch): FROM 카드 행 그룹화 + 컴팩트
0c9e22a6 feat(batch): 등록 REST API 연결 자동 호출 + 응답 필드 추출
570b3267 feat(batch): batch-management-new 에 conditional 매핑 추가
0bba1836 fix(batch): 빈 화면 원인 openTab 키명 정정 + 풀폭
f70719ae fix(batch): batch_execution_logs VARCHAR 컬럼에 String.valueOf
3ab7deb1 test(batch): Phase 3 — MappingTransformerTest (18 cases)
d5925472 fix(batch): writeTo @Transactional 제거 + end_time Timestamp 객체
6f8461a5 feat(batch): Phase 5 — executeBatchConfig + batch_execution_logs 기록
17172cf9 feat(batch): Phase 4 — BatchExecutor ETL 본체
f9a9c678 feat(batch): Phase 3 — MappingTransformer lookup 엔진
f31a7f85 feat(batch): Phase 2 — 프런트 ConditionalEditor + 조건 변환 매핑 UI
2675c829 feat(batch): Phase 1 — MAPPING_CONFIG JSONB 컬럼 + JSON 직렬화
dce665ca feat(batch): Phase 0 — batch_mappings CRUD path
f53307a7 Merge remote-tracking branch 'origin/main' into hjjeong
검증 상태 (2026-05-14 갱신)
| Phase | 검증 방식 | 결과 |
|---|---|---|
| Phase 0 (CRUD path) | DB 스키마 + GET 응답 — 부분 검증 | ✅ 스키마 OK / 런타임 미검증 |
| Phase 1 (MAPPING_CONFIG) | 운영 DB 컬럼 존재 확인 | ✅ 이미 컬럼 있음 (멱등 안전) |
| Phase 2 (ConditionalEditor) | 사용자 브라우저 UI 검증 | ✅ 화면 표시/조작 확인 |
| Phase 3 (MappingTransformer) | JUnit 18 tests | ✅ 전부 통과 |
| Phase 4 (BatchExecutor) | 정적 점검 + dev 실행 흔적 | 🟡 골격 OK (id=1027 (2026-02-08, total=1) 1행 처리 성공 흔적). 실 운영 데이터로 종단 미검증 |
| Phase 5 (executeBatchConfig + 로그) | 정적 점검 + dev 실행 흔적 | 🟡 INSERT/UPDATE 동작 확인 (id=14 (2026-04-03, server_name='unknown')). 다중 회사 동시 실행은 미검증 |
🚨 아직 테스트 안 한 것 / 운영 첫 사용 시 검증할 것 (★ 필독)
PR 머지 후 첫 운영 사용자 또는 QA 가 반드시 직접 확인할 항목.
A. Phase 3 conditional 룰 종단 (가장 큰 갭)
- 현재 4개 DB 통틀어
batch_mappings.mapping_config채워진 행 0건 - 첫 사용자가 ConditionalEditor 로 룰 입력 → save → execute 까지의 전체 흐름이 종단으로 동작하는 적이 한 번도 없었음
- 확인 절차:
batchmngList/edit/[id]또는batch-management-new진입- ConditionalEditor 로 conditional 룰 1건 추가 (예:
from_value='A' → to_value='가나') - 저장 → DB 에서
batch_mappings.mapping_config가 JSONB 로 잘 들어갔는지 확인 (SELECT id, mapping_type, mapping_config FROM batch_mappings WHERE batch_config_id=...) - 수동 실행 버튼 →
batch_execution_logs새 행 추가 확인 + TO 테이블에to_value가'가나'로 변환되어 들어갔는지 확인
- 잠재 이슈:
MappingTransformer.applyConditional의 default fallback / 매칭 우선순위 / NULL 처리 — 단위 테스트 통과지만 실 데이터 형태가 다를 수 있음
B. Phase 4 외부 소스/대상 종단
- FROM =
external_db(외부 DB SELECT) —ExternalDbConnectionService.executeQuery경유. dev 흔적 없음 - FROM =
restapi(등록 REST API 호출) —ExternalRestApiConnectionService.fetchData+dataArrayPath추출. dev 흔적 없음 - TO =
restapi(행 단위 POST/PUT/DELETE) —ExternalRestApiConnectionService.testConnection경유. dev 흔적 없음 - 확인: 각 타입별로 1건씩 실 호출 성공/실패 카운트가
batch_execution_logs.success_records/failed_records에 정확히 반영되는지
C. Phase 5 다중 회사 동시 실행 (tenant routing 충돌 가능성)
- 정적으로는
executeBatchConfig가@Transactional아님 + 매번sqlSession.getConnection()새로 borrow → 안전하게 보임 - 하지만 같은 시각에 회사 A 의 cron 과 회사 B 의 수동 실행이 겹칠 때
TenantHolder(ThreadLocal) 가 정확히 매번 set/clear 되는지 미검증 - 확인: 회사 2개 (
siflex,test01) 에서 동시에 같은 batch 실행 → 각자의batch_execution_logs에만 행 추가되고 cross-DB 오염 없는지
D. UPSERT (save_mode=UPSERT + conflict_key) 종단
BatchExecutor.buildInsertSql의ON CONFLICT (...) DO UPDATE SET ... = EXCLUDED. ...분기 — dev 흔적 없음- 확인: UPSERT 모드 batch 1건 만들어 실행 → 같은 키로 두 번째 실행 시 INSERT 가 아니라 UPDATE 로 동작하는지
E. 행 단위 실패 격리
writeToInternal가 try-catch 로 row-level 실패만 카운트 — 전체 트랜잭션 롤백 없음 (vexplor_rps 와 동일 패턴, 의도적)- 확인: 일부러 NOT NULL 위반 행 1건 + 정상 행 9건 섞어 실행 →
success=9, failed=1정확히 집계되는지
F. 회사 코드 필터 (companyCode) 누수
BatchExecutor.execute가config.get("company_code")로만 회사 식별 → MappingTransformer 의 fixed/conditional 룰에 회사 정보 주입은 정확한지- 확인: 회사 A 의 batch 가 회사 B 의 데이터를 fetch/write 하지 않는지 (multi-tenant 핵심)
알려진 잠재 리스크 (정적 점검에서 OK 였지만 운영 시 마주칠 가능성)
— ✅ 2026-05-14 정적 OK 확인 (sqlSession.getConnection()+ tenant routing@Transactional미사용 → 매번 routing). 다중 회사 동시 실행 종단 검증은 위 "🚨 C" 항목MyBatis namespace.id 매칭— ✅ 2026-05-14 XML 대조로batchExecutionLog.{insert,update}BatchExecutionLog둘 다 존재 확인@Transactional writeToself-call —d5925472에서 제거함. 행 단위 독립 commit (vexplor_rps 와 동일 패턴)— ✅ 2026-05-14 운영 DB read-only 로 컬럼 타입 일치 확인 +duration_ms / *_recordsVARCHARString.valueOf송출 정합 OK- 외부 호출 인증 안내 — Wehago HMAC 자동 안내 /
auth_tokens자동 조회는 의도적 미구현 (Amaranth 회사 전용이라 INVYONE 일반에는 부적합)
Phase 4 의 의도적 단순화 (vexplor_rps 대비)
to_api_body템플릿 기반 일괄 전송 — 미지원 (행 단위 POST/PUT/DELETE 만)URL_PATH_PARAM컬럼 처리 — 미지원- inline-mode REST(
from_connection_id없이 직접 URL/Key) — 미지원 auth_tokens자동 조회 — 미지원row_filter_config— 미지원external_dbTO 쓰기 — 미지원 (INVYONEExternalDbConnectionService가 보안상 SELECT-only)
위 항목이 운영 배치에 필요해지면 Phase 4.x 로 incremental 추가.
다음 후보 작업 (우선순위 무관)
A. 편집 화면 일관성
batchmngList/edit/[id]/page.tsx 에 신규 생성 화면 (batch-management-new) 과 동일한 흐름 적용:
- TO DB 자동 선택
- Select controlled (value prop)
- ConditionalEditor 동작 검증 (Phase 2 에서 이미 추가했지만 사용자 직접 확인 필요)
B. 같은 패턴 일괄 점검
- Select
valueprop 누락 다른 페이지에 있을 가능성 - ::varchar 캐스팅 누락 매퍼들 (
externalDbConnection.xml,externalCallConfig.xml,booking.xml,delivery.xml,multiConnection.xml,taxInvoice.xml등) - camelCase / snake_case mismatch 다른 진입점에 있을 가능성
C. Phase 4+5 통합 검증 ★ — 2026-05-14 deferred
PR 머지 + 운영 첫 사용 시점에 위 "🚨 아직 테스트 안 한 것" 의 A~F 항목으로 흡수. 별도 사전 검증 라운드는 안 함 (사용자 결정).
D. vexplor_rps 차이 보강
- 응답 미리보기 Quick Test 버튼 (
runQuickResponseTest) — 등록 연결 외 inline 모드에서도 즉시 응답 확인 - 인증 토큰 자동 안내 UI
E. 사이드 이슈
- 사이드바 메뉴 로딩 timeout (사용자 사용 중 단발성 발생,
/admin/user-menus30s timeout) — 단발성이라 패스했지만 재현되면 별도 진단 필요 - Frontend tsc 타입 에러 2871건 — pre-existing 누적, dev 동작은 OK 라 무시 가능하지만 정리 가치 있음
참고 메모리
feedback_no_db_no_settings.md— 명시 요청 없으면 DB/env/build 변경 금지feedback_commit_after_solved.md— 중간 단계마다 커밋 X, 해결 후 묶어서project_batch_varchar.md—batch_*의_id컬럼은 VARCHAR. ::varchar 캐스팅 필수
운영 DB 접속 (검증용 read-only 만)
host: 183.99.177.40
port: 5432
user: postgres
password: invyone0909!!
db: invyone (메타) / 테넌트 별 invyone_* DB
사용자 허락한 범위: read-only SELECT 만.