200MB 모듈을 팀 단위로 해결하기: 당근 숏폼팀의 On-demand Dynamic Feature Module 도입
당근 숏폼팀은 편집 기능의 **40MB**를 **On-demand DFM**으로 분리해 글로벌 사용자 부담을 줄였다.
당근 스토리의 비디오 편집 기능이 붙으면서 숏폼 모듈은 200MB까지 커졌고, 리소스를 CDN으로 옮긴 뒤에도 40MB가 남았다. 편집은 일부 사용자만 쓰는 기능이어서, 국내 전용 실험이 글로벌 전체 사용자 설치 용량에 영향을 주는 구조를 바꾸기로 했다.
선택한 해법은 Dynamic Feature Module(DFM) 이었다. 설치 시 함께 넣는 방식이 아니라, 사용자가 편집 버튼을 눌렀을 때만 내려받는 On-demand Delivery로 전환해 비사용자는 40MB를 아예 받지 않도록 했다.
적용 과정에서는 몇 가지 현실적인 제약이 드러났다.
- Hilt/DI 제약: DFM은 base와 별도로 빌드되기 때문에, base 빌드 시점에 DFM의
@Inject와@Module을 Hilt가 알 수 없다. 결국 base는 EntryPoint로 의존성을 노출하고, DFM 쪽은 Dagger Component를 직접 구성하는 방식이 필요했다. - SplitCompat 설정: on-demand로 설치된 split APK의 코드와 리소스를 보려면 SplitCompat이 필요했다. 설정이 없으면
ClassNotFoundException이나 리소스 로딩 실패가 날 수 있었다. - SO 파일 관리:
System.loadLibrary()로는 DFM에 있는 native library를 찾지 못해UnsatisfiedLinkError가 날 수 있어, **SplitInstallHelper.loadLibrary()**를 써야 했다. 또 base와 DFM의ndk.abiFilters를 맞추고,libc++_shared.so같은 STL 충돌도 신경 써야 했다. - R8 규칙 관리: DFM에만 둔 keep 규칙 때문에 base 타입이 난독화되는 문제가 생겨, keep 규칙은 base에 집중하는 쪽으로 정리했다.
여러 시행착오 끝에 구조는 더 단순해졌다. 기능 코드는 base 모듈에 두고, 용량의 대부분을 차지하던 SO 파일만 DFM으로 분리하는 방식으로 수렴했다. 코드 자체는 수백 KB 수준이었고, 실제 절감 대상은 네이티브 라이브러리였기 때문이다.
이 방식의 장점은 분명했다.
- DFM 로딩에 실패해도 앱 전체가 죽지 않고, SO 파일만 없다는 상태로 안내할 수 있다.
- 로컬 개발과 디버깅이 훨씬 쉬워진다.
- SO 파일만 분리해도 90% 이상 용량 절감 효과를 얻을 수 있었다.
대신 처음 보는 엔지니어에게는 코드와 SO 파일이 분리된 구조가 낯설 수 있고, 설치 여부 확인부터 SO 로딩, 기능 실행까지의 흐름을 명시적으로 관리해야 했다. 그럼에도 핵심 목표는 달성됐다. 국내 전용 숏폼 편집 기능을 글로벌 사용자에게 부담시키지 않으면서도 안정적으로 운영하는 구조를 만든 것이다.
마지막으로 정리한 운영 포인트는 세 가지였다. 안정성은 다른 팀과 글로벌 서비스에 영향을 주지 않는 것, 독립성은 팀이 자율적으로 개발하고 실험할 수 있는 것, 운영 효율성은 누구나 이해하고 고칠 수 있는 것이었다. 결국 이번 프로젝트는 "멋있게 만들기"보다 "안정적으로 굴리기"가 더 중요하다는 결론으로 끝났다.
부록에서는 DFM 적용 시 테스트 방법도 제시했다. 실제 환경에서는 Play Console 내부 테스트나 Internal App Sharing으로 다운로드 플로우를 검증하고, 로컬에서는 bundletool로 base-only / DFM 포함 / universal APK 상태를 번갈아 만들며 확인한다. 적용 전에는 모듈 사이즈, 다운로드 타이밍, SplitCompat 위치, 설치·미설치 케이스를 모두 점검하고, 운영 중에는 다운로드 성공률과 SO 로딩 실패율, 편집 진입 성공률, 네트워크 재시도 성공률을 모니터링해야 한다.
이 요약은 원문 이해를 돕기 위한 큐레이션입니다. 저작권은 원저작자에게 있으며, 정확한 내용과 맥락은 원문을 확인하세요.
요약 오류, 출처 표기 문제, 삭제 요청은 문의 · 건의로 알려주세요.
