CI가 느려져서, 돈으로 시간을 사기로 했다
AI로 생성한 개념 삽화입니다.
클라우드 비용을 줄이려고 시작했는데, 결국 조금 더 비싼 컴퓨터를 쓰는 쪽으로 마음이 기울고 있다. Myven의 CI 때문이다. 코드를 바꿀 때마다 자동으로 테스트를 돌려 문제가 없는지 확인하는 과정인데, 요즘은 그 결과를 기다리는 시간이 꽤 길어졌다.
기능이 늘면 테스트도 늘어난다. 테스트가 많아지는 건 좋은 일이지만, 코드를 고치고 결과를 확인하기까지의 간격도 함께 벌어진다. 한 번 기다리는 건 괜찮아도, 하루에 여러 번 반복하면 이야기가 달라진다.
처음에는 GitHub Actions의 실행 비용이 부담스러워서 가지고 있던 컴퓨터에 runner를 설치했다. runner는 CI 작업을 실제로 실행하는 프로그램이다. 이미 있는 장비를 활용하니 비용은 아낄 수 있었지만, 테스트가 길어지면서 이제는 시간 쪽이 병목이 됐다.
그래서 AWS 인스턴스에서 같은 작업을 돌려 봤다.
시간당 요금보다 한 번 끝내는 데 드는 비용
상시 운영하는 서버는 보통 최대 부하를 고려해 조금 여유 있게 잡는다. 갑자기 요청이 늘어도 버텨야 하니, 평소에는 자원이 남는 시간이 생긴다.
CI처럼 계산을 집중적으로 하고 끝나는 작업은 관점이 조금 다르다. 필요한 동안만 인스턴스를 사용하고 종료한다면, 시간당 요금이 높더라도 작업을 빨리 끝내는 쪽이 더 저렴할 수 있다.
Myven의 테스트 일부를 실행한 기록은 이랬다.
| 인스턴스 | 실행 시간 |
|---|---|
| C7g.xlarge | 7분 16초 |
| C8g.xlarge | 5분 30초 |
이 시간은 해당 테스트 작업의 환경 준비와 정리까지 포함한 실행 시간이다. Myven 전체 CI의 소요 시간이나 인스턴스의 보편적인 성능을 뜻하지는 않는다.
같은 작업이 1분 46초 빨리 끝났다. 실행 시간으로 보면 약 24% 단축이고, 같은 작업을 처리하는 속도로 환산하면 약 32% 향상이다.
여기서 흥미로웠던 건 비용이다. 내가 비교한 요금에서는 C8g.xlarge가 C7g.xlarge보다 시간당 약 10% 비쌌다. 이 차이를 적용해 단순 계산하면 다음과 같다.
1회 실행 비용 ≈ 시간당 요금 × 실행 시간
C7g의 실행 비용을 100으로 두면
C8g의 실행 비용 = 100 × 1.10 × (330초 ÷ 436초)
≈ 83
시간당 요금은 더 높은데, 한 번 실행하는 데 드는 컴퓨팅 비용은 약 17% 낮아진다. 더 빨리 끝내서 비싼 요금을 내는 시간 자체가 줄어든 것이다.
이 계산은 시간당 요금 차이를 10%로 둔 비교이며, 현재 AWS 가격표를 제시하는 것은 아니다. 실제 청구액에는 인스턴스를 켜 두는 전체 시간과 스토리지 같은 부대 비용도 영향을 준다. 그래도 이번 작업에서는 시간당 단가만 보고 고르면 오히려 손해일 수 있다는 점이 분명하게 보였다.
직렬 실행과 병렬 실행은 다르게 봐야 한다
여기서 구분해야 할 것이 있다. 컴퓨터 한 대의 처리 속도를 높이는 것과, 여러 작업을 동시에 처리하게 만드는 것은 서로 다른 방법이다.
테스트를 하나씩 순서대로 실행하는 것을 직렬 실행이라고 한다. 앞 테스트가 끝나야 다음 테스트를 시작하는 방식이다. 이 경우에는 CPU 코어가 더 많아져도 테스트를 실행하는 흐름 자체가 하나라면 남는 코어를 충분히 활용하기 어렵다. 계산이 병목인 부분은 코어 하나의 처리 성능이 좋아져야 빨라진다.
반면 병렬 실행은 서로 독립적인 테스트를 여러 개 동시에 돌리는 방식이다. 이때는 코어 수가 늘어난 만큼 일을 나눠 맡길 여지가 생긴다. 다만 실행 도구에도 동시에 돌릴 작업 수를 설정해 줘야 한다. 큰 인스턴스로 바꾸기만 하고 테스트를 계속 하나씩 실행하면, 늘어난 자원만큼 요금을 내면서 시간은 별로 줄이지 못할 수 있다.
앞의 C7g.xlarge와 C8g.xlarge 비교는 테스트의 병렬 실행 개수를 늘려 얻은 결과가 아니다. 같은 작업을 다른 세대의 인스턴스에서 실행한 비교다. 이 결과만으로 코어 수를 늘렸을 때도 같은 비율로 빨라진다고 볼 수는 없다.
병렬로 나눌 수 있다면, 더 큰 인스턴스도 선택지가 된다
그다음에는 자연스럽게 이런 생각이 들었다. 테스트를 독립적으로 나눌 수 있다면, 인스턴스 크기와 병렬 실행 개수를 함께 늘려 보면 어떨까?
시간당 요금이 두 배인 인스턴스에서 작업을 절반의 시간에 끝낼 수 있다면, 계산상 실행 비용은 같다.
시간당 요금 2배 × 실행 시간 ½ = 같은 실행 비용
같은 돈으로 결과를 두 배 빨리 받는 셈이다. CI에서는 꽤 매력적인 교환이다.
다만 이 계산은 전체 실행 시간이 절반으로 줄어드는 이상적인 경우다. 실제 CI에는 순서대로 처리해야 하는 준비 작업과, 여러 개를 동시에 돌릴 수 있는 테스트가 섞여 있다.
예를 들어 총 10분 중 반드시 순서대로 처리해야 하는 부분이 2분이고, 병렬로 나눌 수 있는 부분이 8분이라고 해 보자. 병렬 부분을 두 배 빠르게 처리해도 전체 시간은 2분 + 4분 = 6분이다. 시간당 요금이 두 배라면 실행 비용은 기존의 2 × 6 ÷ 10 = 1.2배가 된다. 이 경우에는 같은 비용으로 시간을 절반 줄이는 것이 아니라, 비용을 20% 더 내고 시간을 40% 줄이는 선택이다.
이것도 작업이 고르게 나뉘고 추가 부담이 없다는 가정이다. 실제로는 오래 걸리는 테스트 하나가 마지막까지 남을 수도 있고, 데이터베이스나 같은 테스트 데이터를 함께 쓰는 테스트끼리 충돌할 수도 있다. 병렬 실행 개수를 늘리기 전에 테스트별 데이터를 분리하고, CPU 외에 어디서 기다리는지도 살펴봐야 한다.
그래서 C7g와 C8g의 비교는 실제 실행 기록이고, 인스턴스 크기를 두 배로 키웠을 때의 효과는 앞으로 확인할 부분이다. 병렬 실행이 가능한 구조를 만들고, 실제로 얼마나 빨라지는지 다시 재야 한다.
아끼고 싶었던 것은 결국 시간이었다
처음에는 CI에 쓰는 돈을 좀 더 아끼고 싶었다. 하지만 비용을 줄이는 동안 테스트 결과를 기다리는 시간이 길어졌고, 그 기다림도 공짜는 아니었다.
결과를 기다리다 다른 일을 시작하면 다시 돌아오는 데 시간이 든다. 작은 수정을 하나 더 확인하고 싶어도 긴 CI를 떠올리면 잠깐 망설이게 된다. 실행 요금에는 잡히지 않지만, 개발하는 입장에서는 꽤 선명한 비용이다.
이번 비교에서는 더 빠른 인스턴스가 시간과 실행 비용을 함께 줄여 줬다. 다음 단계에서도 같은 결과가 나올지는 더 측정해 봐야 한다. 다만 이제는 시간당 요금이 가장 싼 컴퓨터를 찾는 것만으로는 충분하지 않다는 생각이 든다.
조금 더 아끼고 싶었지만, CI를 기다리며 흘려보내는 시간이 아깝다. 이제는 돈을 주고 시간을 사야겠다. 이번처럼 시간을 샀는데 계산서까지 줄어들면 더 좋고.