역할

lh-smah-batchDB에 등록된 PostgreSQL 함수(프로시저)를 주기에 맞춰 대신 호출해 주는 실행기임. 자기 업무 로직이 거의 없음. 전체 그림은 공개노트/개발/시스템 구성.

  • cmn.tb_proc_m(프로시저기본)에서 실행 대상 목록을 읽어 SELECT {프로시저명}(...) 형태로 호출함
  • 통계 화면의 원천 데이터(단지별 가입자수, IoT 제어수, 이벤트 발생수 등)가 여기서 만들어짐
  • 외부에 노출된 API 없음. 아무도 배치를 호출하지 않고 자체 스케줄(@Scheduled)로만 돎
  • 예외로 COTIS 잡 1개만 LH 에너지통합플랫폼을 직접 HTTP 호출해 단지·건물·세대 기준정보를 받아 적재함

SCW와의 구분

같은 cmn.tb_proc_m 테이블을 공개노트/개발/모듈/SCW 스케줄러 서버도 읽음. 담당이 컬럼으로 갈림.

구분배치SCW
집는 행cron_schdl_yn = 'N'cron_xprssn_vl이 있는 행
주기 출처소스에 하드코딩된 cron 7개DB의 cron_xprssn_vl
실행 대상DB 프로시저(집계·삭제)자바 서비스 로직(날씨, 단말기 수집 등)
actv_trg_svr_id쿼리에서 미사용Active WAS 판정에 사용

기술 스택

항목
프레임워크Spring Boot 2.4.7, Java 8, Gradle, jar 패키징(lh-smah-batch-{프로필}.jar)
배치spring-boot-starter-batch + Spring @Scheduled
영속성MyBatis + HikariCP, PostgreSQL smahdb
포트8880 (컨트롤러 0개지만 내장 톰캣이 뜸)
외부 호출Apache HttpClient, Squid 프록시 경유(주소가 소스에 하드코딩됨)
로그현재 파일 /app/logs/was/tomcat-batch/logs/batch.log, 롤링본·에러 파일은 /app/logs/tomcat-batch/
Jenkins 잡없음. APW·SCW·OAW만 있음 → 수동 빌드·배포로 추정
  • 다른 WAS와 달리 war가 아니라 jar임. 톰캣에 올리지 않고 java -jar로 뜸. 공개노트/개발/배포와 빌드
  • 프로필은 빌드 시점에 고정됨(src/main/resources-{프로필}). 실행할 때 -Dspring.profiles.active=prod를 줘도 안 바뀜. 어느 프로필인지는 jar 파일명으로 판별할 것
  • 8880 포트가 이미 점유되어 있으면 배치 전체가 기동 실패함
  • 로그 패턴에 [traceId=]가 있으나 배치는 MDC에 값을 넣지 않아 항상 빈 값

잡 7개와 스케줄

cron은 전부 BatchScheduler에 하드코딩되어 있고 DB로 못 바꿈.

cron하는 일실제 실행 시점
job1매분 5초pod_cd='m'·pod_hr='1' 프로시저매분
jobMin5분마다 10초pod_hr='5' 프로시저분이 5의 배수일 때
jobMin1515분마다 15초pod_hr='15' 프로시저분이 5의 배수일 때
jobHour5분마다 25초pod_cd='H' 시간별 집계run_scd분(비면 5분)
jobDay5분마다 30초pod_cd='D' 일별 집계·삭제run_scd시 10분(비면 00:10)
jobMon5분마다 35초pod_cd='M' 월별 집계run_scd일 00:15(비면 1일)
jobCotis매일 03:20:10에너지통합플랫폼 기준정보 동기화매일 1회
  • 시간/일/월 잡은 cron 자체가 5분마다 돌고, 실제 실행 여부를 코드 안에서 걸러 냄. 그래서 run_scd5의 배수만 유효함
  • 초를 5·10·15·25·30·35로 어긋나게 둬 동시 기동을 피함
  • pod_cd='Y'(연별)는 조회 쿼리만 있고 잡이 없음. 등록해도 영원히 안 돎
  • 잡 7개는 각자 전용 스레드풀(크기 1)에서 독립적으로 돎. 서로 막지 않음

