• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

멀티스레드 환경에서의 트랜잭션 및 lock 관련 질문드립니다.

23.02.04 21:24 작성 조회수 1.08k

0

- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요!
- 먼저 유사한 질문이 있었는지 검색해보세요.
- 서로 예의를 지키며 존중하는 문화를 만들어가요.
- 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.

 

안녕하세요 강사님! 강의를 듣고 정말 많은 도움을 받고 있습니다! 멀티스레드 환경 부분을 듣고 직접 이것저것 해보면서 몇가지 질문 사항이 있습니다.

1) 트랜잭션

  • 스프링 배치에서 시작하는 트랜잭션 외에 ItemProcessor에서 다른 service(@Transactional)를 호출하여 데이터 처리를 하게 되는 경우 앞서서 배치가 생성해놓은 트랜잭션에 함께 포함되는 것이 아니라 별도로 트랜잭션이 열리게 된다는 다른 질문의 답변을 보았습니다! 그렇다면 배치의 트랜잭션이 끝나는 시점이 서비스 트랜잭션이 끝나는 시점과 다를텐데요. 이 때 배치의 트랜잭션이 종료되는 시점은 service처리가 다 이후 write처리까지 다 끝난 이후 인지, service 트랜잭션의 종료와는 상관 없이 트랜잭션이 종료 되게 되는 것인지 궁금합니다. + 트랜잭션의 종료 시점이 다르게 될 경우 발생될 수 있는 문제들은 어떻게 처리하게되는 걸까요?(배치를 돌릴때 최대한 다른 트랜잭션을 열면 안되는 것인지 궁금합니다!) ( org.hibernate.LazyInitializationException :  failed to lazily initialize a collection of role 예외, 트랜잭션을 닫을 시점에 entity manger is null (AbstractItemCountingItemStreamItemReader.close)예외가 발생하였는데 이것과 관련되어있는지도 궁금합니다...!)

2) 멀티스레드 환경의 chunk 배치

  • 멀티스레드 환경에서 chunk 배치를 사용하는 경우 JpaPagingItemReader를 사용한다고 가정하였을때, '스레드 동기화를 보장'한다는 것에 대해 궁금한데요. 예를 들어, 쓰레드가 5개인 환경에서 조회할 값이 isSample 컬럼이 false인 데이터 100개라고 가정하고 pagingItemReader를 이용해서 offset을 0으로 두고 10개씩 (chunksize = 10, pageSize = 10) 읽고 processor가 isSample 컬럼을 true로 바꾸는 역할을 수행하는 것을 가정합니다. 먼저 1~10번의 아이템을 각각의 1~5번의 스레드가 10개의 각각의 아이템을 할당 받는 과정에서 동기화(중복된 아이템을 스레드가 겹쳐서 읽지 않음)를 이루고 처리후 다음 10개를 읽고 처리하는 과정을 반복한다고 이해하였는데요. 이때, 만약 처음 1~10번의 아이템을 읽고 처리하는 과정에서 한 스레드가 처리과정에서 오랜 시간이 소요되는 병목지점이 발생한다면 commit이 이루어지지 않고, 다음 아이템 11~20번을 읽을때 앞서서 처리되지 않은 아이템을 다시 읽어오고 또다시 병목 지점이 발생할 수도 있고, 결과적으로 같은 아이템을 다른 스레드가 중복으로 처리하게 될 수도 있을 텐데 이럴경우 어떻게 처리가 이루어지는지 궁금합니다. (이런 부분에서는 동기화가 이루어지지 않는 것인지, 제가 이해한 과정이 맞는지도 궁금합니다...!)

3) synchronizeditemstreamreader를 사용하는 경우 lock

  • SynchronizedItemStreamReader를 사용하는 경우에는 동기화를 위해서 쓰레드가 lock을 획득하고 처리하는 과정이 이루어진다면, DB connection의 갯수는 항상 쓰레드의 갯수보다 많게 유지해야 하는 걸까요? ( 작게하면 Connection is not available, request timed out after 오류가 뜨고 있습니다...!)

강의 정말 잘 보고 있습니다! 감사합니다 강사님!

 

답변 1

답변을 작성해보세요.

