본문 바로가기
프로그래밍/기타

[gradle] gradle의 퍼포먼스를 늘릴 수 있는 유용한 정보 및 옵션들

by 사바라다 2021. 10. 13.

요즘은 spring의 빌드 도구로 java, kotlin을 가리지 않고 gradle을 많이 사용합니다. 오늘은 이와 관련된 주제로 gradle의 퍼포먼스를 늘릴 수 있는 유용한 정보 및 옵션드에 대해서 알아보는 시간을 가져보도록 하겠습니다.

의존성을 주입할 때 dynamic version 보다는 static version을 사용하자

일반적으로 의존성을 주입할 때 dnynamic version 보다는 static version을 사용하는 것을 권장합니다. 이유는 버전을 dynamic version으로 하게되면 이후 특정 모듈의 버전이 올라감에 있어서 호환성 이슈가 발생할 수 있기 때문입니다. 이와는 별개로 static version을 사용하는것이 좋은 이유가 있습니다. 바로 성능적인 문제입니다. gradle 공식문서를 따르면 dynamic version을 사용하면 무조건 해당 repository에 접근해서 새로 출시된 버진이 있는지를 체크하기 때문에 추가적인 IO가 필요로 든다고 되어있습니다. 따라서 특수한 사정이 있지 않는 한 static version을 사용하시기를 권장드립니다.

implementation("org.springdoc:springdoc-openapi-webflux-ui")       // dynamic, 권장하지 않음, mavenPom 설정이 되어있다면 상관없음.
implementation("org.springdoc:springdoc-openapi-webflux-ui:1.+")   // dynamic, 권장하지 않음
implementation("org.springdoc:springdoc-openapi-webflux-ui:1.5.1") // static, 권장

불필요한 의존성은 제거하자

당연한 말이겠지만 불필요한 의존성은 최소화하는것이 좋습니다. 의존성이 늘어난다는 것은 결국 사용하는 코드가 늘어난다는 것이기 때문입니다. 이로인해 컴파일 시간이 늘어날 수 있습니다. 만약 특정 의존성에서 1가지 또는 2가지 클래스만 사용한다고하면 의존성을 가져오는 것이 아니라 해당 코드만 복사하여 사용하는것도 좋은 방법입니다.

repository의 수를 최소화하자

repository는 의존성을 가져오는 지점입니다. 일반적으로 mavenRepository를 이용하실 탠데요. mavenRepository 말고도 central 등 다양한 repository가 있습니다. 이러한 repository 수도 적절하게 조절하는 것이 좋습니다. 만약 사내 nexus가 있으시다면 단일 지점으로 만드셔서 가져오는 것도 좋은 방법이 될 수 있습니다.

api와 imiplementation

https://docs.gradle.org/current/userguide/performance.html#the_java_library_plugin

멀티모듈로 프로젝트를 구성할 때 gradle의 api와 implementation을 잘 맞게 이용하는 것이 좋습니다. implementation은 해당 모듈에만 컴파일타임에 사용되는 의존성입니다. 즉, 특정 모듈을 의존하고 있는 외부 모듈에서는 implementation으로 의존하고 있는 또다른 외부 의존성을 사용하는 것이 불가능합니다. api는 해당 의존성을 가지고 있는 상위 모듈들에게도 모두 컴파일되게 됩니다. 아래 예제 의존성을 보시겠습니다. infra module에서는 core 모듈을 의존성으로 가지고 있습니다. 그리고 core 모듈에서는 implementation으로 springdoc-openapi-webflux-ui의 의존성을 가지고 있고 api로 jackson-module-kotlin의 의존성을 가지고 있습니다. 이럴경우 infra 모듈에서는 jackson 모듈만 정상적으로 사용할 수 있으며 springdoc 모듈은 컴파일타임에 컴파일 에러가 발생한다는 의미입니다.

dependencies { // infra 모듈
    api(project(":core"))
}

dependencies { // core 모듈
    implementation("org.springdoc:springdoc-openapi-webflux-ui:1.+")
    api("com.fasterxml.jackson.module:jackson-module-kotlin")
}

이렇게 구분 지어둔 이유는 모든 의존성을 api로 가져간다면 멀티모듈에서 상위모듈을 컴파일할 때 하위모듈에 있는 의존성을 함께 한번씩 더 컴파일하게 됩니다. 이렇게 되면 상위 모듈에서 사용하지 않으면 불필요한 의존성을 가지고오게 됩니다. 이런것을 방지하기 위해서 implementation과 api를 나누어두었습니다. 따라서 사용하실 때 적절하게 사용하는 것을 권장드립니다.

