아빠는 개발자

[java] 멀티스레드 최적화 (java application batch) - 이론편 본문

Java

[java] 멀티스레드 최적화 (java application batch) - 이론편

father6019 2024. 9. 21. 22:05
728x90
반응형

멀쩡하던 배치가 느려졌다. 

것도.. 한 5-6배? ㅋㅋㅋㅋ 어처구니가 없는 .. 색인 쿼리중 참조 하는 테이블의 데이터가 변해서 맛이 간거 같은게.. 예상이지만..

쿼리를 이것저것 돌려봐도 그렇게 느리진 않았는데.. 

 

뭐 암튼..

 

성능개선의 하나의 방법으로 병렬처리 성능을 올려보자..

 

현재 우리회사의 배치는 

8core 에서 2개의 스레드로 실행되고 전체,증분 색인 두개가 동시에 돈다면 2개의 스레드를 사용하는 어플리케이션이 2개가 실행된다.

그럼 max 를 4까지 올려봐도...

 

1. 시스템 자원 파악

먼저, 서버나 시스템의 전체 CPU 코어 수, 메모리와 같은 자원을 파악해야 합니다. 각 애플리케이션이 실행되는 환경에서 사용 가능한 자원의 양을 기준으로 적절한 스레드 수를 할당해야 합니다.

int availableCores = Runtime.getRuntime().availableProcessors();

 

 

2. 각 애플리케이션별 스레드 수 할당

모든 애플리케이션이 동시에 실행될 경우, 시스템의 전체 자원에 비례하여 각 애플리케이션에 적절한 스레드 수를 할당하는 것이 중요합니다. 예를 들어, 8개의 CPU 코어를 가진 시스템에서 4개의 애플리케이션이 실행된다면, 각 애플리케이션에 2개의 스레드를 할당하는 것이 기본적인 전략이 될 수 있습니다.

하지만, 각 애플리케이션의 성격에 따라 다르게 할당할 수 있습니다. 예를 들어:

  • CPU 바운드(CPU Bound) 작업이 많은 애플리케이션은 CPU 코어 수에 맞춰 스레드 수를 제한해야 합니다.
  • IO 바운드(IO Bound) 작업이 많은 애플리케이션은 CPU 코어보다 약간 더 많은 스레드를 사용할 수 있습니다.
// 각 애플리케이션별 CPU 코어 나누기
int coresPerApp = availableCores / numberOfApplications;

// 각 애플리케이션에 최적의 스레드풀 크기 설정
ExecutorService executor = Executors.newFixedThreadPool(coresPerApp);

 

3. 스레드풀에 대한 공통 고려사항

  • 스레드 수 제한: 애플리케이션이 무작정 많은 스레드를 생성하지 않도록, 고정된 스레드풀을 사용하여 자원 낭비를 방지합니다.
  • Thread Factory 사용: 스레드 풀의 동작을 제어하거나 모니터링하기 위해 ThreadFactory를 사용해 커스텀 스레드를 만들 수 있습니다. 스레드 네이밍이나 우선순위를 조정할 수 있습니다.
ExecutorService executor = Executors.newFixedThreadPool(coresPerApp, new ThreadFactory() {
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        thread.setName("CustomThread-" + thread.getId());
        return thread;
    }
});

 

 

 

 

ThreadPoolTaskExecutor는 스프링 프레임워크에서 자주 사용하는 스레드 풀로, 비동기 작업을 처리하는 데 자주 활용됩니다. ThreadPoolTaskExecutor는 ExecutorService와 비슷한 역할을 하지만, 스프링 환경에 맞게 좀 더 많은 설정을 제공합니다.

주요 설정으로는 **최대 스레드 수 (max thread)**와 **코어 스레드 수 (core thread count)**를 관리할 수 있으며, 이 두 가지 설정을 적절히 구성하여 최적화된 스레드풀을 구성할 수 있습니다.