1

  1. 트랜잭션

    스프링 배치에서 트랜잭션은 기본적으로 생성되는 TransactionManager 가 관리하게 되는데 배치를 실행하는 시점 즉 JobLauncher.run() 으로 Job 을 실행하고 JobRepository 가 트랜잭션을 얻어서 처리를 할 때 만약 이미 활성화 된 트랜잭션이 존재 할 경우 오류를 내 뱉고 있습니다. 가령 다음과 같은 상황입니다.

    @Bean
    public ApplicationRunner applicationRunner(){
        return new ApplicationRunner() {
            @Override
            @Transactional
            public void run(ApplicationArguments args) throws Exception {
                JobParameters jobParameters = new JobParametersBuilder().addDate("date", new Date()).toJobParameters();
                jobLauncher.run(job,jobParameters);
            }
        };
    }

    위에 보시면 JobLauncher.run() 이 실행되기 전 이미 @Transactional 이 선언되어 트랜잭션이 활성화 된 상태이기 때문에 오류가 발생합니다.

    " Existing transaction detected in JobRepository. Please fix this and try again (e.g. remove @Transactional annotations from client)."

    그렇기 때문에 스프링 배치에서 트랜잭션은 외부에서 이미 생성된 별도의 트랜잭션이 존재할 경우 이를 기본적으로 허용하지 않고 있습니다. 특별한 이유가 아니면 스프링 자체 트랜잭션과 스프링 배치의 트랜잭션을 별도로 운용할 필요는 없습니다. 다만 옵션 설정을 통해 이 부분도 해결은 가능합니다.

    JobRepository 를 생성하는 초기화 과정에서 다음과 같이 커스텀하게 구현할 수 있습니다.

    @Bean
    protected JobRepository createJobRepository() throws Exception {
        JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
        ----
        ----        
        factory.setValidateTransactionState(false);
        return factory.getObject();
    }

    위에서 factory.setValidateTransactionState(false); 하시면 이미 존재하는 외부의 트랜잭션 활성화 여부를 체크하지 않습니다. 그러면 오류가 발생하지는 않지만 그 외 이슈가 발생할 수 있으니 신중하게 접근해야 합니다. 기본은 true 입니다. 참고하시면 될 것 같습니다.

    org.hibernate.LazyInitializationException 관련 문제는 Jpa 에서 세션이 종료된 상황에서 연관관계를 참조할 경우에 발생하는데 이 부분은 트랜잭션이 이미 종료된 상황이기 때문에 오히려 트랜잭션이 필요한 상황입니다. 이 경우도 여러가지 상황에서 발생할 수 있는 오류입니다. 어디에서 트랜잭션을 벗어나 있는지 체크해 보시기 바랍니다.

     

  2. 멀티스레드 환경의 chunk 배치

    멀티 스레드에서 데이터를 읽어올 때 스프링 배치에서 제공하는 JpaPagingItemReader 는 동시성의 이슈가 발생하지 않도록 동기화 하고 있습니다. 그렇기 때문에 어떤 스레드에서 병목현상이 발생한다 할지라도 다른 스레드가 대기 상태에 있기 때문에 데이터를 중복적으로 읽어 올 수는 없습니다. 다만 지속적인 병목현상으로 인해 DB 커넥션이 끊어진다거나 메모리 누수 등의 문제로 어플리케이션이 멈추거나 종료가 될 수 있습니다.

    이것을 해결하기 위해서는 스레드 5개가 각 아이템을 읽어올 때 스레드간 아이템이 겹쳐지지 않게 읽어오도록 구현하면 됩니다. 만약 A 스레드에서 병목 현상이 발생한다고 했을 때 B 스레드는 A 스레드의 아이템을 접근하지 않고 자신에게 할당한 아이템만 참조하도록 해야 합니다. 이 부분은 저의 강의에서 소개하고 있으니 참고해 주시기 바랍니다.

     

  3. synchronizeditemstreamreader를 사용하는 경우 lock

    보통은 DB Connection 은 pool 로 관리하기 때문에 스레드 갯수보다 커넥션 수가 많아야 된다는 기준은 없습니다. 스레드가 배치를 처리하는 시간보다 요청하는 스레드가 많을 경우 스레드가 대기를 타거나 DB 커넥션이 부족해서 위와 같이 오류를 발생할 수는 있습니다. 넓게 본다면 이부분은 스프링 배치의 문제라기 보다는 스레드와 DB 커넥션 풀 간의 운용에 관한 문제라 할 수 있습니다. 다만 스프링 배치에서 Synchronizeditemstreamreader 가 기능을 수행하기 위한 조건적인 측면에서 DB 커넥션 개수 기준을 특별하게 정했을 수도 있습니다. 저도 이 부분은 정확하게 테스트 해보지는 않았는데 한번 보도록 하겠습니다.

답변 감사합니다 강사님!! 참고하여 다시 테스트 진행해보도록 하겠습니다! 감사합니다!