ClassNotFoundException
문제를 해결하기 위해, 심볼릭 링크(Symbolic Link)를 활용한 무중단 배포 전략을 단계별로 설명한다. 이 가이드를 통해 안정적인 배치 운영 환경을 구축할 수 있다.스프링 배치 Job 실행 중에 새로운 버전의 JAR 파일이 배포되면서 발생하는 java.lang.NoClassDefFoundError
또는 java.lang.ClassNotFoundException
는 많은 개발자가 직면하는 문제이다. 이로 인해 배치 Job은 비정상적으로 중단되고 데이터 정합성 문제까지 야기할 수 있다. 본 문서는 이러한 문제를 해결하고 안정적인 배포 파이프라인을 구축하는 것을 목표로 한다.
0. 시작 전 준비 사항
본 튜토리얼을 진행하기에 앞서, 아래의 환경이 준비되어 있는지 확인해야 한다.
- Java Development Kit (JDK): v11 이상
- Spring Batch Application: 실행 가능한 JAR 파일 형태로 빌드된 애플리케이션
- OS: Linux/Unix 계열 (심볼릭 링크 지원)
모든 요구사항이 충족되었다면, 배치를 운영할 서버의 디렉터리 구조를 다음과 같이 가정한다.
/app/batch/
├── 20250616100000/ # 버전별 디렉토리 (v1)
│ └── my-batch.jar
└── current-batch.jar -> /app/batch/20250616100000/my-batch.jar # 심볼릭 링크
1단계: 문제 상황 분석
기존 배포 방식은 실행 중인 JAR 파일을 직접 교체하여 예측 불가능한 오류를 발생시킨다. Job이 실행되는 도중 배포 스크립트가 JAR 파일을 덮어쓰거나 삭제하면, 실행 중이던 JVM은 해당 파일의 변경을 인지하지 못하고 클래스 로딩에 실패한다.
# 문제 발생 가능성이 있는 배포 스크립트 예시
scp new-batch.jar user@server:/app/batch/my-batch.jar
이 방식은 Job의 실행 상태와 무관하게 파일을 교체하므로 매우 불안정하다.
배포 직후
java.lang.ClassNotFoundException
또는 java.lang.NoClassDefFoundError
오류가 발생한다면, 실행 중인 JAR 파일이 배포 프로세스에 의해 변경되었을 가능성이 매우 높다. 이는 실행 중인 JVM이 참조하던 파일 디스크립터(File Descriptor)가 유효하지 않게 되기 때문이다.2단계: Symbolic Link를 활용한 배포 전략 수립
문제의 핵심은 실행 중인 Job이 참조하는 파일 경로를 배포 과정에서 변경하지 않는 것이다. 심볼릭 링크를 활용하면 이 아이디어를 간단하게 구현할 수 있다. 실제 Job 실행 스크립트는 고정된 이름의 심볼릭 링크를 바라보게 하고, 배포 시에는 새로운 버전의 JAR를 업로드한 뒤 심볼릭 링크의 연결 대상만 변경한다.
배포는 타임스탬프나 버전 번호를 포함한 새 디렉터리를 생성하는 것부터 시작한다. 이렇게 하면 이전 버전의 파일이 항상 서버에 보존된다.
# 1. 새 버전의 JAR 파일을 위한 디렉토리 생성 (타임스탬프 활용)
VERSION=$(date +%Y%m%d%H%M%S)
mkdir /app/batch/$VERSION
# 2. 새로운 JAR 파일을 해당 디렉토리로 복사
cp new-batch.jar /app/batch/$VERSION/my-batch.jar
# 3. 새로운 JAR를 가리키는 임시 심볼릭 링크 생성
ln -s /app/batch/$VERSION/my-batch.jar /app/batch/current-batch.jar.tmp
# 4. mv 명령어로 심볼릭 링크를 원자적으로(atomically) 교체
# 이 과정은 매우 빠르게 진행되어 링크가 없는 순간이 거의 없음
mv -Tf /app/batch/current-batch.jar.tmp /app/batch/current-batch.jar
mv -Tf
명령어는 대상이 존재할 경우 덮어쓰므로, 심볼릭 링크를 '원자적으로' 교체하여 전환 과정에서 발생할 수 있는 위험을 최소화한다. 이는 스크립트 안정성을 높이는 중요한 기법이다.3단계: 실행 스크립트 작성 및 결과 확인
모든 배포 준비가 완료되었다. Job 실행 스크립트(예: run.sh
)는 실제 JAR 파일이 아닌, 고정된 이름의 심볼릭 링크(current-batch.jar
)를 실행하도록 작성한다.
#!/bin/bash
java -jar /app/batch/current-batch.jar --job.name=myJob
이 방식을 통해 얻는 결과는 다음과 같다.

- 실행 중인 Job: 배포가 진행되어도 최초 실행 시점의 심볼릭 링크가 가리키던 이전 버전의 JAR 파일을 계속 참조하여 안정적으로 완료된다.
- 새롭게 실행되는 Job: 배포 완료 후 실행되는 Job은 변경된 심볼릭 링크를 따라 새로운 버전의 JAR 파일을 실행한다.
결론
이상으로 스프링 배치 환경에서 심볼릭 링크를 활용한 무중단 배포 전략을 검토하였다. 이 방식은 실행 중인 Job의 안정성을 보장하고, 문제가 발생했을 때 신속하게 이전 버전으로 롤백할 수 있는 환경을 제공한다. 배포 스크립트 또한 파일 복사와 심볼릭 링크 교체라는 명확한 역할만 수행하게 되어 관리가 용이해진다.
이를 통해 배치 서비스의 안정성을 크게 향상시키고, 장애에 대한 두려움 없이 지속적인 통합 및 배포(CI/CD) 파이프라인을 구축할 수 있다.
'기술' 카테고리의 다른 글
.gitlab-ci.yml 하나로 main, dev 브랜치 최신 상태로 동기화하기 (0) | 2025.06.16 |
---|---|
Cursor 생산성 200% 향상: MCP로 나만의 Colima 도우미 만들기 (0) | 2025.06.16 |
n8n을 Cursor AI의 MCP 서버로 활용하기 (0) | 2025.06.16 |
내 서버에 n8n 자동화 허브 구축하기 (feat. Coolify) (0) | 2025.06.15 |
Ubuntu 22.04 환경에서 Coolify PaaS 구축하는 방법 (0) | 2025.06.15 |