Spring Batch에서 다중 DataSource 환경에서의 커넥션 누수 방지와 안정적인 트랜잭션 처리 방법

🔹 문제 배경

대규모 배치 시스템에서 master/slave로 분리된 DB 환경을 사용하는 경우,
하나의 Step 내에서 slave로부터 읽고, master에 쓰는 구조가 필요하다.
그러나 잘못된 트랜잭션 구성으로 인해 slave의 커넥션이 반환되지 않고 누수(leak)되는 문제가 발생할 수 있다.


🔹 해결 전략

  1. slaveMapper 조회는 TransactionTemplate으로 감싸 트랜잭션 분리
  2. masterMapper는 기본 @Transactional 환경 (Step 트랜잭션)에 참여
  3. 예외가 발생해도 slave 쿼리는 이미 커밋 or 롤백되며 커넥션이 안전하게 반환됨
  4. 이 구조를 모든 Tasklet에 확장할 수 있도록 공통 템플릿 클래스 설계

🔹 핵심 코드 예시

① 트랜잭션 분리용 유틸 컴포넌트

@Component
public class SampleSlaveTransactionSupport {

private final TransactionTemplate template;

public SampleSlaveTransactionSupport(@Qualifier("slaveTransactionManager") PlatformTransactionManager txManager) {
this.template = new TransactionTemplate(txManager);
this.template.setReadOnly(true);
}

public <T> T read(Supplier<T> supplier) {
return template.execute(status -> supplier.get());
}
}

② Tasklet 내 안전한 사용 구조

List<SampleEntity> list = slaveTxSupport.read(() -> slaveMapper.selectList());

if (!list.isEmpty()) {
for (int i = 0; i <= list.size() / 1000; i++) {
List<SampleEntity> chunk = ...;
masterMapper.insertBatch(chunk); // 예외 발생 시에도 slave 커넥션은 이미 반환됨
}
}

③ 공통 추상 클래스 템플릿

public abstract class AbstractSlaveMasterTasklet<T> implements Tasklet {
...
protected abstract List<T> readFromSlave();
protected abstract void writeToMaster(List<T> list);
}

④ 실제 구현 예

@Component
public class SampleTasklet extends AbstractSlaveMasterTasklet<SampleEntity> {
@Override
protected List<SampleEntity> readFromSlave() {
return slaveMapper.selectSampleList();
}

@Override
protected void writeToMaster(List<SampleEntity> chunk) {
masterMapper.insertSampleList(chunk);
}
}

🔹 기대 효과

  • ✅ 커넥션 누수 없이 안전한 트랜잭션 처리
  • ✅ slave/master 분리에도 명확한 책임 분할
  • ✅ 공통 구조로 모든 Tasklet에 일관성 있게 적용 가능
  • ✅ 대용량 배치 환경에서도 안정적 운영 가능

관련 글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다