Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Performant Android CI

Performant Android CI

This talk will cover a breadth of topics to make Android CI pipelines as fast as possible: caching every bit of Gradle possible, custom git checkouts, a look at relevant JVM args, how we can apply layered Docker images and design a supporting CI pipeline.

Jason Pearson

April 13, 2025
Tweet

More Decks by Jason Pearson

Other Decks in Technology

Transcript

  1. Performant Android CI Jason Pearson The Purpose of CI 💻

    The Journey to Performant Android CI
  2. Performant Android CI Jason Pearson The Purpose of CI 💻

    The Journey to Performant Android CI
  3. Performant Android CI Build & Cache Techniques 📦 CI Pipeline

    Design Layered Docker Images Git Custom Checkout & Fetch
  4. Performant Android CI Build & Cache Techniques 📦 CI Pipeline

    Design Layered Docker Images Git Custom Checkout & Fetch
  5. ~900 Modules 💻 ~1M lines of code ~100 contributors Codebase

    Context Dagger Anvil (KAPT) 🗡 Gradle Kotlin Scripts Kotlin 1.9
  6. CI Context Single Docker image Datadog, Umbra Firebase Test Lab

    Gradle Develocity Self hosted EC2 Runners Artifactory GitLab CI
  7. • 3-4 minutes pipeline veri fi cation jobs • 30

    minutes in Lint • 50+ minutes in Test • ~30 minutes Building APKs • ~15 minutes running integration tests that are unreliable Very long MR pipeline times
  8. The Journey to Performant Android CI Some caching via Develocity

    📦 All artifacts all the time Full git clone via GitLab Slow and flaky UI tests
  9. The Journey to Performant Android CI Some caching via Develocity

    📦 All artifacts all the time Full git clone via GitLab Slow and flaky UI tests
  10. The Journey to Performant Android CI Everything cached 📦 All

    artifacts all the time Full git clone via GitLab Slow and flaky UI tests
  11. The Journey to Performant Android CI Everything cached 📦 Minimize

    git overhead All artifacts all the time Slow and flaky UI tests
  12. The Journey to Performant Android CI Everything cached 📦 Minimize

    git overhead Fast & Reliable UI Tests All artifacts all the time
  13. The Journey to Performant Android CI Everything cached 📦 Only

    artifacts as needed Fast & Reliable UI Tests Minimize git overhead
  14. Fast & Reliable Later… Minimize git overhead The Journey to

    Performant Android CI Everything cached 📦 Only artifacts as needed
  15. Fast & Reliable Only artifacts as needed Minimize git overhead

    The Journey to Performant Android CI Everything cached 📦 Later…
  16. Fast & Reliable Only artifacts as needed Minimize git overhead

    The Journey to Performant Android CI Everything cached 📦 Later…
  17. Fast & Reliable Only artifacts as needed Minimize git overhead

    The Journey to Performant Android CI Later… Build & Cache Techniques 📦
  18. Fast & Reliable Only artifacts as needed The Journey to

    Performant Android CI Later… Build & Cache Techniques 📦 Git Custom Checkout & Fetch
  19. Fast & Reliable Later… CI Pipeline Design The Journey to

    Performant Android CI Git Custom Checkout & Fetch Build & Cache Techniques 📦
  20. The number of milliseconds to hold onto soft references per

    MB of heap size, default 1000 = 2h 16m 32s 8GB Heap @ 1 MS Policy 8GB Heap @ 1000 MS Policy = 0h 0m 8s 8GB Heap @ 10 MS Policy = 0h 1m 22s SoftRefLRUPolicyMSPerMB JVM Memory Sizing
  21. tcmalloc malloc replacements jemalloc Freeing up as much memory as

    possible. Optimize for speed while keeping memory usage steady. Pending evaluation…
  22. • Reduce the number of inputs per task and make

    them more stable • Gradle build scans to observe performance Gradle Build Cache
  23. Relies on caching outputs of configuration phase, which rely on

    all inputs used in the build. So we need to: 1. Find all inputs 2. Hash them 3. Search cache provider Gradle Configuration Cache on CI
  24. 75 60 45 30 15 0 seconds Find Grep Find

    w/Prune ripgrep Source Hashing Search speed 🔎
  25. ~/.gradle/caches/<< gradle_version >>/dependencies-accessors ~/.gradle/caches/<< gradle_version >>/generated-gradle-jars ~/.gradle/caches/<< gradle_version >>/kotlin-dsl ~/.gradle/caches/<<

    gradle_version >>/transforms ~/.gradle/caches/modules-2 ~/.gradle/caches/jars-9 .gradle/<< gradle_version >> .gradle/configuration-cache build-logic/**/build ci-cache.yml
  26. $ aws s3 cp “$(pwd)” ”$S3_BUCKET_AND_PATH" \ --exclude “*" \

    --include “*.zip" \ --recursive AWS S3 + ZIP
  27. ➜ .gradle git:(main) ✗ du -sh 8.11.1 7.4M 8.11.1 ➜

    .gradle git:(main) ✗ du -sh configuration-cache 1.7M configuration-cache Gradle Configuration Cache on CI
  28. ➜ 8.11.1 du -sh transforms 1.1G transforms ➜ 8.11.1 du

    -sh generated-gradle-jars 177M generated-gradle-jars ➜ 8.11.1 du -sh kotlin-dsl 37M kotlin-dsl ➜ 8.11.1 du -sh dependencies-accessors 12M dependencies-accessors Gradle Configuration Cache on CI
  29. 10 8 6 4 2 0 seconds tar gzip pigz

    zstd lz4 Compression Options - against 1 GB 🗜
  30. 10 8 6 4 2 0 seconds tar gzip pigz

    zstd lz4 Compression Options - against 1 GB 🗜 with multiple cores
  31. 10 8 6 4 2 0 seconds tar gzip pigz

    zstd lz4 Compression Options - against 1 GB 🗜 with multiple cores
  32. 10 8 6 4 2 0 seconds tar gzip pigz

    zstd lz4 Compression Options - against 1 GB 🗜 with multiple cores
  33. 10 8 6 4 2 0 seconds tar gzip pigz

    zstd lz4 Compression Options - against 1 GB 🗜 with multiple cores
  34. zstd at its fastest speed has better compression than gzipped

    tar lz4 is lossless, which means its compression is almost nothing Multi-threaded didn’t make a difference in production Compression Options 🗜
  35. AWS S3 + ZSTD # find sources via ripgrep #

    dry run # create zstd archives # full run $ upload zstd archives via AWS S3
  36. 75 60 45 30 15 0 seconds Custom git checkout

    & fetch default clone blobless clone treeless clone shallow clone
  37. 75 60 45 30 15 0 seconds default clone blobless

    clone treeless clone shallow clone Custom git checkout & fetch treeless fetch
  38. FROM zillow-docker.artifactory… COPY . /prebuild RUN \ rm -rf /root/.gradle

    && \ mkdir -p /root/.gradle && \ mv /prebuild/gradle-user-home-backup / root/.gradle Layered images
  39. FROM zillow-docker.artifactory… COPY . /prebuild RUN \ rm -rf /root/.gradle

    && \ mkdir -p /root/.gradle && \ mv /prebuild/gradle-user-home-backup / root/.gradle Layered images
  40. Automate building, publishing, using Docker images Flatten to reduce negative

    space and parallelize work Use downstream pipelines to organize and declutter CI Pipeline Design
  41. CI Pipeline Design: Critical Checks Group checks that always complete

    in 60 seconds or less. Fail entire pipeline if critical check fails, but otherwise let pipelines continue.
  42. CI Pipeline Design: Team Jobs Dynamically run team specific work.

    Make it easy to keep author-team relationship up to date.
  43. 3 2 1 0 minutes Original Gradle Tools Increased pull

    time nullified with pre-pulling images Observability - Docker Overhead
  44. 3 2 1 0 minutes Original Gradle Tools Increased pull

    time nullified with pre-pulling images Observability - Docker Overhead