3 분 소요

이번 포스팅은 신입개발자로써 멀티스레딩을 회사에 적용한 경험에 대한 포스팅입니다.
(21년 9월에 배정받은 업무입니다.)
당시 개발할때 발표한 발표자료와 회사코드에 주석을 작성했던것이 큰 도움이 되었네요.

해결 해야하는 문제

자사서비스에서 동영상을 처리할때 외부 비디오 서비스를 사용하였습니다.
해당 비디오 서비스API는 영상 등록의 경우 API응답까지 대기시간이 꽤 있었습니다.
(영상을 InputStream으로 변환 -> InputStream으로 영상 등록 API호출 -> 응답대기후 응답에 따라 처리)
평소엔 별 문제가 없었지만 순간적으로 많은 영상을 등록 처리해야 할때 문제가 발생하였습니다.
이럴경우 첫번째 영상등록 API호출 -> 두번째 영상등록 API호출 -> 세번째 ··· 같이 처리되어(동기처리) 뒤로 밀린 영상들의 경우 영상등록이 너무 늦어지는 현상이 생겼습니다.
또한 해당 비디오 서비스에서 하나의 계정에서 영상변환을 최대 3개까지만 동시에 처리하였습니다.
즉 영상이 더 많이 올 경우를 대비해 여러 계정에 순차적으로 영상을 병렬처리하여 등록 해야했습니다.

요구사항 분석 및 해결 방안

핵심적인 요구사항은 다음과 같습니다.

  • 영상 등록 API호출 후 응답대기시간을 줄여야한다.
    • 영상 호출 자체를 병렬적으로 처리하여 동시에 등록 후 응답을 대기하였습니다.
    • 병렬처리를 위해 멀티스레딩을 적용하였었습니다.
    • 영상의 응답을 DB업데이트 이후 서버에서 사용하지 않기때문에 응답을 받은 쓰레드에서 직접 DB에 업데이트 합니다.
  • 들어오는 영상을 여러 계정에 하나씩 처리해야합니다.

    A계정토큰, B계정토큰, C계정토큰이 있다고 가정합니다.
    등록해야 될 영상이 10개 일경우 토큰을 10번 가져와 영상등록을 합니다.
    각 영상등록시 호출 순서에 따라 계정 토큰들이 발급됩니다.
    ex 10개일시 A,B,C,A,B,C,A,B,C,A 순서로 토큰을 발급받습니다.
    그 후 다음 영상 등록요청이 3개가 왔습니다.
    다시 토큰을 가져와 영상을 등록합니다. 이경우 B, C, A의 순서로 토큰이 발급되어야합니다.
    즉 애플리케이션서버가 종료되기 전까진 영상의 순서에따라 토큰을 발급합니다.

    • 외부 비디오서비스 계정정보를 관리하는 싱글턴객체를 만들었습니다.(이하 TokenInjector)
    • 해당 싱글턴객체에서 메서드(이하 getToken())를 이용하여 계정정보를 가져옵니디.
    • TokenInjector에서 영상이 호출될때 영상호출에 대한 횟수를 카운트하여 계정토큰enum에서 순서를 계산하여 가져왔습니다.
      (지금와서 생각해보면 원형큐 자료구조를 이용하면 훨씬 더 문제를 쉽게 해결 할 수 있어보이네요.)
    • 프로젝트의 DB설계상 상품의 이미지와 영상을 같은 테이블에서 관리하였기때문에 DB를 이용하여 영상의 순서를 파악하는것은 비효율적이라 생각하였습니다.

멀티스레딩 적용기

자바의 멀티스레딩과 @Async
멀티스레딩 적용을 위한 문법과 어떻게 적용하였는지는 위 글에서 포스팅하였습니다.

