728x90
https://www.youtube.com/watch?v=Ri1P4FcvUgg 영상을 의역해두었습니다.
TikTok 프로젝트 ?
- TikTok 프로젝트는 아주 큰 모노레포로 이루어져있습니다.
- 전체 모듈을 900개가 남습니다.
- 전체 코드를 컴파일 하고 있습니다.
- 600명이 넘는 안드로이드 개발자가 존재합니다.
- 매일 6천번이상 빌드 됩니다.
왜 빌드 속도가 중요할까 ?
- 1% 만 빌드 속도를 개선하면 15억정도 연간 절감할 수 있습니다.
- 코드 변경을 확인하기 까지 기다리는 시간이 줄어듭니다. 이는 더 나은 경험을 제공합니다.
- 따라서 이부분에 많은 노력을 들이고 있습니다.
어떻게 빌드 속도를 최적화할까요 ?
- Pay to win 기기를 업그레이드 하면 됩니다. (ㅋㅋ)
- 기가 업그레이드 하는 속도보다, 우리 코드가 증가하는 속도가 더욱 빠릅니다.
- 매년 기기 업그레이드는 제한적이기 때문에 쉽지만, 지속적으로 개선하기 어렵습니다
- 기가 업그레이드 하는 속도보다, 우리 코드가 증가하는 속도가 더욱 빠릅니다.
- 지난 몇년 동안은 컴파일되는 절대적인 코드량을 줄이는데 노력해왔습니다. 바로 AAR 을 이용해서 프로젝트의 일부분을 배포하는 방법 입니다. 이 과정에서 코드의 변경사항이 없기 때문에 빌드 단계에서 다양한 단계들이 스킵되고 결과적으로 시간이 많이 절약됩니다. → 빌드 타임에서 약 6% 효과가 있었습니다.
- 물론 부작용도 있었습니다. 그래들 캐시 시스템과 잘 호환되지 않는 문제가 있었습니다.
- 또한 라이브러리들 끼리 서로 의존하면서 생기는 문제들도 있었습니다 (chain of dependencies)
- 결과적으로는 공식적인 방법을 사용하는 쪽으로 선회했습니다.
- 그 방식은 빌드캐시와 증분빌드 입니다. 새로운 주제는 아니지만 어떻게 동작하는지 알아야했습니다.
- 어떻게 해야 그래들 캐시 히트를 최대로 끌어올릴 수 있고, 빌드 태스크들을 줄여낼 수 있는지 여전히 궁금증이 남아 있었습니다.
- 틱톡에서 사용된 다양한 사례들을 여러분게 소개시켜드리려 합니다.
- 먼저 이야기 하고 싶은 부분은 증분 빌드 입니다. 증분 빌드는 빌드 속도 최적화에서 게임 체인저였습니다.
- 사용하지 않는 리소스, 의존성 제거하기
- 최대한 모듈을 수직적으로 의존성을 두지 않고, 수평적으로 두기.
- ./gradlew dependencies
- com.autonomousapps.dependency-analysis 를 사용해보세요.
- 큰 모듈 분해하기
- Big base module 을 두지 않습니다. 계속 커지다보면 더이상 공통으로 사용할 클래스 외에도 다른 코드들이 추가되기 쉽습니다. 이는 코드의 변경사항이 생겼으니 컴파일 되어야 한다는 의미입니다. 기억하세요 수정이 발생하면 컴파일 됩니다.
- 큰 모듈을 어떻게 구분할까요 ?
- TikTok 은 Single Responsibility Score 를 측정하여 관리합니다.
- 스코어 방식 : used ref counts / export refs count
- 상위 모듈 컴파일에 거의 참여하지 않는 클래스 / 해당 모듈이 제공하는 모든 클래스
- 값이 크다는 의미는 사용되지 않는 클래스들이 많다는 것을 의미합니다. 즉 점수가 높은 모듈부터 낮은 모듈로 디커플링 하여 수정이 발생하면 영향 받는 상위 모듈을 최소화합니다. 명심하세요 수정이 발생하면 연관된 모듈이 컴파일 됩니다.
- 현재의 빌드 툴체인(Gradle, K2) 덕분에 모듈이 많아지는 것을 두려워하지 마세요. (900개가 넘는 모듈을 가진 TikTok 이 산 증인 입니다.)
- 스코어 방식 : used ref counts / export refs count
- TikTok 은 Single Responsibility Score 를 측정하여 관리합니다.
- Project structure 를 잡을 때 원칙
- 단일 책임을 가지도록 하자
- 자주 변경되는 것은 최대한 상위 레이어로 이동해서 다른 모듈에 영향이 없도록 하자.
- 모듈간 직접 접근하는 것을 DI 나 SPI(Service Provider Interface) 로 풀어내자.
- Classpath 를 분리하자
- 프로젝트에 플러그인을 추가하는데 어떤 방식을 사용하고 있나요 ? 아마 관행적으로 루트 build.gradle 에 추가하고 있지 않나요 ? 이는 캐시 시스템에 문제를 일으킵니다.
- build.gradle 에서 버전을 변경하면 전체 프로젝트 캐시 hash(fingerprint) 가 변경되어 프로젝트 전역적으로 캐시들이 무효화 됩니다.
- 따라서 필요한 모듈에 최소한으로 선언되어야 합니다.
- Root 에서 제공하고 있고, 모듈 2곳에서 사용한다면 2곳에 각각 넣어주는게 좋습니다.
- 프로젝트에 플러그인을 추가하는데 어떤 방식을 사용하고 있나요 ? 아마 관행적으로 루트 build.gradle 에 추가하고 있지 않나요 ? 이는 캐시 시스템에 문제를 일으킵니다.
- 증분빌드를 위한 캐시
- 복잡하고 자주 변경되는 태스크를 실행하면 캐시 알고리즘의 설계상 문제로 캐시 히트율이 30% 정도밖에 되지 않았습니다.
- 왜 그럴까 ? 먼저 코틀린 컴파일러가 증분 빌드에서 무엇을 하는지 알 필요가 있습니다.
- 증분빌드시 caches-jvm, last-build.bin, build-history.bin 에 output 히스토리가 남게됩니다. 다만 그래들 캐시 패키지에는 해당 파일들이 포함되지 않기 때문에 캐시가 히트해도 동작하지 않게 됩니다.
- 어떻게 해결하나요 ?
- 컴파일 한 바이트 코드들을 output 디렉토리에 두고, 같은 캐시키로 저장하는 것입니다.(영상 보기를 권장해요)
- 그래들 캐시 시스템의 문제
- 수백개의 불안정한 파라미터들이 100% 일치해야 캐시로부터 히트 가능합니다.
- 너무 비효율적이기 때문에 Baseline cache 를 활용해볼 수 있습니다.
- required 정보와 optional 정보로 나눠서 fingerprint 가 계산될 수 있도록 변경했습니다. 빌드 속도가 30% → 80% 로 개선되었습니다.
- Kotlin 컴파일에 사용되는 파라미터 등을 사용하기 위해서 Kotlin 을 fork 해서 사용해야했습니다.
- required 정보와 optional 정보로 나눠서 fingerprint 가 계산될 수 있도록 변경했습니다. 빌드 속도가 30% → 80% 로 개선되었습니다.
- 그래들 태스크는 어떻게 실행될까요 ?
- 그래들 태스크는 다양한 상태로 분리되어 있습니다.
- EXECUTED, UP-TO-DATE, FROM-CACHE, SKKIPED, NO-SOURCE
- 그중 EXECUTED, UP-TO-DATE, FROM-CACHE 3가지가 매우 중요합니다.
- EXECUTED : 기본 상태로 태스크 로직을 실행합니다.
- UP-TO-DATE : input 에 변경이 있고, out 이 존재하는 상태입니다.
- 변경사항이 없다면 그래들 태스크 실행을 생략합니다.
- FROM-CACHE : input 의 fingerprint 가 캐시 키와 일치하는 상태입니다.
- input 이 레코드된 히스토리와 일치하면 원격 서비스에서 직접 output 을 가져옵니다. (Cache Hit!)
- 그래들 태스크는 다양한 상태로 분리되어 있습니다.
728x90
'Android > 번역' 카테고리의 다른 글
| [번역] The Composable node tree 🌲 (0) | 2023.12.30 |
|---|---|
| [번역] Stateful vs Stateless Composables (0) | 2023.12.30 |
| [번역] Modifiers: order of precedence (0) | 2023.12.30 |
| [번역] The color of Composable functions (1) | 2023.12.30 |
| [번역] Effective android - Using Jetpack Compose with MVVM (0) | 2023.12.29 |