실행 이력 보는 법

장애 진단은 cmn.tb_proc_run_h(프로시져실행이력)부터 봄. 자바가 프로시저 반환값을 버리기 때문에 성공·실패는 이 테이블에만 남음.

rsl_cd
0실행 중. 시작 로그만 남고 종료 로그가 없음 = 비정상 종료 의심
1성공
2 및 그 외오류. 원인은 er_msg

조회 키는 proc_nm + proc_trg_tm_vl(월 YYYYMM / 일 YYYYMMDD / 시간 YYYYMMDDHH0000 / 분 YYYYMMDDHHMI00)임.

자바 로그에는 오류가 없는데 통계가 비어 있는 경우가 흔함. 프로시저 내부에 예외 처리가 있어 업무 오류를 rsl_cd='2'로 기록하고 정상 반환하기 때문임. 반드시 er_msg를 볼 것.

Spring Batch 메타 테이블(batch.batch_*)도 병행해서 쌓임. 하루 약 2,600건 생기고 sp_table_delete가 30일 지난 행을 지움(1회 삭제 상한 있음). 배치가 며칠 멈췄다 재개되면 밀린 행이 한 번에 안 지워짐.

로그 검색 키워드

  • 잡 동작: << 1분배치 >>, << 5분 배치 >>, << 15분 배치 >>, << HourlySP >>, << DailySP >>, << MonthlySP >>
  • 대상 개수: SP개수:0이 계속 찍히면 tb_proc_mus_yn='N'이거나 cron_schdl_ynY로 잘못 들어간 것임
  • COTIS: total: (한 줄만 나옴)
  • 실패: batch_error.log

COTIS — 기준정보 동기화

매일 03:20에 LH 에너지통합플랫폼(eep.lh.or.kr)에서 단지(sbd) → 건물/동(bld) → 세대(hsh) → 단지공급정보(ssi) → 그룹-단지관계(gsr) 순으로 받아 cmn.tb_*_m에 upsert함.

  • 신규 단지·동·세대가 앱/관리자에 안 보이면 이 잡부터 볼 것. 03:20 이후 로그 확인
  • 갱신자는 전부 updr_id = 'batch'로 남음. “누가 바꿨나” 문의는 이걸로 식별함
  • 대상별 예외 처리가 없음. 앞 순서(sbd)에서 죽으면 뒤는 아예 안 돎
  • 에너지통합플랫폼이 200이 아닌 응답을 주면 잡이 실패하고 tb_proc_run_h에 cotis 행 자체가 안 남음 → “이력이 없다 = 외부 호출 실패”로 읽으면 됨
  • 이력은 항상 성공(rsl_cd='1')으로 고정 기록됨. 성공 여부 판단에 쓰지 말 것. er_msg 자리에 대상별 건수 문자열이 들어감
  • 세대 테이블의 홈넷식별번호·검침일 등 일부 컬럼은 COTIS가 건드리지 않음(항상 null로 INSERT)
  • 2023-12에 “한 번에 너무 많이 INSERT해서 터진” 장애가 있어 현재는 200건씩 나눠 넣음. 세대 수가 많은 단지가 추가되면 재발 가능 영역임

배치가 멈추면 무엇이 비는가

안 도는 잡증상
jobHour시간별 App·WEB 가입자·접속자 통계, 시간별 IoT 제어 통계가 빔. 투표 마감 처리·결과 집계가 안 됨
jobDay일별 통계 전부 공백(단지·동 세대수, App 이용, WEB 가입, DR 실적, IoT 제어, 관리비·임대료 등록 현황, 전출입, 이벤트 발생수). 추가로 보관주기 데이터 삭제·탈퇴회원 삭제가 멈춰 DB 용량이 계속 증가
jobMon월별 집계
jobCotis신규 단지·동·세대가 DB에 안 들어와 앱·관리자에서 안 보이거나 옛날 값으로 남음