프로젝트에 멀티스레딩을 적용하며 신경 쓴 점은 크게 3가지 입니다.

  1. 계정정보에 대한 접근이 동시성문제에서 안정적이어야 하며 순차적으로 계정정보를 불러올수있어야 한다.
    • TokenInjector에서 영상호출순서를 체크하는 필드를 AtomicInteger타입을 사용하여 토큰 발급시 순서계산(조회, 증가)에 대한 동시성문제를 해결하였습니다.
  2. 영상 등록후 응답에 따라 해당 스레드에서 DB업데이트를 한다.
    • 영상에 대한 PK가 있기때문에 스레드에서 바로 업데이트처리 하여도 무방하다고 생각하였습니다. (ps. 아직 등록 전이라 해당 데이터를 다른 로직에서 사용하지 않는 상태)
    • DB접근으로는 MyBatis를 사용하였으며 기술이사님께 질문하여 현 프로젝트에서는 MyBatis의 SqlSessionTemplate의 부분을 설정하여 스레드 세이프하게 사용가능을 확인하였습니다.
      (아직 맡은 업무를 JPA로 전환하기 전 개발일기입니다.)
  3. 추후 멀티 스레딩을 활용 해야할 경우를 위해 @Async로 멀티 스레딩을 이용한다.
    • ThreadPoolTaskExecutor를 이용하여 매번 멀티스레딩을 할 수 있습니다.
      그러나 추후 멀티스레딩을 활용해야할때 관련 로직들을 매번 구현해줘야합니다.
      횡단관심사로 묶어 AOP를 이용하여 개발하여 이후에 멀티 스레딩이 필요할시 어노테이션으로 간편하게 적용 할 수 있길 원하였습니다.
      (핵심 비즈니스 로직과 멀티스레딩 로직이 분리되서 얻는 이점도 많죠)
    • @Async 설정은 @Bean등록 방식을 선택하였습니다.
      AsyncConfigurer를 상속받아 설정하는 것 보다 Bean등록 방식이 Executor의 ThreadNamePrefix, 코어갯수, 종료시간 등 세부 설정별로 Bean으로 등록하여 사용하기 용이하기때문입니다.

적용 후 문제

@Async설정까지 다 완료 된 이후 테스트를 진행하였습니다.
별 문제없이 작동 될줄 알았던 API호출이 동기적으로 호출 되는것이 확인되었습니다.
이유는 @Async에 있었습니다. AOP를 이용하여 동작하는 만큼 AOP의 한계인 자가호출에서 자유로울수 없었습니다.
영상을 API호출 후 응답을 확인하고 DB에 업데이트하는 메서드가 포함되어 있어서 생기는 문제였습니다.

    // 대략 이런 느낌이라 보시면됩니다.
    @Async
    public void videoConvert(Video video){
        ReponseEntity response = httpUtil.post(url, token); <- 영상 등록
        updateVideo(convertResponse(response)); <- 자가 호출
    }
    
    public Video convertResponse(ReponseEntity response, Video video){
        // 응답을 Video객체에 맞게 변환후 video 객체에 데이터 셋팅 
        return video;
    }
    
    public void updateVideo(Video video){
        videoMapper.update(video)
    }

문제점 해결? (자가호출 회피)

한 블로그와 스택오버 플로우에서 해결법을 찾았습니다.
AsyncService를 하나 만들고 해당 서비스는 Runnable를 파라미터로 받아 실행만 해주는 메서드를 선언하여 비동기로 실행돼야 할 메서드를 파라미터로 넘겨서 호출합니다.

@Service
public class AsyncService {
    
    @Async("videoAsyncExecutor")
    public void videoAsyncRun(Runnable runnable){
        runnable.run();
    }

}

@Service
@RequiredArgsConstructor
public class videoService {
    private final AsyncService asyncService;
  
    // asyncService를 이용하여 메서드에 비동기처리를 합니다.
    public void runVideoConvert(Video video){
        asyncService.vimeoAsyncRun(() -> this.videoConvert(video));
    }
    
    // 비동기 적용 메서드
    public void videoConvert(Video video){
      // 비디오 변환로직 및 API 호출로직
    }
}

후기

업무직후 느낀점은 나름 복잡하게 주어진 문제를 잘 해결한거 같아 기쁜? 마음이 있었습니다.

하지만 지금와서 이 개발기를 쓰며 느낀점은 몇가지 비효율적인 문제도 보이고 하네요.
특히 계정토큰을 인덱스로 계산하는 부분이 참 아쉽네요.
최근들어 많이 생각하는 부분이 개발자로써의 터널시야을 부술려는 노력이 필요하다는 생각을 많이합니다.
멀티스레딩으로 개발당시 한창 디자인패턴에 대해서 공부를 할 때라 모든 문제를 디자인패턴에 대입하여 풀려하고 자료구조를 배울때에는 모든 문제를 자료구조로, 아키텍쳐를 공부할때는 아키텍처적으로 생각이 나는 현상이 있습니다.
좋은 개발자가 될려면 이런 부분에 자유로워져야겠다는 생각이 드는 포스팅이었습니다.

댓글남기기