개인프로젝트를 하면서 엑셀파일읽기 관련한 처리를 한 적이 있는데, 마침 실무에서도 관련한 건이 있었다.
이전에 할 당시에는 단순히 FileInputStream을 사용하여 처리하던 부분을 BufferedStream을 사용하여 메모리 효율 및 성능을 개선하였다.
그런데 더 알아보니까 멀티스레드, 비동기처리를 통해 개선할 수 있다는 것을 알게되었다.
지금까지 멀티스레드나 비동기처리는 애플리케이션 전체적으로, 다수의 유저가 애플리케이션을 실행할때 성능을 개선할 수 있는 방안으로 생각하였으나 특정 하나의 기능, 특히 I/O 관련한 (국소적인) 성능개선을 위해 생각할 수 있는 방안이었다는 것을 알게 되었다.
이 방안을 실무에 적용하고 이후에도 확장성있게 활용하기 위해 이 글을 기록한다.
일단, 멀티스레드와 비동기 처리에 대한 개념은
단일 스레드 - 동기처리, 단일 스레드 - 비동기처리...여러가지 작업 방법이 있는데
가장 핵심은 멀티 스레드 - 비동기처리이다.
중요한 것은 어찌되었든 주어진 일을 모두 끝낸다는 점이고, 중간 중간 일처리를 서로 공유한다던가 침범을 한다는 위험성을 제거해주고 자원 활용률이 많이 늘어난다는 단점을 해결해야 한다.
무엇보다, 모던 자바 인 액션에서는 멀티스레드를 왜 사용하냐고 할 정도로 부정적으로 평가하였는데, 일단 적용해보고 판단해보는게 좋을 것 같다.
일단, 멀티스레드의 자원소비율을 높인다는 단점을 제거하기 위해 스레드풀이라는 개념을 사용하였다.
멀티스레드를 지원해주는 라이브러리가 여러가지 있는데, 이 중 ExecutorService 내용이 있었고 이해가 쉬워서 한번 적용해보려고 한다.
특히 다수의 작업을 지휘할 수 있는 중앙처리장치가 있는, 개발자가 작업의 삭제 및 생성 등의 고려사항을 신경쓰지 않도록 하는 편리한 인터페이스라 하여 유심히 알아보았다.
엑셀파일을 읽어오고 파싱을 하는 부분을 멀티스레드로 처리한다면,
public List<JobRecommendResponse> recommendJobWithResume(InputStream pdf) throws IOException, CsvException {
/*
* NAS로부터 엑셀파일을 읽어온다.
*/
Resource[] resources = resourcePatternResolver.getResources("classpath:_skillspr/*.csv");
List<JobRecommendResponse> results = new ArrayList<>();
/*
* ExecutorService를 통해 스레드풀 및 스레드 생성
*/
ExecutorService executorService = Executors.newFixedThreadPool(resources.length);
/*
* 스레드풀에 스레드를 제출하고, 그 결과값을 future 객체를 반환받는다.
* 이 결과를 futures에 저장하며, 멀티스레드이므로 다건의 결과 저장을 위해 List 변수 초기화.
*/
List<Future<JobRecommendResponse>> futures = new ArrayList<>();
// csv 파일 순회
for (Resource resource : resources) {
Future<JobRecommendResponse> future = executorService.submit(() -> {
List<String[]> records;
try (CSVReader csvReader = new CSVReader(new InputStreamReader(resource.getInputStream()))) {
records = csvReader.readAll();
} catch (Exception e) {
log.info("멀티 스레딩 작업 중 에러");
throw new CustomException(ErrorCode.MULTI_THREADING_ERROR);
}
return new JobRecommendResponse(resource.getFilename().substring(0, resource.getFilename().length() - 4), pr);
});
//futures 리스트에 future 객체 반환값(결과값) 저장
futures.add(future);
}
이때 스레드풀에서 한 스레드가 처리를 완료할때까지 대기하며, 완료 시 결과를 future로 반환하고 우리는 futureList에 이를 저장하여 완료시점을 알 수 있다.
JAVA 8부터는 CompletableFuture를 사용하며, 기존 future 사용 시 블로킹처리하므로 비동기 의미가 없어 사용을 거의 안한다고 한다.
비동기처리 시 @Async를 주입받아 사용하며, 보니까 async를 사용할 경우 기본적으로 멀티스레드 및 이를 관리할 스레드풀을 사용할 것이라는 전제가 있는 것처럼 보인다.
SimpleAsyncTaskExecutor 기본설정은 스레드를 계속 생성하여 스레드풀 관리가 일어나지 않으므로 사용자정의를 통해 스레드풀을 만들어야 한다.
@Configuration
@EnableAsync
public class AsyncConfig {
private static int CORE_POOL_SIZE = 500; // 동시에 실행할 쓰레드의 갯수를 의미, default 값은 1이다.
private static int MAX_POOL_SIZE = 3000; // 쓰레드 풀의 최대 크기를 지정, default 값은 Integer.MAX_VALUE
private static int QUEUE_CAPACITY = 5000; // 큐의 크기를 지정, default 값은 Integer.MAX_VALUE 이다.
private static String THREAD_NAME_PREFIX = "async-task";
@Bean
public Executor asyncTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
executor.initialize();
return executor;
}
}
이 사용자 정의한 비동기 DI를 주입한 메서드가 있다면,
@Slf4j
@Service
public class UserService {
@Async("async-task")
public void hello(){
try {
log.info("비동기 처리를 수행합니다.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
위 비동기 메서드를 호출한다면 이 동작의 완전한 처리를 기다리지 않고, 다음 로직을 바로 진행한다(논블로킹, 비동기처리).
핵심은 "비동기처리한 메서드를 호출하는 것, 이를 위해 스레드풀 환경상태를 구성해주고 멀티스레드 처리를 사용하는 것.
List<CompletableFuture<JobRecommendResponse>> futures = new ArrayList<>();
// csv 파일 순회
for (Resource resource : resources) {
CompletableFuture<JobRecommendResponse> future = <엑셀파일처리 비동기 메서드>
futures.add(future);
}
// 완료될 때까지 대기
for (CompletableFuture<JobRecommendResponse> future : futures) {
try {
results.add(future.join());
} catch (Exception e) {
log.info("멀티 스레딩 작업 중 에러");
throw new CustomException(ErrorCode.MULTI_THREADING_ERROR);
}
}
실무에 적용을 해보도록 한다. 적용이 힘들다면 일전에 만든 엑셀 다운로드 관련 개인프로젝트 소스를 이용하여 적용해보자.
그리고 성능분석을 위해 visualvm을 사용해보자.
async, visualvm - https://heowc.tistory.com/68
bufferedstream - https://nidelva.tistory.com/12
비동기 / 멀티스레드 개념 이해 - https://te-ho.tistory.com/82
비동기 멀티스레드 로직 - https://dgjinsu.tistory.com/30