서론
젠킨스에 Executor 를 몇개를 사용하는게 최적일까요?
조사를 하며 배운 점을 정리해보겠습니다.
뭔놈의 쓰레ㄱ... 아니 쓰레드가 이렇게 많은지
각 쓰레드의 의미, 역할을 Jenkins Executor 을 예제 삼아서 완벽하게 파악해보기!
완벽하게 파악하기 위해
다같이 숨참고 JAVA 다이브!
Java 동시처리에서 구분할 개념
Concurrency와 Parallelism:
Concurrency (동시성): 여러 작업을 번갈아가며 실행하는 것. 실제로 동시에 실행되지 않더라도, 사용자 입장에서는 동시에 실행되는 것처럼 보입니다.
Parallelism (병렬성): 여러 작업을 실제로 동시에 실행하는 것. 이는 멀티코어 CPU에서 가능합니다.
따라서 사람이 보기엔 같아보이지만, 실제로 어떻게 동작하는지는 구분이 필요합니다.
잠깐만, 가장 흔히 아는 Spring Boot Application 에서 동시처리
보통 CPU 는 적게는 수개, 많게는 수십개인데,
Spring Boot 서버에서는 수백개의 요청을 동시에 후루룩 처리합니다.
이게 어떻게 가능한지 궁금해져서 이것도 찾아봤습니다.
Spring Boot 애플리케이션이 CPU 코어 수보다 훨씬 많은 요청을 동시에 처리할 수 있는 이유는 다음과 같습니다:
a) 비동기 처리: Spring Boot는 비동기 프로그래밍 모델을 지원합니다. 이를 통해 I/O 작업 중 대기 시간을 효율적으로 활용할 수 있습니다.
b) 스레드 풀: 애플리케이션 서버(예: Tomcat)는 스레드 풀을 사용하여 요청을 처리합니다. 이를 통해 제한된 수의 스레드로 많은 요청을 관리할 수
있습니다.
c) 컨텍스트 스위칭: OS는 빠른 속도로 스레드 간 컨텍스트 스위칭을 수행하여, 적은 수의 CPU 코어로도 많은 작업을 처리할 수 있게 합니다.
d) I/O 바운드 작업: 대부분의 웹 요청은 I/O 바운드 작업이며, CPU를 지속적으로 사용하지 않습니다. 이로 인해 CPU가 다른 작업을 처리할 수 있는 여유가 생깁니다.
결론:
Spring Boot는 Java의 Native Thread (Kernel Thread)를 사용하여 병렬성을 확보하고, 동시에 비동기 프로그래밍 모델을 통해 동시성을 극대화합니다. 이를 통해 다수의 요청을 효율적으로 처리할 수 있습니다.
다시 본론인 Jenkins Executor
Jenkins Exector는 ExecutorService 클래스 내부에서 관리합니다.
ExecutorService에 대해 자세히 설명하고,
동시성과 병렬성 처리 여부를 확인한 후 Java의 스레드 관련 개념들과 Jenkins executor의 연관성을 설명해보죠.
- Jenkins executorService
Jenkins에서 executorService는 주로 java.util.concurrent.ExecutorService 인터페이스의 구현체를 사용합니다.
일반적으로 ThreadPoolExecutor 클래스를 기반으로 구현됩니다.
Jenkins의 실제 구현을 보면:
ExecutorService executorService = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>()
);
이 구현은 동시성과 병렬성을 모두 지원합니다:
- 동시성(Concurrency): 여러 작업을 번갈아가며 실행할 수 있습니다.
- 병렬성(Parallelism): 멀티코어 환경에서는 여러 작업을 동시에 실행할 수 있습니다.
따라서 Jenkins의 executor는 동시성과 병렬성을 모두 처리할 수 있습니다.
- Java의 Kernel Thread와 User Thread 할당 방식
Java는 기본적으로 1:1 모델을 사용합니다. 즉, 각 Java 스레드(User Thread)는 하나의 OS 스레드(Kernel Thread)에 매핑됩니다.
이 방식은 다음과 같은 특징이 있습니다:
- 직접적인 OS 자원 활용
- 높은 동시성 지원
- 스레드 간 전환 비용이 상대적으로 높음
- Green Thread와 Native Thread
- Green Thread:
- JVM에 의해 관리되는 사용자 수준 스레드
- OS 커널의 관여 없이 JVM이 스케줄링
- 현대 Java에서는 거의 사용되지 않음
- Native Thread:
- OS 커널에 의해 직접 관리되는 스레드
- 현대 Java에서 기본적으로 사용되는 방식
- OS의 스케줄링 알고리즘에 의존
- Jenkins Executor와의 연관관계
Jenkins executor는 Native Thread를 기반으로 동작합니다.
검증을 위해 Jenkins의 소스 코드에서 동시처리/병렬처리와 관련된 라이브러리 import 문을 발췌해보겠습니다.
Jenkins는 주로 java.util.concurrent 패키지의 클래스들을 사용합니다. 다음은 Jenkins 소스 코드에서 자주 볼 수 있는 import 문들입니다:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import hudson.util.DaemonThreadFactory;
import hudson.util.NamingThreadFactory;
import jenkins.util.InterceptingExecutorService;
import jenkins.util.TimerTask;
이러한 import 문들은 Jenkins의 여러 클래스에서 사용됩니다.
예를 들어, Jenkins의 핵심 클래스 중 하나인 hudson.model.Queue
에서는 다음과 같은 코드를 볼 수 있습니다:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import jenkins.util.InterceptingExecutorService;
public class Queue {
private final ExecutorService threadPoolForLoadingActivities;
Queue(LoadBalancer loadBalancer) {
this.threadPoolForLoadingActivities = new InterceptingExecutorService(
Executors.newCachedThreadPool(new NamingThreadFactory(new DaemonThreadFactory(), "Jenkins queue load-activities"))
);
// ... 나머지 코드
}
// ... 나머지 메소드들
}
또한 Jenkins의 ExecutorService
관련 설정을 담당하는 jenkins.util.SystemProperties
클래스에서는 다음과 같은 코드를 볼 수 있습니다:
import java.util.concurrent.TimeUnit;
public class SystemProperties {
public static int getExecutorServiceKeepAliveTime() {
return getInteger(Queue.class.getName() + ".executorServiceKeepAliveTime", (int)TimeUnit.MINUTES.toSeconds(1));
}
// ... 나머지 메소드들
}
이러한 코드들은 Jenkins가 Java의 concurrent 패키지를 활용하여 동시처리와 병렬처리를 구현하고 있음을 보여줍니다.
특히 ExecutorService
, ThreadPoolExecutor
, ScheduledExecutorService
등의 클래스들은 Jenkins의 작업 실행과 스케줄링에 핵심적인 역할을 합니다.
(이 정보는 Jenkins의 공개된 소스 코드를 바탕으로 한 것입니다. 실제 구현은 Jenkins의 버전에 따라 약간의 차이가 있을 수 있습니다.)
그리하여 Jenkins executor는 Native Thread를 기반으로 동작하는 것이 검증됐습니다.
이는 다음과 같은 의미를 갖습니다:
- 각 executor는 실제 OS 스레드에 매핑됩니다.
- 멀티코어 환경에서 실제 병렬 처리가 가능합니다.
- OS의 스레드 스케줄링을 직접 활용할 수 있습니다.
Jenkins는 executorService를 통해 효율적인 스레드 풀 관리를 수행합니다:
- 작업 큐를 사용하여 동시에 실행할 수 있는 작업 수를 제한합니다.
- 스레드 재사용을 통해 스레드 생성/소멸 비용을 줄입니다.
- 작업 부하에 따라 동적으로 스레드 수를 조절할 수 있습니다.
결론적으로, Jenkins의 executor 시스템은 Java의 Native Thread 모델을 기반으로 하여 효율적인 동시성 및 병렬성 처리를 제공합니다.
이를 통해 Jenkins는 여러 작업을 효과적으로 관리하고 실행할 수 있습니다.
5. I/O Bound 인가??? CPU Bound 인가???
젠킨스가 처리하는 작업의 유형이 또 중요하겠죠!!
저희 회사에서 현재 젠킨스를 활용하는 작업은 스프링 배치를 사용한 정산 작업, 즉 데이터베이스 작업입니다.
또한 많은 회사들이 스프링배치 프로젝트로 만든 배치 서비스를 젠킨스를 활용해 관리합니다.
데이터베이스의 작업은 I/O Bound 작업입니다.
아 잠깐 I/O Bound 와 CPU Bound 를 짧게 설명하겠습니다.
- CPU bound 작업:
- 주로 계산 집약적인 작업입니다.
- 예: 복잡한 수학 계산, 이미지 처리, 암호화, 압축 등
- 이러한 작업은 CPU 처리 능력에 의해 제한됩니다.
- CPU 사용률이 높고, I/O 대기 시간이 적습니다.
- I/O bound 작업:
- 주로 입출력 작업에 시간을 많이 소비하는 작업입니다.
- 예: 파일 읽기/쓰기, 네트워크 통신, 데이터베이스 쿼리 등
- 이러한 작업은 I/O 장치의 속도에 의해 제한됩니다.
- CPU 사용률이 낮고, I/O 대기 시간이 깁니다.
만약 젠킨스가 하는 일이 CPU Bound 작업이라면,
Native Thread 를 기반으로 하는 Executor 의 개수는 정확하게 CPU 코어 개수에 영향을 받습니다.
BUT !!!
I/O Bound 라고 하면 얘기가 달라집니다!
작업의 흐름 속에 Input 또는 Output 으로 맡겨진 상태일 땐 CPU가 대기상태가 됩니다.
또한 현대의 자바는 비동기 I/O 를 지원하기 때문에, 일처리를 말겨놓고 CPU 가 묶여있지 않습니다.
그럼 뽀로로 하는거죠! (노는게 제일좋아)
따라서 스프링 배치 스케줄러 젠킨스의 경우, Executor 를 더 늘려도 괜찮습니다.
예를 들어, 4개의 CPU 코어가 있는 시스템에서 20개의 I/O bound 작업을 실행한다고 가정해봅시다.
각 작업이 90% 의 시간을 I/O 대기에 사용한다면, 20개의 executor를 사용하더라도 CPU는 대부분의 시간 동안 20% 미만의 사용률을 보일 것입니다.
결론
Jenkins Executor 는 Native Thread 를 활용하므로 cpu core 개수의 영향을 받는 OS Thread 에 직접적으로 매핑됩니다.
그래서 CPU Bound 인 작업을 위주로 하는,
예를 들어 Gradle/Maven build, 코드 분석, 단위/통합 테스트, Docker 이미지 빌드(I/O 혼재)의 경우
Executor는 cpu core 의 개수 * 1.5 ~ 2배가 가장 적당합니다.
그리고 I/O Bound 인 작업을 위주로 하는,
예를 들어 Spring Batch, 헬스체크, 백업 및 마이그레이션 등 작업의 경우
Executor는 개수를 많이 늘려도 무관하며, 메모리 성능과 작업 성질 등을 고려해야 합니다.
정확히는 테스트를 통해 알아봐야 합니다.
끗!
'개발 > Linux & DevOps' 카테고리의 다른 글
젠킨스 build.xml 에 대한 고찰 (0) | 2024.11.26 |
---|---|
nginx, logrotate 운영 환경 세팅 (kill USR1, 파일디스크립터) (4) | 2024.07.22 |
aws 초청강의 - msa가 필요한 이유 (0) | 2023.03.24 |
도커 정보 초기화 (0) | 2023.02.28 |
jenkins user 유저 권한 없을 때 / 비밀번호 분실 / 비밀번호 찾기 (0) | 2022.12.15 |