밀린 회차를 따라잡지 않음(catch-up 없음). 03:00~06:00 정지했다 살아나면 그날 00:10 일배치는 영영 안 돎. 수동으로 SELECT cmn.{프로시저명}('0','{YYYYMMDD}','M');를 실행해 메꿔야 함(각 SQL 파일 주석의 < example > 참고).

주요 프로시저

프로시저 목록·SQL 로직·집계 규칙은 공개노트/개발/모듈/통계 집계 DB 함수에 정리돼 있음. 여기서는 배치 관점만 다룸.

운영 DB의 실제 등록 내역은 아래로 확인할 것.

SELECT * FROM cmn.tb_proc_m WHERE cron_schdl_yn='N' AND us_yn='Y' ORDER BY pod_cd, run_sqn;
  • 시간(H) 4개(App·WEB 가입자·접속자, IoT 제어, 전자투표), 일(D) 13개(각종 일별 집계 + 보관주기 삭제·탈퇴회원 삭제·공공데이터 감시)
  • sp_tb_elc_vt_rsl_lSQL 파일명이 sp_elc_vt_rsl_l.sql로 불일치
  • cmn.sf_sp_st_log / sf_sp_ed_log는 각 프로시저가 내부에서 부르는 이력 기록용 공통 함수임

tb_proc_m 주요 컬럼 (배치 관점)

컬럼배치에서의 의미
proc_nm그대로 SQL에 치환되어 호출됨
cron_schdl_ynN이어야 배치가 집음. Y면 SCW 담당
pod_cd / pod_hr주기코드(m/H/D/M) / m일 때 1·5·15로 세분
run_scd실행 시점. H=분, D=시, M=일. 5의 배수만 유효
run_sqn실행 순번. 앞 순번이 실패하면 뒤가 전부 안 돎
us_ynY만 실행
actv_trg_svr_id배치 쿼리에서 미사용. 아래 알려진 이슈 참고

알려진 이슈

  1. 프로시저 루프에 예외 처리가 없음. 하나가 실패하면 그 뒤 run_sqn의 프로시저가 그 회차에 전부 스킵됨
  2. actv_trg_svr_id 필터가 쿼리에 없음. 배치를 2대 이상 띄우면 같은 프로시저를 중복 실행함. 집계 프로시저는 대부분 DELETE 후 INSERT라 PK 충돌 또는 집계 중복·누락이 날 수 있음
  3. 중복 실행 방지가 스레드풀 크기 1에만 의존함. 잡 파라미터에 실행 시각을 넣어 Spring Batch의 중복 방지가 무력함. 같은 서버 안에서는 막히지만 막힌 사실이 로그에 안 남고 batch.batch_job_execution에 FAILED만 쌓임
  4. 재시도·스킵·자동 재기동 없음. 실패한 회차는 그냥 버려짐
  5. COTIS에 HTTP 타임아웃이 없음. 상대 서버가 응답을 안 주면 스레드가 무한 대기하고, 스레드풀이 1이라 다음 날 기동도 거부됨
  6. COTIS 트랜잭션이 통짜임. 중간에 실패하면 앞서 성공한 upsert도 롤백됨
  7. COTIS 건수 집계 누락. rsl_row_cntgsr 건수가 빠져 있어 로그의 total:과 값이 다름
  8. jasypt 암복호화 키가 같은 설정 파일에 평문임([REDACTED] 처리 대상). 알고리즘도 구식이라 암호화 실효가 거의 없음. 공개노트/개발/모듈/비밀번호와 전화번호 암호화
  9. local 프로필 설정에 운영 DB 정보와 구형 평문 비밀번호 형식이 주석으로 잔존함
  10. 프록시 주소와 cron이 소스에 하드코딩됨. 바뀌면 재빌드가 필요함
  11. @Profile 가드가 주석 처리되어 있음. 개발 PC에서 local로 띄워도 스케줄러가 전부 돌아 개발 DB 프로시저가 실제로 실행됨
  12. dev/prod 콘솔 로그가 실제로는 안 나감. appender 정의만 있고 연결이 빠져 있음
  13. 로그 경로가 두 곳으로 갈림. 현재 파일과 롤링·에러 파일 디렉터리가 달라 둘 다 봐야 함
  14. prod 커넥션풀 설정이 전부 주석 처리되어 Hikari 기본값(pool 10, 타임아웃 30초)으로 돎. dev(6)보다 큼
  15. run_sqn 중복(sp_tb_evn_occ_dly_y·sp_tb_mngr_h_dly_y 둘 다 D/5/10)으로 실행 순서가 비결정적임
  16. 테스트 코드 사실상 없음(컨텍스트 로딩 1건)

