AWS CloudFront Önbellek Politikası Yapılandırması
CDN dünyasında en çok can yakan konulardan biri önbellekleme stratejisidir. Yanlış yapılandırılmış bir cache politikası, hem kullanıcı deneyimini mahvedebilir hem de AWS faturanı beklenmedik seviyelere taşıyabilir. CloudFront’u sadece “origin’in önüne koy, çalışsın” mantığıyla kuranların er geç başı belaya giriyor. Bu yazıda CloudFront önbellek politikalarını sıfırdan, gerçek dünya senaryolarıyla birlikte ele alacağız.
CloudFront Önbellek Mimarisini Anlamak
CloudFront, içeriği edge location’larda önbelleğe alarak son kullanıcıya origin sunucusundan değil, coğrafi olarak en yakın noktadan sunar. Ancak bu mekanizmanın düzgün çalışması için CloudFront’a neyi, ne kadar süreyle ve hangi koşullarda saklayacağını söylemen gerekir.
Temel olarak üç kavramla iç içe çalışıyoruz:
- Cache Policy: Önbellekte saklama süresini ve hangi istek özelliklerinin cache key’e dahil edileceğini belirler
- Origin Request Policy: Origin’e iletilecek header, cookie ve query string bilgilerini kontrol eder
- Response Headers Policy: CloudFront’un istemciye döndürdüğü response’lara ek header ekler
Bu üçünü birbirine karıştırmak çok yaygın bir hata. Cache Policy, origin’e ne gönderileceğiyle değil, neyin önbelleğe alınacağıyla ilgilidir. Origin Request Policy ise cache key’e dahil olmayan ama origin’in ihtiyaç duyduğu bilgileri taşır.
TTL Kavramı ve Cache Key
Cache Key, CloudFront’un bir içeriği önbellekte benzersiz olarak tanımlamak için kullandığı bileşenlerdir. Varsayılan olarak yalnızca URL (host + path) cache key’in parçasıdır. Ancak query string parametreleri, belirli header’lar veya cookie’ler de cache key’e eklenebilir.
Şunu düşün: /api/products?category=electronics&user=ahmet isteğinde user parametresini cache key’e eklersen, her kullanıcı için ayrı bir cache entry oluşur ve hit rate sıfıra düşer. Bu performans felaketi demek.
TTL (Time to Live) ise önbellekteki içeriğin ne kadar süre geçerli sayılacağını belirler:
- MinTTL: Origin ne derse desin, içerik en az bu kadar süre önbellekte kalır
- DefaultTTL: Origin bir Cache-Control header’ı göndermemişse bu değer kullanılır
- MaxTTL: Origin Cache-Control gönderse bile önbellekte kalma süresi bu değeri geçemez
AWS CLI ile Cache Policy Oluşturma
AWS Console üzerinden yapılan işlemleri scriptlerle otomatize etmek hayat kurtarır. Özellikle birden fazla CloudFront dağıtımı yönetiyorsan CLI zorunlu hale gelir.
Önce mevcut cache policy’leri listeleyelim:
aws cloudfront list-cache-policies
--type custom
--query 'CachePolicyList.Items[*].{Name:CachePolicy.CachePolicyConfig.Name,Id:CachePolicy.Id}'
--output table
AWS’nin sunduğu managed policy’leri görmek için:
aws cloudfront list-cache-policies
--type managed
--query 'CachePolicyList.Items[*].{Name:CachePolicy.CachePolicyConfig.Name,Id:CachePolicy.Id}'
--output text
Şimdi bir statik site için özelleştirilmiş cache policy oluşturalım. Önce JSON dosyamızı hazırlıyoruz:
cat > static-site-cache-policy.json << 'EOF'
{
"CachePolicyConfig": {
"Name": "StaticSiteOptimized",
"Comment": "Statik site icin optimize edilmis cache politikasi",
"DefaultTTL": 86400,
"MaxTTL": 31536000,
"MinTTL": 1,
"ParametersInCacheKeyAndForwardedToOrigin": {
"EnableAcceptEncodingGzip": true,
"EnableAcceptEncodingBrotli": true,
"HeadersConfig": {
"HeaderBehavior": "none"
},
"CookiesConfig": {
"CookieBehavior": "none"
},
"QueryStringsConfig": {
"QueryStringBehavior": "whitelist",
"QueryStrings": {
"Quantity": 1,
"Items": ["v"]
}
}
}
}
}
EOF
aws cloudfront create-cache-policy
--cache-policy-config file://static-site-cache-policy.json
Bu politikada sadece v query string parametresini (versiyon parametresi) cache key’e dahil ettik. style.css?v=1.2.3 gibi URL’lerle cache busting yapabilirsiniz.
Dinamik İçerik İçin Cache Policy
API endpoint’leri için ayrı bir strateji gerekir. Çoğu durumda API response’larını önbelleğe almak istersin ama kullanıcıya özgü verileri asla karıştırmamalısın:
cat > api-cache-policy.json << 'EOF'
{
"CachePolicyConfig": {
"Name": "APIEndpointCache",
"Comment": "Public API endpoint'leri icin kisa sureli cache",
"DefaultTTL": 30,
"MaxTTL": 300,
"MinTTL": 0,
"ParametersInCacheKeyAndForwardedToOrigin": {
"EnableAcceptEncodingGzip": true,
"EnableAcceptEncodingBrotli": false,
"HeadersConfig": {
"HeaderBehavior": "whitelist",
"Headers": {
"Quantity": 1,
"Items": ["Accept"]
}
},
"CookiesConfig": {
"CookieBehavior": "none"
},
"QueryStringsConfig": {
"QueryStringBehavior": "whitelist",
"QueryStrings": {
"Quantity": 3,
"Items": ["page", "limit", "sort"]
}
}
}
}
}
EOF
aws cloudfront create-cache-policy
--cache-policy-config file://api-cache-policy.json
Burada Accept header’ını ekledik çünkü aynı endpoint hem JSON hem XML döndürebilir. page, limit, sort parametreleri ise sayfalama ve sıralama için kritik.
Origin Request Policy ile Cache Key’i Genişletmek
Cache key’e dahil olmayan ama origin’in görmesi gereken bilgiler için Origin Request Policy kullanıyoruz. Klasik örnek: kullanıcının dilini cache key’e eklemeden origin’e iletmek:
cat > origin-request-policy.json << 'EOF'
{
"OriginRequestPolicyConfig": {
"Name": "ForwardLanguageAndAuth",
"Comment": "Dil ve auth bilgisini origin'e ilet, cache key'e ekleme",
"HeadersConfig": {
"HeaderBehavior": "whitelist",
"Headers": {
"Quantity": 2,
"Items": [
"Accept-Language",
"Authorization"
]
}
},
"CookiesConfig": {
"CookieBehavior": "whitelist",
"Cookies": {
"Quantity": 1,
"Items": ["session_id"]
}
},
"QueryStringsConfig": {
"QueryStringBehavior": "all"
}
}
}
EOF
aws cloudfront create-origin-request-policy
--origin-request-policy-config file://origin-request-policy.json
Dikkat: Authorization header’ını cache key’e eklemeden origin’e iletmek tehlikeli olabilir. Bunu yalnızca CloudFront’un private content erişimini kontrol etmediği senaryolarda yap.
Distribution’a Policy Atama
Policy’leri oluşturduktan sonra bunları distribution’a bağlamamız gerekiyor. Mevcut bir distribution’ı güncellemek için önce config’i çekip değiştirmek gerekir:
# Mevcut distribution config'i al
DIST_ID="E1234567890ABC"
aws cloudfront get-distribution-config
--id $DIST_ID
--query 'DistributionConfig' > dist-config.json
# ETag degerini al (guncelleme icin gerekli)
ETAG=$(aws cloudfront get-distribution-config
--id $DIST_ID
--query 'ETag'
--output text)
echo "ETag: $ETAG"
Cache policy ID’lerini aldıktan sonra belirli bir behavior’a atayabiliriz. Bu işlemi daha pratik hale getiren bir script:
#!/bin/bash
# update-cache-policy.sh
DIST_ID="${1:-E1234567890ABC}"
CACHE_POLICY_ID="${2:-658327ea-f89d-4fab-a63d-7e88639e58f6}"
PATH_PATTERN="${3:-/api/*}"
# Mevcut config ve ETag'i al
CONFIG=$(aws cloudfront get-distribution-config --id "$DIST_ID")
ETAG=$(echo "$CONFIG" | jq -r '.ETag')
# DistributionConfig'i cikart ve cache policy'yi guncelle
echo "$CONFIG" | jq --arg policy_id "$CACHE_POLICY_ID"
--arg path "$PATH_PATTERN"
'.DistributionConfig.CacheBehaviors.Items[] |=
if .PathPattern == $path
then .CachePolicyId = $policy_id | del(.ForwardedValues)
else . end' |
jq '.DistributionConfig' > updated-config.json
# Distribution'i guncelle
aws cloudfront update-distribution
--id "$DIST_ID"
--distribution-config file://updated-config.json
--if-match "$ETAG"
echo "Distribution guncellendi, deploy basliyor..."
Cache Invalidation ile Önbelleği Temizleme
Deployment sonrası içeriği güncellemen gerektiğinde invalidation kullanırsın. Ancak her deployment’ta tüm cache’i silmek hem maliyetli hem de CDN’i işlevsiz kılar. Akıllıca kullanmak gerekir:
#!/bin/bash
# smart-invalidation.sh
# Sadece degisen dosyalari invalidate et
DIST_ID="E1234567890ABC"
DEPLOY_PATH="${1:-/}"
# Tek dosya invalidasyonu
aws cloudfront create-invalidation
--distribution-id "$DIST_ID"
--paths "/index.html" "/manifest.json"
# Belirli bir dizini invalidate et
aws cloudfront create-invalidation
--distribution-id "$DIST_ID"
--paths "/static/js/*" "/static/css/*"
# Invalidation durumunu takip et
INVALIDATION_ID=$(aws cloudfront create-invalidation
--distribution-id "$DIST_ID"
--paths "/*"
--query 'Invalidation.Id'
--output text)
echo "Invalidation basladi: $INVALIDATION_ID"
# Tamamlanana kadar bekle
aws cloudfront wait invalidation-completed
--distribution-id "$DIST_ID"
--id "$INVALIDATION_ID"
echo "Invalidation tamamlandi!"
İlk 1000 invalidation path’i ücretsiz, sonrası ücretli. Bu yüzden wildcard (/*) yerine mümkün olduğunca spesifik path’ler kullan. Büyük uygulamalarda dosyalara versiyon hash’i eklemek (webpack gibi araçlarla) invalidation ihtiyacını tamamen ortadan kaldırır.
Gerçek Dünya Senaryosu: E-ticaret Sitesi
Bir e-ticaret platformu için karmaşık bir önbellek stratejisi kuralım. Farklı content type’ları için farklı davranışlar tanımlayacağız:
# 1. Urun gorselleri icin agresif cache (1 yil)
cat > product-images-policy.json << 'EOF'
{
"CachePolicyConfig": {
"Name": "ProductImages-LongCache",
"DefaultTTL": 31536000,
"MaxTTL": 31536000,
"MinTTL": 31536000,
"ParametersInCacheKeyAndForwardedToOrigin": {
"EnableAcceptEncodingGzip": true,
"EnableAcceptEncodingBrotli": true,
"HeadersConfig": { "HeaderBehavior": "none" },
"CookiesConfig": { "CookieBehavior": "none" },
"QueryStringsConfig": {
"QueryStringBehavior": "whitelist",
"QueryStrings": {
"Quantity": 2,
"Items": ["w", "h"]
}
}
}
}
}
EOF
# 2. Urun listeleme sayfasi icin orta sureli cache (5 dakika)
cat > product-list-policy.json << 'EOF'
{
"CachePolicyConfig": {
"Name": "ProductListing-ShortCache",
"DefaultTTL": 300,
"MaxTTL": 600,
"MinTTL": 0,
"ParametersInCacheKeyAndForwardedToOrigin": {
"EnableAcceptEncodingGzip": true,
"EnableAcceptEncodingBrotli": true,
"HeadersConfig": { "HeaderBehavior": "none" },
"CookiesConfig": { "CookieBehavior": "none" },
"QueryStringsConfig": {
"QueryStringBehavior": "whitelist",
"QueryStrings": {
"Quantity": 4,
"Items": ["category", "page", "sort", "brand"]
}
}
}
}
}
EOF
# Policy'leri olustur
aws cloudfront create-cache-policy --cache-policy-config file://product-images-policy.json
aws cloudfront create-cache-policy --cache-policy-config file://product-list-policy.json
echo "E-ticaret cache policy'leri olusturuldu"
Bu senaryoda ürün görsellerine tam 1 yıl TTL atadık. Bunun çalışması için görsel URL’lerinde hash veya versiyon parametresi olması şart. Aksi halde güncellenen bir görseli kullanıcılar 1 yıl boyunca göremez.
Cache Hit Rate’i İzleme
CloudWatch ile cache performansını takip etmek operasyonel açıdan kritik:
# Cache hit rate metriklerini cek
aws cloudwatch get-metric-statistics
--namespace AWS/CloudFront
--metric-name CacheHitRate
--dimensions Name=DistributionId,Value=E1234567890ABC
Name=Region,Value=Global
--start-time "$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ)"
--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
--period 3600
--statistics Average
--query 'Datapoints[*].{Time:Timestamp,HitRate:Average}'
--output table
# Origin'e giden istek sayisini kontrol et
aws cloudwatch get-metric-statistics
--namespace AWS/CloudFront
--metric-name Requests
--dimensions Name=DistributionId,Value=E1234567890ABC
Name=Region,Value=Global
--start-time "$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)"
--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
--period 300
--statistics Sum
--output table
Cache hit rate’in yüzde 70’in altında kalması ciddi bir alarm sinyali. Bu durumda cache key’ini incelemeye başlamalısın. Genellikle gereksiz yere dahil edilmiş cookie veya header’lar soruna yol açıyor.
Sık Yapılan Hatalar ve Çözümleri
Yıllar içinde gördüğüm en yaygın hataları ve çözümlerini paylaşayım:
Cookie Flood Problemi: Tüm cookie’leri cache key’e eklemek (CookieBehavior: all) her kullanıcı için benzersiz cache entry oluşturur. Analitik araçların eklediği _ga, _fbp gibi cookie’ler bu durumu tetikler. Çözüm: Yalnızca ihtiyaç duyulan cookie’leri whitelist’e al.
Vary Header Karmaşası: Origin sunucusu Vary: Accept-Encoding, User-Agent gibi geniş bir Vary header’ı dönüyorsa CloudFront her User-Agent için ayrı içerik önbellekler. Bu mobile, tablet ve desktop için üç ayrı cache entry anlamına gelir. Çözüm: Origin’de Vary header’ını sadece Accept-Encoding ile sınırla.
Cache-Control’u Olmayan Response’lar: Origin’den Cache-Control header’ı gelmeyen response’lar DefaultTTL süresince önbelleklenir. Bu bazen hassas içeriklerin önbelleğe alınmasına neden olabilir. Çözüm: Her response’ta açıkça Cache-Control tanımla.
ForwardedValues ve CachePolicyId Çakışması: Eski CloudFront konfigürasyonlarında ForwardedValues kullanılırdı. Yeni CachePolicyId ile birlikte kullanılamaz, ikisinden birini seçmen gerekir. API ile güncelleme yaparken bu çakışmayı gösteren InvalidArgument hatasıyla sık karşılaşılır.
Query String Sıralaması: ?page=1&sort=price ile ?sort=price&page=1 CloudFront için farklı URL’lerdir ve iki ayrı cache entry oluşur. CloudFront’ta query string normalization açık değilse bu hit rate’i düşürür. Cache Policy’de QueryStringBehavior: whitelist kullanarak yalnızca bilinen parametreleri dahil etmek kısmen bu sorunu çözer.
Terraform ile Infrastructure as Code
Production ortamlarında CloudFront konfigürasyonunu Terraform ile yönetmek bakımı kolaylaştırır:
# Terraform ile cache policy olusturma
cat > cloudfront-cache.tf << 'EOF'
resource "aws_cloudfront_cache_policy" "static_assets" {
name = "StaticAssetsPolicy"
comment = "Statik dosyalar icin uzun sureli cache"
default_ttl = 86400
max_ttl = 31536000
min_ttl = 1
parameters_in_cache_key_and_forwarded_to_origin {
enable_accept_encoding_brotli = true
enable_accept_encoding_gzip = true
cookies_config {
cookie_behavior = "none"
}
headers_config {
header_behavior = "none"
}
query_strings_config {
query_string_behavior = "whitelist"
query_strings {
items = ["v", "hash"]
}
}
}
}
output "static_assets_cache_policy_id" {
value = aws_cloudfront_cache_policy.static_assets.id
}
EOF
# Plan ve apply
terraform plan -target=aws_cloudfront_cache_policy.static_assets
terraform apply -target=aws_cloudfront_cache_policy.static_assets -auto-approve
Terraform state’i ile policy ID’leri otomatik olarak takip edilir ve diğer resource’larda referans olarak kullanılabilir.
Performans Test Senaryosu
Önbellek konfigürasyonunu canlıya almadan önce test etmek için basit bir yaklaşım:
#!/bin/bash
# cache-test.sh - Cache davranisini test et
CF_URL="https://d1234567890.cloudfront.net"
TEST_PATH="/static/js/main.abc123.js"
echo "=== Ilk Istek (MISS bekleniyor) ==="
curl -sI "${CF_URL}${TEST_PATH}" | grep -E "x-cache|age|cache-control"
sleep 1
echo ""
echo "=== Ikinci Istek (HIT bekleniyor) ==="
curl -sI "${CF_URL}${TEST_PATH}" | grep -E "x-cache|age|cache-control"
echo ""
echo "=== Farkli Edge'den Test (Pragma: no-cache) ==="
curl -sI -H "Pragma: no-cache" "${CF_URL}${TEST_PATH}" | grep -E "x-cache|age|cache-control"
# Cache HIT rate kontrolu
echo ""
echo "=== Bircok istek ile hit rate testi ==="
HIT_COUNT=0
TOTAL=20
for i in $(seq 1 $TOTAL); do
RESULT=$(curl -sI "${CF_URL}${TEST_PATH}" | grep "x-cache" | head -1)
if echo "$RESULT" | grep -qi "hit"; then
((HIT_COUNT++))
fi
done
echo "Hit Rate: ${HIT_COUNT}/${TOTAL} = $(echo "scale=0; ${HIT_COUNT}*100/${TOTAL}" | bc)%"
Response’daki X-Cache: Hit from cloudfront header’ı önbellekten geldiğini, Miss from cloudfront ise origin’e gittiğini gösterir.
Sonuç
CloudFront önbellek politikası, CDN’den gerçek anlamda fayda sağlamanın temel koşuludur. Yanlış yapılandırılmış bir cache policy ya hiçbir şeyi önbelleğe almaz ve origin’ini boğar, ya da yanlış içerikleri önbelleğe alarak güvenlik sorunlarına kapı açar.
Özetlemek gerekirse en kritik pratik kurallar şunlar:
- Cache key’ini minimumda tut: Yalnızca önbelleklenen içeriği gerçekten değiştiren parametreleri dahil et
- Statik varlıklar için agresif TTL kullan, dosya adlarına hash ekleyerek cache busting yap
- API endpoint’leri için kısa TTL değerleriyle başla, zamanla artır
- Cookie’leri mümkün olduğunca cache key’den dışarıda bırak
- Cache-Control header’larını origin’de açıkça belirle, CloudFront’un tahmin etmesine bırakma
- Cache hit rate’ini CloudWatch ile düzenli izle, yüzde 70 altı alarm eşiği olarak kabul et
- Production değişikliklerini Terraform veya CloudFormation ile yönet, console’dan yapılan ad-hoc değişikliklerden kaç
İlk kurulumu doğru yapmak sonradan yapılacak onlarca invalidation ve debug seansından çok daha az zaman alır. Biraz ön hazırlık, sonrasında saatlerce süren sorun giderme seanslarını engelleyen tek şeydir.
