Pada pipeline CI/CD yang modern, permasalahan utama bukan hanya menjalankan banyak cakupan pengujian—tetapi juga menjaga waktu eksekusi tetap pendek. Dengan matrix environment, Anda bisa menjalankan build pada beberapa kombinasi sistem operasi, versi runtime, atau dependensi target secara paralel. Namun tanpa caching, setiap tugas tetap mengunduh ulang paket dan membangun dari awal. Dalam artikel ini kita langsung bahas konfigurasi GitHub Actions dan GitLab CI yang menggabungkan matrix environment dengan cache deterministik, serta strategi fallback ketika cache tidak tersedia.

1. Konsep Matrix Environment dan Manfaatnya

Matrix environment memungkinkan mendefinisikan beberapa kombinasi parameter (misalnya sistem operasi dan versi Node.js) dalam satu workflow. Ini penting untuk memastikan kompatibilitas lintas target tanpa membuat workflow terpisah untuk setiap kombinasi.

Prinsip dasarnya adalah:

  • Setiap kombinasi dijalankan di job terpisah sehingga bisa berjalan paralel.
  • Ambil repository workspace yang sama, lalu gunakan cache agar dependensi tidak diunduh ulang.
  • Gunakan strategi caching yang deterministik agar cache reuse bisa terjadi meski matrix berkembang.

2. Strategi Cache Deterministik

Cache yang baik membutuhkan key yang konsisten tapi cukup spesifik. Jika terlalu umum, build bisa menerima cache usang; jika terlalu spesifik, cache tidak pernah reuse. Pendekatan yang berhasil biasanya mencakup:

  • Bagian statis: Nama tool dan versi environment (contoh: node-18-linux).
  • Hash file lock: Hash dari package-lock.json atau sejenisnya.
  • Fallback key: Menyediakan prefix umum untuk digunakan saat cache miss spesifik.

Contoh key GitHub Actions: node-18-linux-${{ hashFiles('package-lock.json') }} dengan fallback key node-18-linux-. Dengan fallback, cache tetap bisa digunakan meski file hash berubah drastis.

3. Implementasi GitHub Actions

Berikut contoh workflow minimal yang menggabungkan matrix dan cache cache npm:

name: CI
on: [push]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        node: [18, 20]
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'npm'
      - name: Install dependencies
        run: npm ci
      - name: Run tests
        run: npm test

Dalam example ini, actions/setup-node sudah menyediakan caching bawaan. Namun untuk kontrol penuh (misalnya caching artifacts yang bukan npm), Anda dapat memakai actions/cache langsung, dengan key seperti berikut:

- name: Cache node_modules
  uses: actions/cache@v4
  with:
    path: |
      ~/.npm
      node_modules
    key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-${{ matrix.node }}-

Dengan restore key yang lebih umum, cache masih bisa digunakan saat hash baru dan cache spesifik belum tersedia.

4. Konfigurasi GitLab CI Parallel Matrix

GitLab CI tidak menyediakan key matrix langsung, tetapi mendukung parallel matrix menggunakan matrix: pada job. Berikut contoh:

stages:
  - test

test_job:
  stage: test
  parallel:
    matrix:
      - OS: [ubuntu:22.04, ubuntu:20.04]
      - NODE_IMAGE: [node:18, node:20]
  script:
    - echo "Running on $OS with $NODE_IMAGE"
    - npm ci
    - npm test
  cache:
    key: "$OS-$NODE_IMAGE-$CI_PROJECT_ID-${CI_COMMIT_REF_SLUG}"
    paths:
      - node_modules/

Perhatikan cache key menggunakan kombinasi OS dan image agar setiap lingkungan mendapat cache sendiri. Jika ingin fallback, tambahkan key yang lebih umum dan atur when: on_failure untuk menarik cache umum jika spesifik gagal:

  cache:
    key:
      files:
        - package-lock.json
      prefix: "$OS-$NODE_IMAGE"
    paths:
      - node_modules/
    policy: pull-push

5. Strategi Cache Miss dan Debugging

Cache miss bisa terjadi karena:

  • Perubahan file lock menghasilkan hash baru.
  • Pipeline dijalankan pada branch baru tanpa cache.
  • Key terlalu spesifik (misalnya menyertakan timestamp).

Solusi:

  • Gunakan restore-keys (GitHub Actions) atau prefix plus files (GitLab) agar cache tetap digunakan jika key utama gagal.
  • Log detail cache: pada GitHub Actions terlihat di output apakah cache hit atau miss. Pastikan actions/cache menulis pesan yang mencantumkan key yang dicoba.
  • Verifikasi bahwa path yang dicache sesuai (misalnya npm menyimpan dependensi di node_modules tapi Anda hanya mencache .npm).
  • Gunakan perintah seperti ls -la sebelum dan sesudah restore cache untuk memastikan konten terisi.

6. Metrik Performa dan Evaluasi

Ukuran dampak dapat diamati dengan membandingkan durasi job sebelum dan sesudah penerapan matrix + cache. Contoh nyata:

  • Sebelum: Job tunggal Ubuntu Node.js 18, install full packages membutuhkan ~3m10s.
  • Setelah: Matrix 2 OS x 2 Node, tetapi setiap job memanfaatkan cache sehingga tahap install turun menjadi ~45s per job.
  • Durasi pipeline keseluruhan: 1m10s sebelum vs 1m30s setelah (meskipun lebih lama total karena paralel, latency balik/merge request turun karena job selesai lebih cepat).

Gunakan monitoring CI (misalnya GitHub Actions timing atau GitLab Pipeline duration) untuk memantau varian durasi dan cache hit rate—terutama pada branch pengembang yang sering berubah.

7. Kesimpulan dan Best Practice

Menggabungkan matrix environment dengan strategi cache deterministik mengurangi waktu eksekusi berulang tanpa mengorbankan coverage. Perhatikan:

  • Cache key harus stabil tapi cukup spesifik.
  • Selalu sediakan fallback key untuk cache miss.
  • Gunakan logging dan perintah diagnostik untuk memastikan cache benar-benar terpakai.

Dengan pendekatan ini, pipeline CI/CD menjadi lebih cepat, paralel, dan efektif dalam menjamin kualitas lintas target tanpa penalti performa yang signifikan.