Waktu kompilasi Rust pada runner ephemeral GitHub Actions sering menjadi bottleneck utama dalam deployment pipeline. Proyek backend berbasis Actix Web mengeksekusi ratusan crate dependensi, parsing proc-macro yang berat (seperti actix-web-codegen dan serde_derive), serta linking biner dalam jumlah besar. Tanpa strategi caching terdistribusi, runner harus membangun seluruh pohon dependensi dari nol di setiap commit.

Akar Masalah: Kompilasi Dependensi dan Proc-Macro Actix Web

Pohon dependensi Actix Web mencakup ekosistem asynchronous Tokio, parser HTTP, layer kriptografi, dan serangkaian crate proc-macro. Proc-macro harus dikompilasi terlebih dahulu untuk mesin host (runner architecture) sebelum rustc dapat memproses kode aplikasi Anda.

Karakteristik runner GitHub Actions yang bersifat ephemeral (dihancurkan setelah selesai berjalan) memicu dua kendala teknis:

  • Kompilasi Ulang Utuh: Direktori target/ dan ~/.cargo hilang di setiap run jika tidak disimpan secara eksplisit.
  • Overhead I/O Incremental Compilation: Fitur incremental compilation Rust dirancang untuk local disk dengan latensi rendah, bukan environment CI.

Optimasi Flag Cargo: Nonaktifkan CARGO_INCREMENTAL

Kesalahan umum dalam pipeline CI Rust adalah membiarkan incremental compilation aktif. Di lingkungan lokal, flag ini mempercepat rebuild dengan menyimpan cache AST dan graf dependency ke disk. Namun, di CI, flag ini menghasilkan ribuan file kecil yang memperlambat proses kompresi/dekompresi cache dan jarang dapat digunakan kembali antar-runner.

Tetapkan environment variable berikut secara global di workflow:

env:
  CARGO_INCREMENTAL: 0
  RUST_BACKTRACE: 1

Menonaktifkan incremental compilation (CARGO_INCREMENTAL: 0) memaksa compiler menghasilkan artefak monolitik yang lebih bersih, mengurangi ukuran cache archive hingga 60%, dan meningkatkan kompilasi bersih (clean build) secara konsisten.

Arsitektur Layering: Swatinem/rust-cache vs sccache

Strategi caching yang efektif menggunakan dua layer terpisah:

  1. Target Cache (Swatinem/rust-cache): Menyimpan direktori ~/.cargo/registry, ~/.cargo/git, dan target/ berdasarkan hash dari Cargo.lock. Sangat cepat saat cache hit penuh (warm build), namun rapuh terhadap perubahan file lock sekecil apa pun.
  2. Object Cache (sccache): Berfungsi sebagai RUSTC_WRAPPER. Caching dilakukan pada level pemanggilan compiler individual untuk setiap crate berdasarkan hash kode sumber, flag compiler, dan versi rustc. Jika Cargo.lock berubah dan target/ terhapus, sccache mencegah kompilasi ulang dependensi pihak ketiga yang tidak berubah.

Menggabungkan keduanya memberikan mitigasi: rust-cache menangani warm builds instan, sementara sccache mengamankan cold builds parsial via GitHub Actions Cache API.

Implementasi Workflow GitHub Actions

Berikut adalah konfigurasi lengkap dan siap pakai pada .github/workflows/ci.yml menggunakan mozilla/sccache-action yang memanfaatkan GitHub Actions cache service secara native:

name: CI Pipeline

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: 0
  RUSTC_WRAPPER: sccache
  SCCACHE_GHA_ENABLED: "true"

jobs:
  build-and-test:
    name: Build & Test Actix Web
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install stable Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Setup sccache-cache
        uses: mozilla/[email protected]

      - name: Setup Cargo Cache
        uses: Swatinem/rust-cache@v2
        with:
          # Cegah invalidasi cache akibat perubahan file non-Rust
          cache-all-crates: "true"
          save-if: ${{ github.ref == 'refs/heads/main' }}

      - name: Check code formatting
        run: cargo fmt --check

      - name: Run Clippy
        run: cargo clippy --all-targets --all-features -- -D warnings

      - name: Run Tests
        run: cargo test --workspace --all-features

      - name: View sccache statistics
        run: sccache --show-stats

Mitigasi Cache Invalidation akibat Cargo.lock

Ketika dependensi yang tidak relevan (seperti crate linter dev-only) diperbarui di Cargo.lock, hash kunci cache pada rust-cache berubah total. Runner terpaksa mengabaikan direktori target/ sebelumnya.

Dengan menyertakan sccache yang didukung GitHub Cache API (SCCACHE_GHA_ENABLED: "true"):

  • Meski direktori target/ kosong, rustc memanggil wrapper sccache.
  • sccache menghitung hash dari Actix Web, Serde, dan Tokio. Karena dependensi inti ini tidak berubah, sccache mengunduh binary object langsung dari cache.
  • Fase linking tetap berjalan, tetapi waktu kompilasi crate dependensi dipangkas dari hitungan menit menjadi hitungan detik.

Batasi penyimpanan cache branch hanya pada branch utama (misal: refs/heads/main) menggunakan opsi save-if pada Swatinem/rust-cache. Hal ini mencegah pull request dengan eksperimen dependensi mencemari kuota 10 GB GitHub Actions cache pada repository Anda.

Observabilitas dan Evaluasi Performa

Metrik performa CI bervariasi bergantung pada jumlah dependensi, namun rasio umum yang terobservasi pada runner Linux 2-core:

  • Cold Build (Tanpa Cache): 8 – 12 menit untuk instalasi toolchain, dependensi Actix Web, dan kompilasi biner aplikasi.
  • Partial Invalidation (Cargo.lock berubah, sccache hit): 1.5 – 3 menit. Crate dependensi diunduh dari object cache, hanya crate aplikasi lokal dan final link step yang dieksekusi ulang.
  • Warm Build (Cache hit penuh rust-cache): 30 – 60 detik. Runner hanya memeriksa timestamp dan langsung mengeksekusi test suite.

Periksa step sccache --show-stats di akhir job log untuk memastikan efektivitas caching. Pastikan baris "Cache hits" meningkat seiring berjalannya pipeline berikutnya:

Compile requests                    412
Compile requests executed           412
Cache hits                          389
Cache misses                         23
Cache timeouts                        0
Cache errors                          0
Non-cacheable compilations            0

Jika "Compile requests executed" bernilai tinggi namun "Cache hits" nol secara konsisten, pastikan permission runner memiliki hak akses actions: write untuk mengizinkan interaksi dengan backend GitHub Cache API.