1. ExecutionContext 사이즈 초과 발생 원인
Spring Batch에서 아래와 같은 방식으로 대용량 데이터를 ExecutionContext에 저장할 경우, 직렬화된 데이터가 수십 KB ~ 수 MB까지 증가하면서 DB 저장에 실패하거나 OutOfMemoryError가 발생할 수 있습니다.
List<MyEntity> entityList = getMyEntityList();
stepExecution.getExecutionContext().put("myEntities", entityList); //
위험
2. 안전한 대체 전략
2-1. Processor에서 get 후 즉시 remove
@StepScope
@Component
public class MyProcessor implements ItemProcessor<MyInput, MyOutput> {
@Value("#{stepExecution.executionContext}")
private ExecutionContext executionContext;
private List<MyEntity> cachedList;
@Override
public MyOutput process(MyInput item) {
if (cachedList == null) {
cachedList = (List<MyEntity>) executionContext.get("myEntities");
executionContext.remove("myEntities"); //
즉시 삭제
}
// cachedList로 처리
return convert(item, cachedList);
}
}
2-2. StepExecutionListener로 꺼내고 바로 삭제
@Component
public class EntityCachingListener implements StepExecutionListener {
private List<MyEntity> cachedEntities;
@Override
public void beforeStep(StepExecution stepExecution) {
ExecutionContext ctx = stepExecution.getExecutionContext();
cachedEntities = (List<MyEntity>) ctx.get("myEntities");
ctx.remove("myEntities"); //
시작 시 제거
}
public List<MyEntity> getCachedEntities() {
return cachedEntities;
}
}
그리고 Processor에서 Listener 사용:
@Component
@StepScope
public class MyProcessor implements ItemProcessor<MyInput, MyOutput> {
private final EntityCachingListener listener;
public MyProcessor(EntityCachingListener listener) {
this.listener = listener;
}
@Override
public MyOutput process(MyInput item) {
return processItemWith(listener.getCachedEntities(), item);
}
}
3. SpEL에서 #{stepExecution}은 어디서 오는가?
Spring Batch는 @StepScope
가 붙은 Bean에 대해 실행 시점에 StepExecution, JobExecution 등을 자동으로 SpEL 컨텍스트에 등록해줍니다.
@StepScope
@Bean
public MyProcessor myProcessor(@Value("#{stepExecution}") StepExecution stepExecution) {
return new MyProcessor(stepExecution);
}
사용 가능한 SpEL 예시:
#{stepExecution}
: 현재 StepExecution 객체#{stepExecution.executionContext['key']}
: StepExecutionContext의 특정 값#{jobParameters['date']}
: Job 파라미터
4. 마무리
ExecutionContext는 재시작이나 공유 데이터 처리에 유용하지만, 대용량 데이터를 직접 저장할 경우 오히려 장애의 원인이 됩니다. 꼭 필요한 정보만 저장하고, 불필요한 데이터는 빠르게 제거하는 전략이 필요합니다.
5. ExecutionContext 크기 확인 유틸
ExecutionContext에 저장된 객체가 실제로 얼마나 큰지 확인하고 싶다면, 아래와 같이 로그로 직렬화된 바이트 수를 확인할 수 있습니다.
import java.io.*;
import java.nio.charset.StandardCharsets;
public class ExecutionContextSizeUtil {
public static int estimateSize(ExecutionContext ctx) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject(ctx);
out.flush();
return bos.toByteArray().length;
}
}
// 사용 예시
int size = ExecutionContextSizeUtil.estimateSize(stepExecution.getExecutionContext());
log.info("ExecutionContext 직렬화 크기: {} bytes", size);
6. ExecutionContext 필터링 직렬화 전략 (선택 적용)
기본 직렬화는 모든 데이터를 저장하지만, 특정 키만 저장하거나 일부는 제외하고 싶다면 커스텀 ExecutionContextSerializer
를 등록할 수 있습니다.
public class FilteringExecutionContextSerializer implements ExecutionContextSerializer {
private final DefaultExecutionContextSerializer delegate = new DefaultExecutionContextSerializer();
@Override
public void serialize(ExecutionContext ctx, OutputStream out) throws IOException {
ExecutionContext filtered = new ExecutionContext();
for (Map.Entry<String, Object> entry : ctx.entrySet()) {
String key = entry.getKey();
if (!key.startsWith("exclude.")) { // 특정 접두어 제외
filtered.put(key, entry.getValue());
}
}
delegate.serialize(filtered, out);
}
@Override
public ExecutionContext deserialize(InputStream in) throws IOException {
return delegate.deserialize(in);
}
}
이 클래스를 Spring Batch 설정에서 등록하면, 저장 시점에 불필요한 항목은 무시됩니다.
7. DB 컬럼 타입 변경 주의사항
ExecutionContext가 커지는 문제를 해결하기 위해 다음과 같은 컬럼 타입 변경이 필요할 수 있습니다:
MySQL 예시:
ALTER TABLE BATCH_STEP_EXECUTION_CONTEXT MODIFY SERIALIZED_CONTEXT LONGTEXT;
ALTER TABLE BATCH_JOB_EXECUTION_CONTEXT MODIFY SERIALIZED_CONTEXT LONGTEXT;
주의 사항 (LOCK)
- 컬럼 타입 변경 시 Table-level Lock이 발생합니다
- 운영 중인 테이블에서는 작업 시간대 조정 필요
- 사전 백업, 테스트 DB에서 시뮬레이션 권장
마무리 체크리스트
- ExecutionContext에 List, Entity 전체 저장은 금지
- stepExecution.get().remove()로 조기에 제거
- 가능하면 ID 목록이나 경량 데이터만 저장
- 필요 시 DB 컬럼 타입을
LONGTEXT
또는CLOB
로 변경 - 커스텀 직렬화 전략으로 필터링도 가능
이 전략을 통해 Spring Batch Job의 안정성과 유지보수성을 크게 개선할 수 있습니다.