확인 필요

확인 필요

  • 운영 cmn.tb_proc_m의 실제 등록 내역. 이 페이지의 프로시저 목록은 SQL 파일 주석의 등록 예시 기준임. pod_cd='m'(1·5·15분) 항목이 실제로 있는지 확인 필요. 없다면 job1·jobMin·jobMin15는 헛돌면서 메타 테이블 행만 쌓고 있는 셈임
  • actv_trg_svr_id 필터 누락이 의도인지 버그인지. 운영에 배치가 몇 대 떠 있는지를 먼저 확인해야 영향도 판단이 가능함
  • COTIS의 정확한 의미와 호출 URL 끝의 1이 무엇인지(추정: 최근 1일치 변경분). 조회 범위가 1일이 맞다면 배치가 하루 이상 멈췄을 때 그 기간 변경분이 영구 누락됨. 에너지통합플랫폼(SQI·헤리트) 연동규격서 확인 필요
  • COTIS 호출에 인증이 정말 없는지. 코드상 토큰·API Key 헤더가 없음. IP 화이트리스트로만 통제되는 구조인지 확인 필요
  • SQL 인젝션 표면. 프로시저명이 문자열 치환으로 SQL에 들어감. tb_proc_m.proc_nm을 수정할 수 있는 관리자 화면이 있다면 임의 SQL 실행 경로가 됨. 화면 존재 여부와 권한 확인 필요
  • Spring Batch 메타 테이블 스키마 접근 방식. 설정에 스키마 접두어가 없는데 실물은 batch 스키마에 있음. DB 계정의 search_path 확인 필요
  • 배치 기동·종료 스크립트와 배포 경로. Jenkins 빌드 대상에 배치가 없어 수동 빌드·수동 배포일 가능성이 높음. 어느 서버에서 어떤 명령으로 뜨는지 확인 필요
  • 통계 테이블(*_dly_y, *_hly_y)을 소비하는 화면 특정. 서버 레포에는 이 테이블을 읽는 코드가 없고 SMW/SMC 또는 LMW/LMC 소스를 봐야 함. 그래야 “배치가 멈추면 어느 화면이 비는지”를 화면 단위로 말할 수 있음
  • sp_tb_evn_occ_dly_y·sp_tb_mngr_h_dly_yrun_sqn 중복 정리 필요 여부
  • tb_proc_run_h.run_ty_cd='P'(COTIS 전용)의 정의. 코드·SQL 어디에도 없음
  • google-auth-library-oauth2-http 의존성이 실제로 필요한지. 소스에서 사용처를 못 찾음

관련

공개노트/개발/모듈/통계 집계 DB 함수 공개노트/개발/시스템 구성 공개노트/개발/모듈/SCW 스케줄러 서버 공개노트/개발/배포와 빌드 공개노트/개발/모듈/APW 연계 서버 기본매뉴얼/운영/tomcat과 배치 기동 기본매뉴얼/운영/WAS 로그 확인 기본매뉴얼/운영/장애 대응 절차