Giriş: CI/CD Pipeline’larında Gizli Maliyetler
Her gün onlarca, bazen yüzlerce deploy alıyoruz. Bu süreç ne kadar hızlı ve sorunsuz olursa, ekipler o kadar verimli çalışıyor. CI/CD pipeline’larının kalbi ise build işlemleri. Ancak bu build’lar, özellikle büyük projelerde, ciddi zaman ve kaynak tüketebiliyor. Bir build’ın saatlerce sürmesi, geliştiricilerin bekleyişini uzatıyor, hızlı geri bildirim döngüsünü kırıyor ve en önemlisi, altyapı maliyetlerini artırıyor. Sadece build süreleri değil, build sırasında kullanılan CPU, RAM ve disk I/O’su da hesaba katılmalı.
Bu makalede, CI/CD pipeline’larında sıkça karşılaşılan ve göz ardı edilebilen bir maliyet kalemine, yani build cache yönetimine odaklanacağız. Kendi projelerimde ve danışmanlık yaptığım yerlerde gözlemlediğim kadarıyla, build cache’lerini doğru yönetmemek, gereksiz yere ciddi zaman ve para kaybına yol açabiliyor. Geliştirme ve operasyon ekiplerinin en değerli varlığı zaman. Bu zamanı daha verimli kullanmanın yollarından biri de build cache’lerini akıllıca yönetmekten geçiyor.
Build Cache Nedir ve Neden Önemlidir?
Build cache, bir yazılım projesinin derlenmesi (compile) veya paketlenmesi sırasında tekrar eden işleri saklayan bir mekanizmadır. Örneğin, bir Java projesinde bağımlılıklar (dependencies) her build’da tekrar indirilmez veya derlenmez; önbelleğe alınır. Benzer şekilde, bir frontend projesinde node_modules klasörü veya derlenmiş JavaScript/CSS dosyaları cache’lenebilir. Temel mantık şu: Eğer bir dosya veya işlem daha önce yapıldıysa ve girdisi değişmediyse, çıktısını tekrar hesaplamak yerine cache’ten kullanmak.
Bu yaklaşımın en büyük faydası, build sürelerini dramatik şekilde kısaltmasıdır. Özellikle büyük monorepo’larda veya çok sayıda bağımlılığı olan projelerde, her build’ın baştan sona çalışması saatler sürebilir. Cache kullanımıyla bu süreler dakikalara, hatta saniyelere inebilir. Bu da geliştiricilerin daha hızlı geri bildirim almasını, daha sık deploy yapmasını ve genel olarak daha üretken olmasını sağlar.
CI/CD Araçlarında Cache Yönetimi: Genel Yaklaşımlar
Çeşitli CI/CD araçları, build cache’lerini yönetmek için farklı mekanizmalar sunar. Jenkins, GitLab CI, GitHub Actions, CircleCI gibi popüler platformlarda cache yönetimi, pipeline’ların temel bir parçasıdır. Genel olarak, cache’ler şu şekillerde yönetilir:
- Cache Anahtarı (Cache Key) Belirleme: Cache’in neye göre oluşturulacağı ve tetikleneceği belirlenir. Bu genellikle proje dosyalarının hash’i, bağımlılık dosyalarının hash’i veya belirli bir versiyon numarası gibi faktörlere dayanır. Anahtar değiştiğinde yeni bir cache oluşturulur.
- Cache Dizini Belirleme: Hangi klasörlerin veya dosyaların cache’leneceği belirtilir. Örneğin,
node_modules,.m2,.gradlegibi dizinler sıkça cache’lenir. - Cache Saklama ve Geri Yükleme: Build başlamadan önce belirlenen cache anahtarına karşılık gelen cache geri yüklenir. Build bittikten sonra ise yeni oluşturulan veya güncellenen cache, belirlenen anahtarla saklanır.
- Cache Temizleme (Cleanup): Eski veya kullanılmayan cache’lerin temizlenmesi de önemlidir. Aksi takdirde disk alanı hızla dolar ve maliyet artar.
Her aracın kendine özgü komutları ve konfigürasyonları olsa da, temel prensipler benzerdir. Örneğin, GitHub Actions’ta actions/cache action’ı kullanılırken, GitLab CI’da cache: anahtarının altında dizinler ve anahtarlar belirlenir.
GitHub Actions Örneği: Cache’in Gücü
GitHub Actions’ta cache yönetimi için actions/cache action’ı oldukça kullanışlıdır. Basit bir konfigürasyonla bağımlılıkları veya build çıktılarını cache’leyebilirsiniz.
name: CI with Cache
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache npm dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # Bu satır otomatik olarak npm cache'ini yönetir
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
Bu örnekte, package-lock.json dosyasının hash’i kullanılarak bir cache anahtarı oluşturulur. Eğer bu dosya değişirse, yeni bir cache oluşturulur. Aksi takdirde, mevcut cache geri yüklenir. Bu, npm ci komutunun çok daha hızlı çalışmasını sağlar çünkü node_modules dizini tekrar tekrar indirilmez.
Build Cache’in Maliyet Etkisi: Sayısal Verilerle
Cache yönetimi sadece hızı artırmakla kalmaz, aynı zamanda altyapı maliyetlerini de doğrudan etkiler. Bir build’ın ne kadar sürede tamamlandığı, CI/CD runner’larının (işçi sunucularının) ne kadar süreyle meşgul olacağını belirler. Çoğu bulut tabanlı CI/CD hizmetinde runner’lar saatlik ücretlendirilir. Bu ücretler, özellikle yoğun kullanımda ciddi rakamlara ulaşabilir.
Mantığı basit bir örnekle düşünelim: bir build cache olmadan kat kat daha uzun sürüyorsa, kazanılan her dakika gün içindeki build sayısıyla çarpılır. Çok sayıda build alan bir ekipte bu, aylık toplamda kayda değer bir runner süresi ve doğrudan fatura tasarrufu anlamına gelir. Büyük projelerde veya daha pahalı runner’lar kullanıldığında bu fark katlanarak artar.
Ancak maliyet sadece runner süresiyle sınırlı değildir. Build’lar sırasında disk alanı, CPU ve bellek kullanımı da önemlidir. Cache’ler, özellikle gereksiz yere büyük veya eski cache’ler, pahalı depolama alanlarını doldurabilir. Disk alanı maliyetleri de göz ardı edilmemelidir. Bazı bulut sağlayıcıları, snapshot’lar veya object storage için de ücretlendirme yapar.
Cache’lerinizi Optimize Etmek İçin Pratik Adımlar
Build cache’lerini verimli kullanmak, birkaç basit ama etkili adımla mümkün. İşte denediğim ve sonuç aldığım bazı yöntemler:
- Doğru Cache Anahtarlarını Kullanın: Projenizin yapısını ve bağımlılıklarını iyi anlayın.
package-lock.json,yarn.lock,pom.xml,build.gradle,requirements.txtgibi dosyaların hash’lerini anahtar olarak kullanmak genellikle iyi bir başlangıç noktasıdır. Bu dosyalar değiştiğinde build’ın yeniden çalışması gerektiğini biliriz. - Gereksiz Cache’lerden Kaçının: Her şeyi cache’lemeye çalışmayın. Sadece gerçekten zaman kazandıracak ve sık değişmeyen şeyleri cache’leyin. Örneğin, her commit’te değişen geçici dosyaları cache’lemek mantıksızdır.
- Cache’leri Düzenli Olarak Temizleyin: CI/CD aracınızın veya kendi scriptlerinizle belirli aralıklarla eski cache’leri temizleyin. Bu, disk alanını boşaltır ve maliyetleri düşürür. Bazı araçlar, otomatik olarak eski cache’leri silme seçeneği sunar.
- Katmanlı Cache Kullanın: Farklı katmanlar için ayrı cache’ler kullanmak faydalı olabilir. Örneğin, bağımlılık cache’i ve derlenmiş kod cache’i ayrı tutulabilir. Bu, bir katman değiştiğinde diğerinin boşa gitmesini engeller.
- Dağıtık Cache Çözümlerini Değerlendirin: Çok büyük projelerde veya dağıtık build sistemlerinde, yerel runner cache’leri yetersiz kalabilir. Redis, MinIO veya bulut depolama hizmetleri gibi dağıtık cache çözümleri daha ölçeklenebilir bir çözüm sunabilir.
Gerçek Bir Senaryo: E-Ticaret Projesinde Cache Kaybı
Bir e-ticaret projesinde çalışırken, frontend build’larının oldukça uzun sürdüğünü fark ettik. npm ci komutu her seferinde dakikalarca alıyordu. package-lock.json dosyasının hash’ini kullanarak bir cache anahtarı belirlemiştik ama cache’lerimiz sık sık geçersiz oluyordu. Detaylı incelediğimizde, CI/CD runner’larının bazen geçici node_modules dizinlerini veya önbelleğe alınmış başka dosyaları temizlemediğini gördük. Bu durum, her seferinde neredeyse sıfırdan bir kurulum yapılmasına neden oluyordu.
Sorunu çözmek için actions/cache action’ını daha dikkatli yapılandırdık ve restore-keys seçeneğini kullanarak, tam anahtar eşleşmediğinde bile benzer anahtarlara sahip önbellekleri geri yüklemeye çalıştık. Ayrıca, runner’lar arasındaki tutarlılığı sağlamak için Docker tabanlı build ortamları kullandık ve her build’ın temiz bir ortamda başladığından emin olduk. Bu değişikliklerle npm ci süresi belirgin şekilde kısaldı; her build’da kazanılan dakikalar, aylık toplamda kayda değer bir runner süresi ve dolayısıyla ciddi bir maliyet tasarrufu anlamına geldi.
Gelişmiş Cache Stratejileri: Build Kita ve Nx
Bazı modern build araçları ve monorepo yönetim sistemleri, daha gelişmiş cache stratejileri sunar. Örneğin, Nx gibi araçlar, sadece bağımlılıkları değil, aynı zamanda build’ın ara çıktılarını da akıllıca cache’ler. Nx, projenizdeki her bir paketin veya uygulamanın bağımlılıklarını ve çıktısını takip eder. Bir paket değiştiğinde, sadece o paket ve ona bağımlı olan diğer paketler yeniden derlenir. Geri kalan her şey cache’ten alınır.
Bu yaklaşım, monorepo’larda devrim niteliğindedir. Bir değişiklik yaptığınızda, tüm monorepo’nun yeniden derlenmesi yerine, sadece etkilenen küçük bir kısım yeniden oluşturulur. Bu, build sürelerini inanılmaz derecede azaltır ve aynı zamanda cache’in doğruluğunu da artırır. Nx, kendi içinde bir distributed cache çözümü sunar ve bu cache’i uzak sunuculara (örneğin, S3, GCS) veya CI/CD ortamlarına senkronize edebilir.
Sonuç: Cache Yönetimi Bir Optimizasyon Fırsatıdır
CI/CD pipeline’larında build cache yönetimi, göz ardı edilemeyecek kadar önemli bir optimizasyon alanıdır. Sadece build sürelerini kısaltmakla kalmaz, aynı zamanda altyapı maliyetlerini düşürür ve geliştiricilerin daha verimli çalışmasını sağlar. Doğru stratejilerle, zaman ve para tasarrufu elde etmek mümkündür.
Unutmayın, cache yönetimi tek seferlik bir iş değildir. Projeleriniz geliştikçe, bağımlılıklarınız değiştikçe ve build süreçleriniz karmaşıklaştıkça, cache politikalarınızı da düzenli olarak gözden geçirmeniz ve güncellemeniz gerekir. Bu sürekli iyileştirme süreci, pipeline’larınızın her zaman en verimli şekilde çalışmasını sağlayacaktır.