병렬 처리 하기 (--parellal 옵션)

요즘 프로젝트는 단일 모듈로 사용하기 보다는 멀티모듈로 구성되는 경우가 많습니다. 이럴 때 빌드되는 모듈의 순서는 가장 하위 모듈이 가장 먼저, 그리고 가장 상위 모듈이 마지막에 빌드되게합니다. 이렇게 순서대로 진행되게되는데요. 이를 가능한한 병렬로 함께 처리할 수 있게해주는 옵션이 있습니다. 바로 --parellal 옵션인데요. 이 옵션은 병렬로 처리할 수 있다면 병렬로 처리하는 옵션입니다. 아래와 같은 의존성을 가지고 있는 프로젝트가 있다고 해보겠습니다.

api -> domain -> common
    -> infra  -> common

이럴 경우 일반적인 build 시에는 build 순서가 common -> infra 또는 domain -> domain 또는 infra -> api 일 것입니다. 하지만 병렬처리를 하게된다면 common -> domain과 infra 동시 진행 -> api가 되는 것입니다. 이러한 병렬진행도 무조건 병렬진행하는 것이 아니라 공통된 의존성을 먼저 처리하고 공통된 의존성이 없다면 병렬처리로 진행한다는 것을 알아주시면 좋을 것 같습니다.

또한 매번 --parellal 옵션을 붙이시는게 부담이시라면 gradle.properties 파일에 org.gradle.parallel=true 옵션을 주시면 기본적으로 빌드할 때 병렬로 처리가 됩니다.

데몬(Deamon)

gradle은 기본적으로 한번 실행하면 죽지 않고 일정시간(기본 30분)동안은 deamon으로 떠 있습니다. 이렇게 하는 이유는 다시 실행했을 때 gradle을 새롭게 띄우지 않기 위함입니다. 만약 gradle 3.0 이하 버전을 사용하신다면 이 기능이 off 되어있으므로 수동으로 켜주실 필요가 있습니다. 켜시는 방법은 gradle.properties 파일에서 org.gradle.daemon=true 옵션을 추가해주시면 됩니다.

또한 기본적으로 gradle은 build될 때 heap space를 1GB를 최대로 사용하고 있습니다. 메모리 부족으로 만약 빌드가 느리다고 판단하신다면 추가적인 메모리 사용을 원하실 수 있습니다. 이때는 org.gradle.jvmargs=-Xmx2048M 이런식으로 사용해주시면됩니다.

증분 build ( clean build vs bulid )

gradle은 build cache를 지원합니다. 기본적으로 gradle은 증분 빌드(incremental build)를 지향합니다. 증분 빌드는 재빌드를 할 때 이전 빌드와 비교하여 다시 빌드할 필요성이 있는지 없는지 판단하여 필요한 부분만 다시 빌드하는 것을 말합니다. 이 때 필요한 부분만 다시 빌드할 수 있도록 local에 caching 해두는 것을 build cache라고 합니다. buid cache는 기본적으로는 꺼져있으며 키기 위해서는 실행시 --build-cache 옵션을 사용하시면 됩니다. 만약 기본적으로 사용하고 싶으시다면 gradle.properties 파일에 org.gradle.caching=true 옵션을 추가해주셔도 됩니다.

아래의 예제를 보시면 좋을것 같습니다. 다른 부분은 다 생략하고 clean build를 연속으로 2번 실행했을 때 나타나는 차이점입니다. 처음에는 cache를 사용하지 않고 2번째에는 이미 build된 cache를 활용하여 컴파일을 하였고 10배 가까이 빌드타임이 줄어든 것을 확인할 수 있었습니다.

> Task :core:compileKotlin
> Task :infra:kaptKotlin
> Task :domain:compileKotlin
> Task :api:compileKotlin

BUILD SUCCESSFUL in 19s
36 actionable tasks: 27 executed, 9 from cache
12:07:09 오전: Tasks execution finished 'clean build'.
> Task :core:compileKotlin FROM-CACHE
> Task :infra:kaptKotlin FROM-CACHE
> Task :domain:compileKotlin FROM-CACHE
> Task :api:compileKotlin FROM-CACHE

BUILD SUCCESSFUL in 2s
36 actionable tasks: 23 executed, 13 from cache
12:07:42 오전: Tasks execution finished 'clean build'.

마무리

오늘은 gradle의 공식문서를 통해서 gradle 빌드시에 속도를 올리는 방법에 대해서, 그리고 최적화하는 방법에 대해서 알아보았습니다.

감사합니다.

참조

gradle_performance

댓글