1. 어떤 문제가 있었나?
스프링 배치를 통해 작업을 수행하는데 이 때 Exclusive Lock을 사용하고 있다. 근데 이 배치와 DBA 님이 만드신 배치와 Deadlock 이 발생하는 문제가 발생했다. DBA님의 배치 시간이 10초 안쪽이여서 내가 만든 배치를 20초 동안 waiting 후 진행하면 문제가 해결될 것이라 생각하여 @Transactional 어노테이션에 timeout을 20초로 설정하였으나 Deadlock이 또 발생해서 문제의 원인을 찾아보려고 한다.
@Transactional(timeout = 20)
2. 어떤 시도를 했나?
우선 디버깅 모드로 데드락이 발생하는 메소드에서 어떤 트랜잭션 정보를 사용하는지 확인해보았다. 그런데 분명 어노테이션에 timeout을 20초로 설정했는데 -1로 되어 있었다. timeout이 -1 이라는 것은 앞에 트랜잭션으로 인해 대기하는 상황이 발생하면 예외를 발생시키겠다는 의미이다.
@Transactional 어노테이션의 기본 전파 속성은 Required이다. Required는 기존 트랜잭션이 없으면 새로운 트랜잭션을 시작하지만 만약 기존 트랜잭션이 존재한다면 해당 트랜잭션에 참여하게 된다. 이와 같은 이유로 해당 메소드에서 새로운 트랜잭션을 시작하는 게 아니라 기존 트랜잭션에 참여하면서 이와 같은 문제가 발생한 것이라는 생각을 하게 되었다.
3. 문제의 원인 및 해결방법은?
우선 내가 만든 배치는 스프링 배치를 기반으로 만들어졌고 Tasklet 방식으로 개발했다. 스프링 배치에서 트랜잭션 관리까지는 하지 못하는(?) 것으로 알고 있어서 Step을 설정할 때 TransactionManager를 주입하고 있다.
@Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(entityManagerFactory);
return jpaTransactionManager;
}
@Bean
public Step testStep(JobRepository jobRepository, Tasklet testTasklet, PlatformTransactionManager transactionManager) {
return new StepBuilder("testStep", jobRepository)
.tasklet(testTasklet, transactionManager)
.build();
}
public Tasklet testTasklet() {
return (contribution, chunkContext) -> {
// ...
testService.execute(); // @Transactional(timeout=10) 이 먹히지 않는 서비스
return RepeatStatus.FINISHED;
};
}
스프링 배치의 Step과 Tasklet의 정확한 동작 원리는 아직 잘 모르겠지만 Step에서 설정한 TransactionManager에 의해 Tasklet에서 트랜잭션이 시작될 것으로 보인다. Tasklet의 트랜잭션은 timeout은 default(-1)로 설정되어 있고 testService의 @Transactional 어노테이션은 전파 속성이 Required이기 때문에 Tasklet에서 시작된 트랜잭션에 참여하도록 동작할 것이다. 이와 같은 이유로 @Transactional의 timeout 속성이 적용되지 않았을 것 같다.
이와 같은 문제를 해결하기 위해 주입하는 TransactionManager에 timeout 값을 아래와 같이 설정해주었다.
@Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(entityManagerFactory);
jpaTransactionManager.setDefaultTimeout(20);
return jpaTransactionManager;
}
자 다시 디버깅 모드로 트랜잭션 정보를 살펴보면 정상적으로 timeout 값이 설정된 것을 확인할 수 있다.
4. 배운 점
- 스프링 배치에서 트랜잭션을 주입하여 사용하기 때문에 @Transaction 어노테이션의 설정이 적용되지 않을 수 있다.
- JpaTransactionManager의 Default Timeout은 -1로 앞의 트랜잭션으로 인해 대기해야 하는 상황이 발생하는 경우 예외가 발생한다.
5. 출처
'TIL(Today I Learned)' 카테고리의 다른 글
[Today I Learned - 13] JpaPagingItemReader Limit, Offset not working - (1) (0) | 2024.01.31 |
---|---|
[Today I Learned - 12] Spring Batch의 Tasklet Transaction (0) | 2024.01.25 |
[Today I Learned - 10] 400 Bad Request With Spring (1) | 2024.01.22 |
[Today I Learned - 9] MySQL 조인 관계에서 락 범위 (0) | 2024.01.17 |
[Today I Learned - 8] Spring Batch에서 Step간 데이터 공유 (0) | 2024.01.16 |