주요 설정 항목

  1. Core Pool Size (코어 스레드 수):
    • 스레드풀에서 기본적으로 유지되는 최소 스레드 수를 정의합니다.
    • 요청이 발생할 때, 기본적으로 이 수만큼의 스레드가 항상 생성되어 대기합니다.
    • 요청이 많아지면 추가로 스레드가 생성될 수 있지만, 생성된 스레드가 core pool size 이하라면 이 스레드들은 작업이 끝나도 사라지지 않고 계속 유지됩니다.
     
    executor.setCorePoolSize(5);
  2. Max Pool Size (최대 스레드 수):
    • 스레드풀이 생성할 수 있는 최대 스레드 수를 정의합니다.
    • 작업 요청이 코어 스레드 수를 초과하면, 스레드풀은 max pool size까지 추가 스레드를 생성합니다.
    • 그러나 이 값을 초과하면 더 이상 스레드를 생성하지 않고, 작업이 큐에 쌓이거나 거부 정책에 따라 처리됩니다.
     
    executor.setMaxPoolSize(10);
  3. Queue Capacity (큐 용량):
    • 코어 스레드 수만큼 스레드가 사용 중일 때, 추가 작업을 처리하기 위해 대기할 수 있는 큐의 크기를 정의합니다.
    • 큐 용량이 다 차면, 새로운 작업은 추가로 생성된 스레드에 의해 처리되며, 스레드가 최대치에 도달하면 작업을 거부할 수 있습니다.
     
    executor.setQueueCapacity(25);
  4. Keep Alive Time (스레드 유지 시간):
    • max pool size까지 확장된 스레드가 일정 시간 동안 아무 작업도 하지 않으면, 그 스레드를 종료시키는 시간을 설정합니다.
    • 기본적으로 코어 스레드들은 이 설정과 상관없이 계속 유지되지만, 코어 스레드를 초과하는 스레드는 이 시간이 지나면 제거됩니다.
     
    executor.setKeepAliveSeconds(60);
  5. RejectedExecutionHandler (거부 정책):
    • 큐가 가득 차고 max pool size에도 도달했을 때, 새로운 작업을 어떻게 처리할지 결정하는 정책입니다.
    • 기본적으로 AbortPolicy가 설정되어 있으며, 이는 예외를 던집니다. 다른 옵션으로는 CallerRunsPolicy (요청한 쓰레드에서 실행), DiscardPolicy (무시), DiscardOldestPolicy (가장 오래된 작업을 삭제)가 있습니다.
     
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
     
     
     

작동 방식

  1. 코어 스레드 수 이하의 요청: 작업 요청이 코어 스레드 수(예: 5) 이하이면, 작업은 즉시 처리됩니다.
  2. 코어 스레드 수 초과, 큐 여유 있음: 작업 요청이 코어 스레드 수를 초과하더라도 큐 용량(예: 25)이 남아 있다면, 요청은 큐에 대기합니다.
  3. 코어 스레드 초과, 큐 꽉 참: 큐가 가득 차면, 스레드풀은 최대 스레드 수(예: 10)까지 추가 스레드를 생성하여 작업을 처리합니다.
  4. 최대 스레드 초과: 최대 스레드 수와 큐가 가득 찬 경우에는 RejectedExecutionHandler에 따라 거부 정책을 적용합니다.

최적화 방법

  • 코어 스레드 수: CPU 바운드 작업의 경우, CPU 코어 수에 맞게 설정하는 것이 좋습니다. IO 바운드 작업이 많은 경우에는 더 많은 스레드를 허용할 수 있습니다.
  • 큐 용량: 큐 용량을 너무 작게 설정하면 스레드풀의 최대 스레드 수에 빨리 도달할 수 있으며, 너무 크게 설정하면 응답성이 저하될 수 있습니다.
  • 최대 스레드 수: 시스템 자원(CPU, 메모리)을 고려하여 적절히 설정합니다. 너무 많은 스레드를 허용하면 성능 저하가 발생할 수 있습니다.

이러한 설정을 통해 애플리케이션의 비동기 작업을 효율적으로 처리할 수 있으며, 스레드풀의 구성은 작업의 특성과 시스템의 리소스 상황에 맞게 적절히 튜닝해야 합니다.

 

Yahoo 에서 협찬 받은 데이터 38,773개를 조회해서 파일로 쓰는 로직으로 테스트 해보자

728x90
반응형