
Spring Batch를 운영하다 보면 Quartz 스케줄러와 연동된 배치가 갑자기 실행되지 않고 실패하는 경우가 있습니다. 특히 DB 마이그레이션 직후나 초기 설정 단계에서 자주 마주치는 에러가 하나 있는데, 바로 Primary Key 중복 오류입니다.
오늘은 로그 파일만 봐서는 원인을 파악하기 힘든 Spring Batch DuplicateKeyException 해결 방법에 대해 깊이 있게 알아보겠습니다. 특히 BATCH_JOB_INSTANCE 테이블에서 발생하는 Duplicate entry '0' 에러의 근본적인 원인과 이를 해결하기 위한 SQL 스크립트 적용법을 단계별로 정리했습니다.
1. 문제 상황: 배치 잡(Job) 실행 실패
잘 돌아가던, 혹은 새로 배포한 배치 애플리케이션이 시작하자마자 종료되면서 다음과 같은 에러 로그를 쏟아냅니다. Quartz 스케줄러가 잡을 트리거했지만, 내부적으로 Spring Batch 메타 데이터를 저장하는 과정에서 예외가 발생한 상황입니다.
주요 에러 로그 분석
2025-11-18 05:05:00.059 INFO [quartzScheduler_Worker-2] org.quartz.core.JobRunShell - Job DEFAULT.hibernateUsersJobLauncher threw a JobExecutionException:
org.quartz.JobExecutionException: Batch Job execution failed.
...
Caused by: org.springframework.dao.DuplicateKeyException: PreparedStatementCallback; SQL [INSERT INTO BATCH_JOB_INSTANCE(JOB_INSTANCE_ID, JOB_NAME, JOB_KEY, VERSION) VALUES (?, ?, ?, ?)];
Duplicate entry '0' for key 'BATCH_JOB_INSTANCE.PRIMARY'
...
Caused by: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '0' for key 'BATCH_JOB_INSTANCE.PRIMARY'
로그의 핵심은 Duplicate entry '0' for key 'BATCH_JOB_INSTANCE.PRIMARY'입니다. Spring Batch는 잡이 실행될 때 BATCH_JOB_INSTANCE 테이블에 새로운 행(Row)을 추가하려고 시도합니다. 이때 새로운 ID를 발급받아 저장해야 하는데, ID 값으로 ‘0’을 사용하려고 하다가 이미 ‘0’번 ID를 가진 데이터가 존재하여 충돌(DuplicateKeyException)이 발생한 것입니다.
2. 원인 분석: 시퀀스 테이블의 초기화 누락
“왜 하필 0번일까?”라는 의문이 드실 겁니다. Spring Batch(특히 MySQL/MariaDB 환경)는 기본적으로 Auto Increment 대신 별도의 시퀀스 테이블을 사용하여 ID를 관리합니다.
주요 시퀀스 테이블은 다음과 같습니다.
- BATCH_JOB_SEQ: Job Instance ID 관리
- BATCH_JOB_EXECUTION_SEQ: Job Execution ID 관리
- BATCH_STEP_EXECUTION_SEQ: Step Execution ID 관리
Spring Batch는 이 테이블들에서 현재 값(Current Value)을 읽어오거나 업데이트하여 다음 ID를 생성합니다. 하지만 이 시퀀스 테이블들에 초기 데이터(row)가 아예 존재하지 않는 경우, 일부 구현체에서는 NEXT_VAL 로직이 정상 동작하지 않고 기본값인 0을 반환하거나, 업데이트 구문이 실패하여 계속 0으로 인식되는 문제가 발생합니다.
즉, 배치 DB 스키마 생성 시 시퀀스 테이블에 대한 초기 INSERT 문 실행이 누락되었기 때문에 발생하는 문제입니다.
3. Spring Batch DuplicateKeyException 해결 솔루션
이 문제를 해결하기 위해서는 시퀀스 테이블에 초기값을 넣어주어야 합니다. 그리고 만약 이미 BATCH_JOB_INSTANCE 등의 테이블에 데이터가 쌓여 있는 상태라면, 시퀀스 값을 현재 최대 ID 값보다 높게 맞춰주는 작업(Sync)이 필요합니다.
아래 절차를 순서대로 진행해 주세요.
단계 1: 시퀀스 테이블 초기화 (데이터 삽입)
가장 먼저 비어있는 시퀀스 테이블에 초기 데이터를 넣어줍니다. duplicate 에러를 방지하기 위해 NOT EXISTS조건을 포함한 안전한 SQL 문을 사용합니다. 아래 SQL을 DB 클라이언트에서 실행해 주세요.
SQL
-- 1. Step Execution 시퀀스 초기화
INSERT INTO BATCH_STEP_EXECUTION_SEQ (ID, UNIQUE_KEY)
SELECT * FROM (SELECT 0 AS ID, '0' AS UNIQUE_KEY) AS tmp
WHERE NOT EXISTS(SELECT * FROM BATCH_STEP_EXECUTION_SEQ);
-- 2. Job Execution 시퀀스 초기화
INSERT INTO BATCH_JOB_EXECUTION_SEQ (ID, UNIQUE_KEY)
SELECT * FROM (SELECT 0 AS ID, '0' AS UNIQUE_KEY) AS tmp
WHERE NOT EXISTS(SELECT * FROM BATCH_JOB_EXECUTION_SEQ);
-- 3. Job Instance 시퀀스 초기화 (가장 중요)
INSERT INTO BATCH_JOB_SEQ (ID, UNIQUE_KEY)
SELECT * FROM (SELECT 0 AS ID, '0' AS UNIQUE_KEY) AS tmp
WHERE NOT EXISTS(SELECT * FROM BATCH_JOB_SEQ);
이 쿼리들은 각 시퀀스 테이블이 비어있을 때만 ID = 0인 초기 행을 삽입합니다.
단계 2: 현재 데이터의 최대 ID 확인
초기화를 마쳤다고 끝이 아닙니다. 만약 기존에 배치가 몇 번 돌아서 BATCH_JOB_INSTANCE 테이블에 이미 데이터가 존재한다면(예: ID가 100번까지 생성됨), 시퀀스 테이블의 ID를 0으로 두면 다음 실행 시 ID 1을 생성하려다 또 충돌이 납니다.
따라서 현재 쌓인 데이터의 최대 ID 값을 확인해야 합니다.
SQL
SELECT MAX(JOB_INSTANCE_ID) FROM BATCH_JOB_INSTANCE;
단계 3: 시퀀스 값 동기화 (업데이트)
상황 A: 위 쿼리 결과가 NULL 이거나 0인 경우 초기 상태이므로 추가 작업 없이 배치를 재실행하면 됩니다.
상황 B: 위 쿼리 결과가 특정 숫자(예: 100)인 경우 시퀀스 테이블의 값을 현재 최대값보다 크게(예: 101) 업데이트해주어야 다음 배치 실행 시 충돌이 발생하지 않습니다.
SQL
-- 예시: MAX(JOB_INSTANCE_ID)가 100이었다면 101로 설정
UPDATE BATCH_JOB_SEQ SET ID = 101 WHERE UNIQUE_KEY = '0';
-- (필요시) 다른 시퀀스 테이블도 동일하게 MAX 값을 확인 후 업데이트
-- UPDATE BATCH_JOB_EXECUTION_SEQ SET ID = [MAX값+1] WHERE UNIQUE_KEY = '0';
-- UPDATE BATCH_STEP_EXECUTION_SEQ SET ID = [MAX값+1] WHERE UNIQUE_KEY = '0';
4. 결론 및 요약
Duplicate entry '0' 에러는 Spring Batch가 사용하는 시퀀스 테이블(BATCH_JOB_SEQ 등)이 비어있어 ID 생성이 꼬였을 때 발생합니다.
오늘 알아본 Spring Batch DuplicateKeyException 해결 과정을 요약하면 다음과 같습니다.
- 원인: 시퀀스 테이블(
BATCH_*_SEQ)에 초기 데이터가 없어 ID가 0으로 고정됨. - 조치 1: 누락된
INSERT문을 실행하여 시퀀스 테이블에 행을 생성. - 조치 2: 기존 데이터가 있다면
MAX(ID)를 확인하여 시퀀스 테이블의ID값을 그보다 크게 업데이트.
이 문제는 Spring Batch 버전을 올리거나 DB를 변경할 때 스키마 스크립트(schema-*.sql)가 제대로 실행되지 않아 자주 발생합니다. 위 SQL 조치 방법을 숙지해 두시면 당황하지 않고 빠르게 서비스를 정상화하실 수 있습니다.