Files
invyone/notes/hjjeong/2026-05-13-batch-pipeline-handoff.md
T
hjjeong 8a10edd8e1 fix(배치관리): DB 커넥션 변경 시 테이블 목록이 안 바뀌는 버그
- 외부 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>
2026-05-19 13:23:24 +09:00

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 확인)

이번 세션에서 한 일:

  1. 코드 ↔ XML namespace.id 매칭 정합성 확인batchExecutionLog.{insert,update}BatchExecutionLog 둘 다 존재. 매핑 OK
  2. 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 명시 송출 → 정합 OK
  3. useGeneratedKeys="true" keyProperty="id" 동작 가능성id 가 VARCHAR + nextval('batch_execution_logs_id_seq') 디폴트. PG JDBC 가 String 으로 회수 → mapper UPDATE WHERE id = #{id} 그대로 사용 가능. OK
  4. tenant routing 정합성 (정적)executeBatchConfig@Transactional 아님 → 매 sqlSession.getConnection() 마다 TenantRoutingDataSource.determineCurrentLookupKey() 호출 → TenantHolder 통과. OK
  5. 과거 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 마무리)
  6. 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: @BeforeAll setupSchema (DROP/CREATE), @AfterAll teardown (DROP), @BeforeEach TRUNCATE

발견한 것

단계 결과
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 이 정공법:

  1. docker compose -f docker/dev/docker-compose.backend.mac.yml up -d 또는 ./gradlew bootRun (운영 DB 가리킴)
  2. 인증 토큰 발급 (POST /api/auth/login 같은 endpoint — admin 계정 필요)
  3. 메타 DB 에 batch_config + batch_mappings INSERT (SQL)
  4. POST /api/batch-management/batch-configs/{id}/execute 호출
  5. batch_execution_logs 확인 + cleanup

BatchExecutor 코드 자체 수정은 PR #12 머지된 코드라 의도적 회피.

Git 상태 (세션 끝 기준)

완료된 커밋 (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 까지의 전체 흐름이 종단으로 동작하는 적이 한 번도 없었음
  • 확인 절차:
    1. batchmngList/edit/[id] 또는 batch-management-new 진입
    2. ConditionalEditor 로 conditional 룰 1건 추가 (예: from_value='A' → to_value='가나')
    3. 저장 → DB 에서 batch_mappings.mapping_config 가 JSONB 로 잘 들어갔는지 확인 (SELECT id, mapping_type, mapping_config FROM batch_mappings WHERE batch_config_id=...)
    4. 수동 실행 버튼 → 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.buildInsertSqlON 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.executeconfig.get("company_code") 로만 회사 식별 → MappingTransformer 의 fixed/conditional 룰에 회사 정보 주입은 정확한지
  • 확인: 회사 A 의 batch 가 회사 B 의 데이터를 fetch/write 하지 않는지 (multi-tenant 핵심)

알려진 잠재 리스크 (정적 점검에서 OK 였지만 운영 시 마주칠 가능성)

  1. sqlSession.getConnection() + tenant routing 2026-05-14 정적 OK 확인 (@Transactional 미사용 → 매번 routing). 다중 회사 동시 실행 종단 검증은 위 "🚨 C" 항목
  2. MyBatis namespace.id 매칭 2026-05-14 XML 대조로 batchExecutionLog.{insert,update}BatchExecutionLog 둘 다 존재 확인
  3. @Transactional writeTo self-calld5925472 에서 제거함. 행 단위 독립 commit (vexplor_rps 와 동일 패턴)
  4. duration_ms / *_records VARCHAR 2026-05-14 운영 DB read-only 로 컬럼 타입 일치 확인 + String.valueOf 송출 정합 OK
  5. 외부 호출 인증 안내 — 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_db TO 쓰기 — 미지원 (INVYONE ExternalDbConnectionService 가 보안상 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 value prop 누락 다른 페이지에 있을 가능성
  • ::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-menus 30s 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.mdbatch_*_id 컬럼은 VARCHAR. ::varchar 캐스팅 필수

운영 DB 접속 (검증용 read-only 만)

host: 183.99.177.40
port: 5432
user: postgres
password: invyone0909!!
db: invyone (메타) / 테넌트 별 invyone_* DB

사용자 허락한 범위: read-only SELECT 만.