출제기준 세세항목: 배치 프로그램
우편물을 한 통 들어올 때마다 바로 배달하지 않고, 하루 동안 모았다가 정해진 시간에 한꺼번에 돌리는 것을 떠올려 보세요. 배치(Batch)는 이렇게 데이터를 모아 두었다가 정해진 시점에 한꺼번에 처리하는 방식입니다. 이런 일을 하는 프로그램을 배치 프로그램이라고 합니다.
배치 프로그램은 사용자가 화면 앞에서 결과를 기다리는 온라인(실시간) 처리와 대비됩니다. 온라인 처리는 버튼을 누르면 곧바로 결과가 나와야 하지만, 배치 프로그램은 사람의 개입 없이 뒤에서 조용히 많은 양을 처리합니다. 월말 급여 계산, 하루치 매출 집계, 1년 동안 로그인하지 않은 계정을 휴면으로 바꾸기, 밤사이 데이터 백업 같은 일이 대표적입니다.
배치는 보통 사용자가 적은 새벽 시간에 돌립니다. 많은 데이터를 한꺼번에 처리하면 서버에 부담이 크기 때문에, 낮 시간의 온라인 서비스와 자원을 다투지 않게 하려는 것입니다.
첫째는 대용량 데이터입니다. 배치는 수십만, 수백만 건의 데이터를 한 번에 다루는 경우가 많으므로, 많은 양을 처리할 수 있어야 합니다. 둘째는 자동화입니다. 사람이 버튼을 누르지 않아도 정해진 때에 스스로 시작하고 끝나야 합니다. 심각한 문제가 생긴 경우가 아니라면 사용자 개입 없이 돌아가야 합니다.
셋째는 견고성입니다. 중간에 잘못된 데이터가 섞여 있거나 예외 상황이 생겨도, 전체 처리가 비정상적으로 멈추지 않고 끝까지 마칠 수 있어야 합니다. 넷째는 안정성(신뢰성)입니다. 문제가 생겼을 때 언제, 어디서, 무엇 때문에 생겼는지 추적할 수 있도록 기록을 남겨야 합니다. 견고성은 '쓰러지지 않는 것', 안정성은 '쓰러졌을 때 원인을 알 수 있는 것'으로 구별하면 됩니다.
다섯째는 성능입니다. 다른 애플리케이션의 동작을 방해하지 않아야 하고, 정해진 시간 안에 처리를 끝내야 합니다. 새벽 2시에 시작한 집계가 아침 9시 업무 시작 전에 끝나지 않으면 문제가 됩니다.
배치는 실행되는 계기에 따라 세 가지로 나눕니다. 정기 배치는 매일, 매주, 매월처럼 정해진 시점마다 실행됩니다. 매일 자정의 일일 매출 집계, 매월 1일의 청구서 생성이 여기에 속합니다. 가장 흔한 유형입니다.
이벤트성 배치는 미리 정한 조건이 만족되면 실행됩니다. 예를 들어 처리할 파일이 특정 폴더에 도착하면 바로 읽어 들이거나, 쌓인 주문이 1,000건을 넘으면 출고 목록을 만드는 식입니다. 시간이 아니라 사건이 방아쇠입니다.
온디맨드(On-Demand) 배치는 사용자가 요청할 때 실행됩니다. 관리자가 화면에서 '지난달 통계 다시 계산' 버튼을 누르면 그때 대량 처리가 돌아가는 경우입니다. 요청은 사람이 하지만, 처리 자체는 배치 방식으로 한꺼번에 이뤄집니다.
배치를 정해진 때에 자동으로 실행하려면 일정을 관리해 주는 스케줄러가 필요합니다. 가장 기본적인 것이 유닉스·리눅스의 크론(cron)입니다. 크론은 운영체제에서 계속 돌아가는 프로그램(데몬)으로, crontab이라는 표에 적힌 일정을 보고 시간이 되면 해당 명령을 실행합니다. crontab -e로 일정을 편집하고, crontab -l로 등록된 일정을 확인하며, crontab -r로 지웁니다.
Quartz는 자바 애플리케이션 안에서 쓰는 스케줄러 라이브러리입니다. 실행할 일(Job)과 언제 실행할지(Trigger)를 따로 정의하고, 스케줄러(Scheduler)가 둘을 묶어 관리합니다. 운영체제의 크론과 달리 애플리케이션 코드 안에서 일정을 다루므로, 작업 결과를 바로 애플리케이션 로직과 이어 쓰기 편합니다.
Spring Batch는 스프링 기반의 대용량 배치 처리 프레임워크입니다. 하나의 배치 작업(Job)을 여러 단계(Step)로 나누고, 각 단계는 데이터를 읽고(ItemReader), 가공하고(ItemProcessor), 쓰는(ItemWriter) 흐름으로 만듭니다. 데이터를 일정 개수씩 묶어(청크) 처리하고 실행 기록을 저장해 두므로, 중간에 실패해도 실패한 지점부터 다시 시작할 수 있습니다. 정리하면 Quartz는 '언제' 돌릴지를, Spring Batch는 '어떻게' 대량으로 처리할지를 맡아 둘을 함께 쓰기도 합니다.
crontab의 한 줄은 일정 다섯 칸과 실행할 명령으로 이뤄집니다. 다섯 칸은 왼쪽부터 분(0~59), 시(0~23), 일(1~31), 월(1~12), 요일(0~7, 0과 7은 모두 일요일)입니다. 예를 들어 30 1 * * 1 /home/app/backup.sh는 '매주 월요일 1시 30분에 backup.sh를 실행하라'는 뜻입니다.
칸 안에 쓰는 기호도 알아 두어야 합니다. 별표(*)는 '모든 값', 쉼표(,)는 여러 값 나열, 하이픈(-)은 범위, 슬래시(/)는 간격입니다. 0 9 * * 1-5는 평일(월~금) 아침 9시, */15 * * * *는 15분마다, 0 0 1 * *는 매월 1일 자정, 0 8,20 * * *는 매일 8시와 20시입니다.
Quartz에서도 크론 표현식을 쓰지만 모양이 조금 다릅니다. 맨 앞에 초 칸이 붙어 '초 분 시 일 월 요일'의 여섯 칸(연도를 붙이면 일곱 칸)이고, 일과 요일 중 하나에는 '지정하지 않음'을 뜻하는 물음표(?)를 씁니다. 예를 들어 Quartz의 0 0 12 * * ?는 매일 정오입니다. 문제에서 칸 수가 다섯인지 여섯 이상인지 먼저 세어 보면 어느 형식인지 알 수 있습니다.
배치는 사람이 지켜보지 않는 시간에 돌기 때문에, 문제가 생겼을 때 알아차리고 되살리는 장치가 중요합니다. 처리 시작과 끝, 처리 건수, 실패한 데이터와 원인을 로그로 남기고, 실패하면 담당자에게 알림이 가게 합니다.
같은 배치가 두 번 실행되는 경우도 대비해야 합니다. 이전 실행이 아직 끝나지 않았는데 다음 실행이 시작되면 같은 데이터를 두 번 처리할 수 있습니다. 실행 중인지 확인하는 장치를 두고, 같은 데이터를 다시 처리해도 결과가 달라지지 않도록(한 번 처리한 건은 건너뛰도록) 만들어 두면 안전합니다.
많은 데이터를 한 번의 트랜잭션으로 처리하면, 마지막 한 건에서 실패해도 전체가 되돌려져 처음부터 다시 해야 합니다. 그래서 1,000건씩처럼 일정 단위로 나눠 저장(커밋)하고, 어디까지 처리했는지 기록해 두어 실패한 지점부터 다시 시작할 수 있게 합니다.
개인정보 보호 규정에 따라 1년 넘게 로그인하지 않은 회원을 매일 새벽 3시에 찾아 휴면 계정으로 옮기는 배치를 만들었습니다. crontab에 0 3 * * *로 등록하고, 처리한 건수와 실패한 건수를 매일 아침 담당자에게 메일로 보내도록 했습니다.
200만 건의 월말 정산 배치가 150만 건째에서 DB 연결 오류로 멈췄습니다. Spring Batch가 청크 단위로 실행 기록을 남겨 두었기 때문에, 연결을 복구한 뒤 처음부터가 아니라 멈춘 지점부터 다시 시작해 30분 만에 정산을 마쳤습니다.
협력 업체가 하루 한 번 보내 주는 재고 파일은 도착 시간이 매일 달랐습니다. 정해진 시각에 돌리는 대신, 파일이 지정 폴더에 들어오면 바로 읽어 재고를 갱신하는 이벤트성 배치로 바꾸자 재고 반영이 평균 두 시간 빨라졌습니다.