Masalah: Eksekusi Redundan pada Pipeline CI Monorepo
Pada arsitektur monorepo React Native yang memisahkan aplikasi (misal: apps/mobile) dan paket internal (misal: packages/ui, packages/utils), GitHub Actions mengeksekusi linting, TypeScript typecheck (tsc), dan pengujian unit (Jest) secara berulang pada setiap Pull Request. Ketika developer hanya memodifikasi packages/utils, eksekusi tsc dan jest tetap dijalankan di apps/mobile jika skrip CI memakai runner standar.
Runner GitHub Actions bersifat ephemeral: direktori kerja dibuang setelah job selesai. Cache lokal Turborepo (.turbo/cache) hilang bersamaan dengan musnahnya virtual machine runner. Solusinya adalah Turborepo Remote Cache, yang menyimpan artefak komputasi dan hash eksekusi pada object storage eksternal agar bisa digunakan lintas runner dan workstation developer.
Prinsip Task Hashing dan Pencegahan Cache Poisoning
Turborepo menentukan cache hit atau cache miss berdasarkan kalkulasi hash input task. Komponen pembentuk hash mencakup:
- Hash git commit atau konten file yang ditentukan pada properti
inputs. - Nilai hash dari dependency graph internal antar-paket monorepo.
- Nilai environment variable yang didaftarkan pada
envdanglobalEnv. - Konfigurasi task di
turbo.json.
Cache poisoning terjadi saat runner mengunduh artefak valid dari hash yang cocok, namun runtime menghasilkan keluaran cacat akibat dependensi tersembunyi yang tidak terdaftar di hash (contoh: membaca file dinamis di luar deklarasi, atau bergantung pada environment variable lokal). Menetapkan inputs dan env secara ketat adalah mitigasi wajib.
Konfigurasi Konkret: turbo.json
Berikut konfigurasi turbo.json yang mengisolasi input spesifik untuk lint, typecheck, dan test, serta mendefinisikan output compiler TypeScript.
{
"$schema": "https://turbo.build/schema.json",
"globalEnv": ["CI", "NODE_ENV"],
"tasks": {
"lint": {
"inputs": [
"src/**/*.{ts,tsx,js,jsx}",
".eslintrc*",
".eslintignore",
"package.json"
],
"outputs": []
},
"typecheck": {
"dependsOn": ["^typecheck"],
"inputs": [
"src/**/*.{ts,tsx}",
"tsconfig*.json",
"package.json"
],
"outputs": ["tsconfig.tsbuildinfo"]
},
"test": {
"dependsOn": ["^test"],
"inputs": [
"src/**/*.{ts,tsx,js,jsx}",
"jest.config.*",
"**/__tests__/**",
"**/__mocks__/**",
"package.json"
],
"outputs": ["coverage/**"],
"env": ["API_URL", "JEST_WORKER_ID"]
}
}
}Catatan konfigurasi: Padatypecheck, gunakan opsi--incrementaldan--tsBuildInfoFile tsconfig.tsbuildinfopada skrippackage.jsonagar Turborepo dapat menyimpan artefak incremental cache TS ke remote storage.
Integrasi GitHub Actions dengan Remote Cache
Gunakan autentikasi Vercel Remote Cache atau custom self-hosted remote cache (misal: S3-compatible via turbo-cache server) menggunakan environment variable TURBO_TOKEN dan TURBO_TEAM.
name: CI Pipeline
on:
pull_request:
branches: [main, staging]
jobs:
validate:
runs-on: ubuntu-latest
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
TURBO_REMOTE_ONLY: true
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install pnpm
run: corepack enable && corepack prepare pnpm@latest --activate
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Run Validations via Turborepo
run: pnpm exec turbo run lint typecheck test --api="https://api.vercel.com" --token="$TURBO_TOKEN" --team="$TURBO_TEAM"Penggunaan TURBO_REMOTE_ONLY: true mencegah runner menulis cache lokal ke disk runner yang akan segera dimusnahkan, meminimalkan I/O overhead.
Metrik Komparasi: Sebelum vs Sesudah Remote Cache
Data berikut mencerminkan pipeline pada monorepo React Native dengan 1 app target iOS/Android dan 4 shared packages, dijalankan pada GitHub standard runner (2-core vCPU, 7 GB RAM).
| Kondisi Task | Tanpa Cache (Cold) | Dengan Remote Cache (Warm) | Efisiensi Durasi |
|---|---|---|---|
lint (Seluruh Monorepo) | 1m 45s | 4s (FULL TURBO) | ~96% |
typecheck (TSC Incremental) | 2m 30s | 8s (FULL TURBO) | ~94% |
test (Jest - 1 pkg terisolasi diubah) | 3m 15s | 35s (Sebagian hit) | ~82% |
| Total Durasi CI | 7m 30s | 1m 15s | ~83% |
Panduan Troubleshooting: Mengatasi Cache Miss yang Tidak Diharapkan
1. Absolute Paths pada Snapshots dan Error Logs
Jest atau compiler terkadang menyematkan path absolut (misal: /home/runner/work/repo/...) ke dalam artefak output. Runner lokal developer menggunakan /Users/dev/..., sehingga checksum output berbeda dan memicu invalidasi.
- Solusi: Konfigurasikan
rootDirJest secara relatif. Hindari path serializer yang mencetak absolute path runner di snapshot test.
2. Line Endings (CRLF vs LF)
Jika developer lokal bekerja di sistem operasi Windows tanpa konfigurasi Git yang tepat, line endings CRLF akan merubah hash file input, menyebabkan cache miss total di runner Linux (Ubuntu).
- Solusi: Tambahkan file
.gitattributespada root repositori:
* text=auto eol=lf3. Debugging Task Hashing
Untuk melacak penyebab spesifik cache miss antar commit atau lingkungan lokal vs CI, gunakan flag --summarize atau --dry=json:
pnpm exec turbo run test --summarizePerintah ini menghasilkan file .turbo/runs/[run-id].json yang mendokumentasikan input files dan representasi hash tiap paket secara granular. Bandingkan file rangkuman antara workstation lokal dan runner GitHub Actions untuk menemukan diff pemicu cache invalidation.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!