티스토리 뷰

Infra

Jenkins 성능 개선 part1 - 캐싱 적용

지마켓 강대현 2023. 7. 27. 11:10

안녕하세요, 저는 Dev Platform 팀에서 사내 배포 시스템을 담당하고 있는 강대현입니다.

이번 포스팅에서는 Jenkins를 이용하고 있는 배포 시스템 개선 경험을 공유하려고 합니다.

Jenkins는 Java 기반의 오픈 소스 도구로, 지속적 통합(Continuous Integration, CI)과 지속적 배포(Continuous Delivery, CD) 구축에 사용됩니다.

 

배경

이번 주제는 Continuous Delivery(이하 CD) 과정에 초점을 둔 배포 시스템의 이야기입니다.
사용 중인 사내 배포 시스템(이하 배포툴)은 직접 개발한 서비스와 오픈 소스 도구인 Jenkins를 이용하여 CD 파이프라인을 구성하고 있습니다. 배포툴은 웹 UI를 통해 배포 생성 및 승인과 같은 사용자 입력을 처리하며, 실제 파일 복사 작업은 Jenkins에서 수행됩니다.

배포 작업이 몰릴 경우 Jenkins에 병목 현상이 발생하여 시스템이 다운되는 문제가 있었는데요, 이를 개선하기 위해 시도했던 내용 중 두 가지 내용을 공유하고자 합니다.

Part1에서는 캐싱 관련 내용을 다루고, Part2에서는 빌드 우선순위 조절에 대한 내용을 다룰 예정입니다.
(이 글을 통해 Upstream-Downstream 구조를 가진 jenkins 서비스에 캐싱을 적용하는 방법과 이를 통해 얻을 수 있는 장점에 대해 알 수 있습니다.)

 

 

기존 서비스의 구조와 문제점

먼저 CD 파이프라인을 간략히 표현하면 아래와 같습니다. 배포툴에서 배포를 생성하면 빌드와 파일 복사 작업이 타겟 서버 수만큼 수행됩니다.

빌드 : Jenkins에서 실행되는 작업의 단위입니다.
타겟 서버 : 배포 과정에서 배포 파일이 최종적으로 배치되는 서버를 의미합니다.

CD pipeline의 간략한 구조

이때, 배포가 몰릴 경우 Jenkins에 병목 현상이 발생해 전체 시스템이 느려지거나 다운되는 문제가 종종 발생했습니다.

 

Upstream과 Downstream

구조를 좀 더 자세히 살펴보겠습니다. Jenkins에서의 작업은 크게 두 부분으로 나뉩니다. Upstream Job(이하 Upstream)은 기본적인 체크와 파일 복사 등을 수행하며, Downstream Job(이하 Downstream)은 배포할 파일의 다운로드를 담당합니다. 따라서 타겟 서버 하나당 두 개의 빌드가 실행되고 있는 셈입니다.

Upstream과 Downstream

개발 용어로 비유하자면 Job = Class, Build = Instance 정도로 비유할 수 있습니다.

 

Upstream은 배포 파일 다운로드 경로에 파일이 존재하는지 체크하고, 존재하지 않을 경우에만 Downstream을 호출합니다. Downstream은 실제로 배포 파일을 다운로드하는 역할을 수행합니다.
하지만 Upstream 빌드는 병렬적으로 실행되고, 각 빌드들은 파일 체크 시점에서 첫 번째 다운로드가 완료되지 않았기 때문에 모든 빌드가 파일이 없다고 판단하게 됩니다. 따라서 모든 빌드들이 Downstream을 실행하게 되어 중복 다운로드가 발생하고, 이는 Jenkins에 부하를 주고 병목 현상을 야기합니다.

기존 구조의 문제점

 

캐싱을 적용한 개선 방안

Downstream이 한 번만 실행되도록 하기 위해서는 빌드가 순차적으로 실행되어야 합니다. 이를 위해 "Throttle Concurrent Builds" 라는 플러그인을 사용할 수 있습니다. 해당 플러그인을 설치한 후 Job의 Configure 메뉴에서 "Throttle" 값을 1로 설정하면 빌드를 순차적으로 동작시킬 수 있습니다.

 

Throttle 설정

"Maximum Total Concurrent Builds"는 전체 노드에서 실행할 수 있는 최대 빌드 수를 의미합니다.
"Maximum Concurrent Builds Per Node"는 노드당 실행할 수 있는 최대 빌드 수를 의미합니다.

 

그러나 이 설정은 여전히 문제점을 가지고 있습니다. 파일 복사와 같이 병렬적으로 수행해도 되는 작업까지 순차적으로 수행하면서 불필요한 대기 시간이 발생하게 됩니다.

여전히 존재하는 문제점

 

최종 개선 방안

최대한의 효율을 끌어내기 위해 필요한 부분만 순차적으로 수행되도록 Throttle 설정과 파일 체크 로직을 Upstream에서 Downstream으로 이동시킵니다. 이렇게 설정하면 Downstream은 순차적으로 수행되며 동시에 스스로 파일 체크를 하는 구조가 되기 때문에 두 번째 실행되는 Downstream부턴 다운로드한 파일이 존재하여 더 이상 다운로드하지 않게 됩니다.

또한, Upstream에서 Downstream이 종료된 후에 수행하는 파일 복사 작업은 병렬적으로 수행됩니다.

개선된 구조

 

결과

이제 Upstream 빌드는 병렬적으로 실행되면서 Downstream은 순차적으로 실행되어 캐싱이 정상적으로 동작합니다.

 

병렬적으로 실행되는 Upstream 빌드

 

아래는 Downstream의 빌드 시간을 비교한 이미지입니다. 첫 번째 Downstream 빌드는 1분 2초가 소요되었지만, 두 번째 Downstream 빌드는 이미 파일이 존재하기 때문에 다운로드 과정을 생략하여 1초 내에 완료되었습니다.

 

DownStream 수행 시간 비교

 

타겟 서버가 50개인 시스템을 가정하여 소요시간을 계산해 보면 아래와 같습니다.

  변경 전 변경 후
소요 시간 3100초 (1분 2초 * 서버 50대) 111초 (1분 2초 * 서버 1대 + (1초 * 서버 49대))

실제로 운영 중인 시스템 중엔 타겟 서버가 100개가 넘어가는 시스템들도 있습니다.

 

마치며

본 글에서는 Upstream-Downstream 구조에 캐싱을 적용하여 병목 현상을 개선하는 방법을 소개했습니다.
작업 전에 이미 파일 체크 로직이 존재하여 캐싱이 적용되어 있는 것으로 오해했었는데요, 아마도 시간이 흐르면서 구조와 설정이 변경되어 정상적으로 동작하지 않게 된 것 같습니다. 다시 한번 영원히 완벽한 구조는 존재하기 어렵다는 것을 느끼게 된 작업이었습니다.

 

part2에서는 빌드 우선순위 조절을 통해 병목 현상을 개선한 경험을 공유할 예정입니다.

긴 글 읽어주셔서 감사합니다.

댓글