Strategi CI matrix build yang baik bukan berarti menguji semua kombinasi yang mungkin. Tujuan utamanya adalah menemukan regresi lintas versi sedini mungkin dengan biaya menit CI yang tetap terkendali. Dalam banyak kasus, kombinasi minimal yang dipilih dengan sadar jauh lebih efektif daripada full cartesian matrix yang lambat dan mahal.
Untuk GitHub Actions, pendekatan praktisnya adalah memisahkan jalur uji wajib dan opsional, menjalankan versi utama pada setiap pull request, lalu memperluas cakupan versi atau OS pada jadwal tertentu, branch tertentu, atau job non-blocking. Prinsip ini juga relevan di GitLab CI, CircleCI, Jenkins, dan platform lain: jangan samakan coverage matrix dengan kualitas desain pipeline.
Kapan matrix build memang diperlukan?
Matrix build berguna ketika aplikasi atau library Anda perlu kompatibel dengan lebih dari satu lingkungan eksekusi. Contohnya:
- Library Node.js atau PHP yang harus mendukung beberapa versi runtime.
- Aplikasi yang berjalan di lebih dari satu OS runner, misalnya Linux dan Windows.
- Proyek yang sensitif terhadap variasi dependency utama, misalnya kombinasi framework dan database client tertentu.
- Produk yang distribusinya ditujukan untuk banyak pengguna eksternal, sehingga kompatibilitas lintas versi benar-benar penting.
Namun, matrix tidak selalu perlu. Jika Anda mengelola aplikasi internal dengan runtime produksi yang tunggal, sering kali cukup satu jalur uji yang merepresentasikan lingkungan produksi. Menambahkan banyak versi hanya menambah waktu tunggu tanpa meningkatkan keyakinan secara proporsional.
Gunakan matrix jika:
- Anda memelihara paket atau library untuk publik.
- Dokumentasi Anda menyatakan dukungan multi versi.
- Perbedaan runtime atau OS memang berpotensi mengubah perilaku.
- Bug kompatibilitas pernah terjadi dan perlu dicegah berulang.
Tidak perlu matrix penuh jika:
- Aplikasi hanya dideploy ke satu runtime yang terkendali.
- Perbedaan versi tidak relevan terhadap jalur eksekusi utama.
- Waktu CI menjadi bottleneck utama tim kecil.
- Anda belum punya tes yang cukup stabil; memperbanyak matrix hanya memperbanyak noise.
Prinsip desain matrix build yang hemat menit
1. Bedakan kombinasi inti dan kombinasi tambahan
Kesalahan umum adalah menjalankan semua kombinasi di setiap pull request. Pendekatan yang lebih hemat:
- Kombinasi inti: versi runtime utama di OS utama, wajib lulus untuk merge.
- Kombinasi tambahan: versi lama, versi terbaru, atau OS sekunder; bisa dijalankan terjadwal, pada branch tertentu, atau sebagai job opsional.
Contoh untuk Node.js:
- PR biasa: jalankan Node LTS aktif pada Ubuntu.
- Push ke main: tambah Node versi lama yang masih didukung.
- Nightly/schedule: jalankan Linux + Windows + versi terbaru.
Dengan pola ini, Anda tetap mendapat sinyal cepat untuk review kode, tanpa kehilangan visibilitas kompatibilitas jangka lebih panjang.
2. Hindari full cartesian matrix jika tidak dibutuhkan
Jika Anda punya 3 versi runtime, 3 OS, dan 2 variasi dependency, matrix penuh berarti 18 job. Sering kali hanya 4-6 kombinasi yang benar-benar memberi nilai. Gunakan exclude untuk kombinasi yang tidak penting atau tidak didukung, dan gunakan include hanya untuk kombinasi khusus yang memang ingin ditambahkan.
Aturan praktis: uji satu kombinasi yang paling mendekati produksi, satu kombinasi minimum yang masih didukung, dan satu kombinasi terbaru untuk mendeteksi perubahan ke depan.
3. Pisahkan job wajib dan opsional
Matrix yang sama tidak harus memiliki tingkat kepentingan yang sama. Buat job terpisah agar branch protection tetap sederhana:
- test-required: cepat, stabil, menjadi status check wajib.
- test-extended: cakupan lebih luas, boleh gagal sementara, atau tidak dijadikan syarat merge.
Ini penting karena jika semua matrix dijadikan wajib, satu kombinasi pinggiran bisa menghambat seluruh alur kerja tim.
4. Pilih fail-fast sesuai tujuan
Di GitHub Actions, strategy.fail-fast menentukan apakah job matrix lain dibatalkan saat satu kombinasi gagal.
- Fail-fast: true cocok untuk PR, saat tujuan utama adalah memberi sinyal cepat bahwa perubahan rusak.
- Fail-fast: false cocok saat Anda ingin full report, misalnya untuk melihat semua versi yang terdampak sebelum memperbaiki.
Trade-off-nya jelas: fail-fast menghemat menit, tetapi mengurangi visibilitas penuh. Sebaliknya, full-report lebih informatif tetapi lebih mahal.
5. Batasi parallelism agar tidak membanjiri runner
Matrix memang paralel, tetapi paralelisme maksimal tidak selalu optimal. Jika proyek memakai layanan eksternal, database sementara, atau paket dependency yang besar, terlalu banyak job paralel bisa membuat antrean runner, memperlambat keseluruhan, atau memicu throttling.
Gunakan max-parallel bila perlu. Ini bukan hanya soal biaya, tetapi juga stabilitas pipeline.
Contoh workflow GitHub Actions yang praktis
Contoh 1: Node.js, job wajib untuk PR dan job tambahan untuk main/schedule
Berikut contoh workflow yang memisahkan jalur wajib dan jalur kompatibilitas tambahan.
name: ci
on:
pull_request:
push:
branches: [main]
schedule:
- cron: '0 2 * * *'
jobs:
test-required:
name: required / node-${{ matrix.node }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest]
node: [20]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- run: npm test
test-extended:
name: extended / node-${{ matrix.node }} / ${{ matrix.os }}
if: github.event_name != 'pull_request'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
max-parallel: 2
matrix:
os: [ubuntu-latest, windows-latest]
node: [18, 20, 22]
exclude:
- os: windows-latest
node: 18
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- run: npm testMengapa desain ini efisien?
- PR hanya menjalankan satu kombinasi utama yang cepat.
- Push ke
maindanschedulememperluas cakupan kompatibilitas. excludemenghapus kombinasi yang tidak penting atau tidak didukung.max-parallelmembatasi konsumsi runner.
Contoh 2: PHP dengan versi minimum, utama, dan terbaru
Untuk proyek PHP, pola umum adalah menguji:
- Versi minimum yang didukung untuk memastikan batas bawah kompatibilitas.
- Versi utama produksi sebagai jalur wajib.
- Versi terbaru untuk kesiapan upgrade.
name: php-ci
on:
pull_request:
push:
branches: [main]
jobs:
test-required:
name: required / php-${{ matrix.php }}
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
php: ['8.2']
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
- run: composer install --prefer-dist --no-interaction
- run: vendor/bin/phpunit
test-compat:
name: compat / php-${{ matrix.php }}
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.1', '8.2', '8.3']
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
- run: composer install --prefer-dist --no-interaction
- run: vendor/bin/phpunitPola ini lebih masuk akal daripada menjadikan semua versi sebagai syarat merge. Anda tetap mendapat jaminan bahwa branch utama sehat, sambil menjaga PR tetap responsif.
Teknik penghematan menit yang paling berdampak
Cache dependency, tetapi pahami batasnya
Cache bisa sangat membantu, terutama untuk npm, composer, atau dependency manager lain. Namun cache tidak otomatis membuat semua job murah. Hal-hal yang perlu dipahami:
- Cache efektif jika kunci cache stabil dan file lock jarang berubah.
- Setiap kombinasi OS atau runtime bisa menghasilkan cache berbeda.
- Cache yang terlalu spesifik sering menghasilkan miss; terlalu umum bisa berisiko tidak relevan.
- Cache mempercepat install, tetapi tidak mengurangi biaya waktu eksekusi test itu sendiri.
Untuk GitHub Actions, lebih aman memakai mekanisme cache bawaan action resmi jika tersedia, karena biasanya sudah mengikuti pola kunci yang masuk akal.
Gunakan dependency lock yang konsisten
Jika tujuan job adalah memverifikasi aplikasi dengan dependency yang sudah dikunci, gunakan instalasi berbasis lockfile agar hasil reproducible. Menggabungkan matrix versi runtime dengan dependency yang terus berubah akan memperbesar noise dan menyulitkan diagnosis.
Jika Anda memang ingin menguji rentang dependency, lakukan itu sebagai jalur terpisah, bukan dicampur ke semua job utama.
Jalankan lint dan static analysis di job terpisah
Lint, format check, dan static analysis biasanya tidak perlu diulang di setiap kombinasi matrix, karena hasilnya sering tidak bergantung pada versi runtime yang berbeda. Menjalankannya sekali di job terpisah lebih hemat dibanding menduplikasi pada semua kombinasi.
Contoh pembagian yang baik:
- lint: sekali saja.
- unit test required: satu atau dua kombinasi penting.
- compatibility matrix: kombinasi tambahan.
Pakai trigger yang tepat
Tidak semua event perlu menjalankan matrix besar. Pertimbangkan:
- pull_request: jalur cepat.
- push ke main: jalur lebih lengkap.
- schedule: tes kompatibilitas luas atau eksperimen dependency.
- manual dispatch: saat ingin memverifikasi kombinasi tertentu sebelum rilis.
Ini salah satu cara paling efektif untuk menekan biaya tanpa mengorbankan keamanan rilis.
Branch protection dan penandaan job yang stabil
Untuk branch protection, pilih nama job wajib yang stabil dan jangan bergantung pada banyak nama matrix yang berubah-ubah. Praktik yang umum adalah:
- Menjadikan hanya job test-required sebagai required check.
- Tidak menjadikan seluruh test-extended sebagai syarat merge.
- Menghindari perubahan nama job terlalu sering karena dapat memutus konfigurasi required checks.
Ini lebih mudah dikelola daripada menandai banyak kombinasi matrix satu per satu. Jika tim Anda sering mengubah daftar versi yang didukung, job agregat yang stabil akan mengurangi beban administrasi branch protection.
Catatan: Tujuan branch protection adalah menjaga kualitas minimum untuk merge, bukan memaksa semua eksperimen kompatibilitas menjadi blocker.
Fail-fast vs full-report: kapan memilih yang mana?
Pilih fail-fast jika prioritasnya kecepatan umpan balik
Untuk pull request aktif, reviewer biasanya hanya butuh jawaban cepat: aman atau tidak. Jika satu kombinasi inti sudah gagal, membatalkan job lain sering masuk akal. Ini menghemat menit dan mempercepat siklus perbaikan.
Pilih full-report jika sedang mengevaluasi dampak lintas versi
Saat Anda menaikkan dependency besar, menambah dukungan runtime baru, atau melakukan refactor yang berisiko, full-report lebih berguna. Anda bisa melihat seluruh spektrum kegagalan sekaligus, bukan satu per satu.
Pola praktis yang seimbang
- PR: fail-fast aktif.
- Main/nightly: fail-fast nonaktif.
Pola ini umum karena sesuai dengan dua kebutuhan berbeda: kecepatan saat review dan visibilitas saat validasi lebih luas.
Anti-pattern umum dalam matrix build
1. Menguji semua kombinasi di setiap PR
Ini anti-pattern paling sering. Hasilnya: pipeline lambat, antrean panjang, dan developer mulai menganggap CI sebagai hambatan, bukan pelindung kualitas.
2. Menjadikan semua job sebagai blocker
Satu kombinasi pinggiran di OS sekunder tidak selalu layak menghentikan merge. Jika semuanya wajib, tim akan terdorong menonaktifkan tes atau melemahkan branch protection.
3. Mencampur tujuan kompatibilitas dan tujuan kualitas dasar
Lint, unit test utama, uji kompatibilitas versi lama, dan eksperimen dependency sebaiknya tidak dicampur ke satu matrix besar. Job akan sulit dipahami dan sulit di-debug.
4. Tidak mendokumentasikan alasan kombinasi dipilih
Tanpa dokumentasi, tim cenderung menambah versi baru tetapi lupa menghapus kombinasi lama yang tak lagi relevan. Akhirnya matrix tumbuh tanpa kendali.
5. Mengabaikan flaky test sebelum memperluas matrix
Jika tes dasar belum stabil, menambah matrix hanya memperbesar frekuensi kegagalan acak. Selesaikan flakiness dulu sebelum menambah dimensi runtime atau OS.
Tips debugging saat matrix mulai rumit
- Periksa apakah kegagalan spesifik runtime atau spesifik OS. Jangan langsung menyimpulkan bug ada di kode aplikasi.
- Bandingkan log instalasi dependency. Banyak kegagalan matrix sebenarnya terjadi sebelum tahap tes.
- Pastikan kombinasi yang diexclude memang benar. Salah ketik pada nilai matrix sering membuat job tak terfilter.
- Uji lokal dengan versi runtime yang sama jika memungkinkan, misalnya memakai container atau version manager.
- Jangan campur banyak perubahan pipeline sekaligus. Ubah matrix bertahap agar sumber masalah mudah diisolasi.
Checklist implementasi untuk tim kecil dan menengah
Untuk tim kecil
- Tentukan satu kombinasi wajib yang paling dekat dengan produksi.
- Tambahkan satu atau dua kombinasi kompatibilitas hanya jika benar-benar didukung.
- Jalankan matrix luas hanya di
mainatauschedule. - Pisahkan lint/static analysis dari matrix test.
- Aktifkan cache dependency.
- Gunakan fail-fast pada PR.
- Jadikan hanya job wajib sebagai branch protection check.
Untuk tim menengah
- Kelompokkan job menjadi required, compatibility, dan release confidence.
- Definisikan versi minimum, versi utama, dan versi terbaru yang didukung.
- Batasi kombinasi OS hanya pada yang relevan secara bisnis atau operasional.
- Gunakan
excludedanincludesecara eksplisit, jangan full cartesian tanpa alasan. - Atur
max-paralleljika runner atau layanan pendukung mudah jenuh. - Review matrix secara berkala saat kebijakan dukungan versi berubah.
- Dokumentasikan alasan setiap kombinasi agar tidak tumbuh liar.
Kesimpulan
Strategi CI matrix build yang efisien berfokus pada sinyal yang paling bernilai, bukan jumlah job terbanyak. Untuk GitHub Actions, kuncinya adalah memilih kombinasi inti, mengecualikan variasi yang tidak penting, memisahkan job wajib dan opsional, memakai cache dengan benar, serta menyesuaikan fail-fast dan parallelism dengan tujuan pipeline.
Jika Anda ragu harus mulai dari mana, mulailah sederhana: satu job wajib untuk PR, satu matrix tambahan untuk main atau schedule, lalu evaluasi dari data kegagalan nyata. Matrix yang baik bukan yang paling lengkap, melainkan yang memberi keyakinan teknis terbaik dengan biaya dan waktu tunggu yang masuk akal.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!