ETag ve Cache-Control ile API Önbellekleme: Koşullu İstekler Veri Yükünüzü Nasıl Azaltır

Cache-Control başlığının ve ETag doğrulamanın tekrarlayan API çağrılarını nasıl 304 yanıtlarına dönüştürdüğünü, If-Match ile kayıp güncellemelerin nasıl önlendiğini ve yük boyutunun nasıl azaltıldığını öğrenin.

Ashley Goolam

Ashley Goolam

31 August 2026

ETag ve Cache-Control ile API Önbellekleme: Koşullu İstekler Veri Yükünüzü Nasıl Azaltır

Kurumsal İçin Apidog

Şirket İçi (On-Premises) Dağıtım

SSO ve RBAC

SOC 2 Uyumlu

Apidog Enterprise'ı Keşfedin

API'niz muhtemelen aynı JSON'ı günde binlerce kez gönderiyor. Bir istemci GET /v1/products/42 isteğinde bulunuyor, 18 KB geri alıyor, beş dakika sonra tekrar istiyor ve aynı 18 KB'yi alıyor. Hiçbir şey değişmedi. Bant genişliği, serileştirme ve veritabanı okuması için yine de ödeme yaptınız.

HTTP bu sorunu zaten çözdü. Cache-Control başlığı, istemcilere bir yanıtın ne kadar süreyle güncel kalacağını söyler. ETag başlığı, değişip değişmediğini kontrol etmek için onlara bir parmak izi verir. Birlikte, tekrarlanan istekleri boş gövdeli 304 Not Modified yanıtlarına dönüştürürler ve bonus olarak yazma işlemlerinizi kayıp güncellemelerden koruyabilirler. Aynı fikirler istemci tarafı desenlerini de besler; eğer React'ta API yanıtlarını önbelleğe alma kılavuzumuzu okuduysanız, bu o hikayenin sunucu tarafıdır.

Bu kılavuz, HTTP önbelleklemenin üç katmanını adım adım inceleyecek, 304 gidiş-dönüşünü gösterecek, no-cache ile no-store arasındaki farkı açıklayacak ve çalışan Express koduyla sona erecektir. Tüm bunları Apidog'da koşullu başlıklar göndererek ve 304'ü kendiniz doğrulayarak nasıl kontrol edeceğinizi de göreceksiniz.

düğme

HTTP önbelleklemenin üç katmanı

API'ler için HTTP önbellekleme üç ayrı karara ayrılır. Ekipler, bunları bir araya getirdiklerinde sorun yaşarlar.

Katman 1: Güncellik (Freshness). Bir istemci, size hiç sormadan bir yanıtı ne kadar süreyle yeniden kullanabilir? Bu Cache-Control: max-age=60'tır. 60 saniye boyunca istemci, önbelleğe alınmış kopyayı yerel olarak sunar. Sıfır ağ trafiği. Bu, mümkün olan en ucuz önbellek isabetidir ve aynı zamanda en risklisidir, çünkü istemci zamanlayıcı süresi dolana kadar bir değişikliği algılayamaz.

Katman 2: Doğrulama (Validation). Yanıt eskidiğinde, istemcinin onu yeniden indirmesi gerekmez. Daha önce verdiğiniz parmak izini göndererek “bu değişti mi?” diye sorar. Kaynak değişmemişse, 304 Not Modified ve boş bir gövde ile yanıt verirsiniz. If-None-Match ile ETag bunun kesin versiyonudur; If-Modified-Since ile Last-Modified, bir saniye hassasiyetine sahip daha eski, zaman damgası tabanlı versiyondur.

Katman 3: Geçersiz Kılma (Invalidation). Veriler değiştiğinde, eski kopyalar nasıl sona erer? Özel istemci önbellekleri, max-age aracılığıyla kendi kendine sona erer. Paylaşılan önbellekler ve CDN'ler, açık temizlemelere (purges), kısa TTL'lere veya eskimişliği sınırlayan stale-while-revalidate gibi direktiflere ihtiyaç duyar.

Güncellik en çok tasarrufu sağlar, doğrulama güncelliğin kaçırdığı her şeyi yakalar ve geçersiz kılma her ikisini de dürüst tutar. Çoğu API'nin üçüne de ihtiyacı vardır.

304 Not Modified gidiş-dönüşü nasıl çalışır?

İşte bir ürün uç noktası için tam döngü, adım adım.

İlk istek. İstemcide hiçbir şey önbelleğe alınmamış:

GET /v1/products/42 HTTP/1.1
Host: api.example.com

İlk yanıt. Gövdeyi ve önbellekleme meta verilerini döndürürsünüz:

HTTP/1.1 200 OK
Cache-Control: private, max-age=60
ETag: "33a64df551425fcc55e4d42a148795d9f2"
Content-Type: application/json
Content-Length: 18432

İstemci, gövdeyi ve ETag'i saklar. Sonraki 60 saniye boyunca sizinle hiç iletişim kurmaz.

İkinci istek, 60 saniye sonra. Kopya eskidi, bu yüzden istemci yeniden doğrular:

GET /v1/products/42 HTTP/1.1
Host: api.example.com
If-None-Match: "33a64df551425fcc55e4d42a148795d9f2"

İkinci yanıt, değişmeyen kaynak. Sunucunuz, gelen ETag'i mevcut olanla karşılaştırır. Eşleşiyorlar, bu yüzden:

HTTP/1.1 304 Not Modified
Cache-Control: private, max-age=60
ETag: "33a64df551425fcc55e4d42a148795d9f2"

Gövde yok. 18 KB yerine, yanıt birkaç yüz baytlık başlıklardan oluşur. İstemci, önbelleğe alınmış kopyasını 60 saniye daha güncel olarak işaretler ve sunar. Ürün değişmiş olsaydı, yeni gövde ve yeni bir ETag ile normal bir 200 dönerdiniz. Durum kodunun kendisini 304 Not Modified açıklayıcımızda daha ayrıntılı olarak ele aldık; kısa versiyonu, 304'ün bir önbellek talimatı olduğu, bir hata olmadığıdır.

Ekonomisi basittir. Koşullu bir GET yine de bir gidiş-dönüş ve mevcut ETag'i hesaplayan herhangi bir iş maliyeti getirir. Ortadan kaldırdığı şey, yük transferi ve istemci tarafı yeniden ayrıştırmasıdır. Mobil istemciler tarafından sorgulanan büyük liste uç noktaları için bu, API çıkışını rutin olarak %60 ila %90 oranında azaltır.

API'ler için önemli olan Cache-Control direktifleri

Cache-Control'un bir düzineden fazla direktifi vardır. JSON API'leri için, beşi ağırlığın çoğunu taşır.

no-store'a karşı no-cache. Bu, üretimdeki API'lerde en yaygın önbellekleme hatasıdır ve her iki yönde de çalışır. no-store, “bunu asla hiçbir önbelleğe yazma” anlamına gelir. Gerçekten hassas yükler için kullanın: jetonlar, bankacılık verileri, kalıcı olmaması gereken kişisel bilgiler (PII). no-cache, kulağa geldiğinin neredeyse tam tersi anlama gelir: önbellekler yanıtı saklayabilir, ancak her yeniden kullanımdan önce kaynakla yeniden doğrulamaları gerekir. Bir ETag ile eşleştirildiğinde, no-cache, istemcilerin asla eski verileri göstermemesini garantilerken her istekte 304 tasarrufu sağlar. Her şeye “güvenli olmak için” no-store etiketi yapıştıran ekipler, koşullu istekleri tamamen devre dışı bırakıyor ve her çağrıda tam yük maliyetini ödüyor.

private. Yanıtı yalnızca son kullanıcının istemcisi tarafından önbelleğe alınabilir olarak işaretler, asla paylaşılan önbellekler veya CDN'ler tarafından değil. Kullanıcıya göre değişen herhangi bir yanıt, ki bu çoğu kimliği doğrulanmış API trafiğidir, private içermelidir. Onsuz, yanlış yapılandırılmış bir proxy, bir kullanıcının hesap verilerini başka birine sunabilir.

max-age. Saniye cinsinden güncellik ömrü. API'ler için küçük düşünün: 30 ila 300 saniye çoğu okuma uç noktasını kapsar. Bir gün boyunca istekleri ortadan kaldırmaya çalışmıyorsunuz; ani yüklenmeleri ve sorgulama döngülerini absorbe etmeye çalışıyorsunuz.

stale-while-revalidate. Pragmatik orta yol. Cache-Control: max-age=60, stale-while-revalidate=300 önbelleklere şunu söyler: eski kopyayı 5 dakika daha sun, ancak arka planda yenile. Kullanıcılar anında yanıt alır; kaynağınız kısa süre sonra güncellenir. Cloudflare ve Fastly gibi CDN'ler, tarayıcılar gibi bunu destekler.

Kimliği doğrulanmış bir okuma uç noktası için mantıklı bir varsayılan şuna benzer:

Cache-Control: private, max-age=60, stale-while-revalidate=120
ETag: "9f8b2c41aa73e0d5"

Tam davranışsal spesifikasyon, RFC 7234'ün yerini alan kesin HTTP önbellekleme belgesi olan RFC 9111'de bulunur. Bir CDN sizi şaşırtacak şekilde davrandığında, yanıt o RFC'de bulunur.

Güçlü ve Zayıf ETag'ler

Bir ETag iki çeşide sahiptir ve W/ öneki onları ayırır.

Bir güçlü ETag (ETag: "33a64df551425fcc") bayt bazında eşitlik vaat eder. Aynı güçlü ETag'e sahip iki yanıt özdeştir, bu da güçlü ETag'leri bayt aralığı istekleri için güvenli ve If-Match ile eşzamanlılık kontrolü için gerekli kılar.

Bir zayıf ETag (ETag: W/"33a64df551425fcc") anlamsal eşitlik vaat eder. Baytlar farklılık gösterebilir, belki alan sıralaması değişti veya bir zaman damgası alanı ilerledi, ancak anlam aynıdır, bu nedenle bir önbellek kopyasını saklayabilir.

Bunun sizi vurduğu yer: sıkıştırma ara yazılımı. Nginx ve bazı framework'ler, bir yanıtı anında sıkıştırdıklarında güçlü ETag'leri zayıf olanlara yeniden yazar, çünkü sıkıştırılmış baytlar artık orijinaliyle eşleşmez. Eşzamanlılık kontrolleriniz bir proxy'nin arkasında gizemli bir şekilde başarısız oluyorsa, uygulama sunucunuz yanıtı gönderdiğinde orada olmayan bir W/ öneki arayın.

Sıkıştırılmamış gövde üzerinde hesaplanan güçlü ETag'leri varsayılan olarak kullanın. Yalnızca aynı verinin farklı gösterimlerini bilerek sunduğunuzda zayıf olanları kullanın.

ETag Oluşturma: Gövde Hash'i ve Sürüm Sütunu

İki strateji baskındır ve doğru olan maliyetin nerede olduğuna bağlıdır.

Yanıt gövdesinin hash'i. Yanıtı serileştirin, hash'leyin (MD5 veya SHA-1 burada uygundur; bu bir parmak izidir, güvenlik sınırı değil) ve tırnak içine alın. Yapısı gereği doğrudur ve şema değişikliklerine ihtiyaç duymaz. Yakalanması gereken nokta: 304'ler de dahil olmak üzere her istekte tam yanıtı oluşturursunuz. Bant genişliğinden tasarruf edersiniz, ancak işlem gücünden veya veritabanı yükünden tasarruf etmezsiniz.

Sürüm sütunu veya updated_at. ETag'i kolayca alabileceğiniz verilerden türetin: satırın sürüm sayacından ETag: "42-v17" veya updated_at'in hash'i. Artık koşullu bir istek, tam serileştirme yerine bir dizinli arama maliyeti getirir. Yakalanması gereken nokta: sürüm, birleştirilmiş tablolardaki değişiklikler de dahil olmak üzere yanıtı etkileyen her değişiklikte yükseltilmelidir. Birini kaçırırsanız eski 304'ler sunarsınız, bu da görünmez olduğu için en kötü önbellekleme hatasıdır.

Gövde hash'lemesiyle başlayın. Varsayılan olarak doğrudur. Profil oluşturma serileştirme maliyetinin önemli olduğunu gösterdiğinde, yoğun uç noktaları sürüm tabanlı ETag'lere taşıyın.

İyimser Eşzamanlılık için ETag'ler: If-Match ve 412

Okumalarda bant genişliğinden tasarruf sağlayan aynı parmak izi, yazmalarda kayıp güncellemeleri önler.

Kayıp güncelleme sorunu: iki yönetici aynı anda ürün 42'yi yükler. Yönetici A fiyatı değiştirir ve kaydeder. Yönetici B bir yazım hatasını düzeltir ve 30 saniye sonra kaydeder, A'nın fiyat değişikliğini B'nin yüklediği eski fiyatla üzerine yazar. Kimse bir hata görmez. Veriler sessizce yanlıştır.

Çözüm, her güncellemeyi istemcinin en son gördüğü sürüme koşullu hale getirmektir:

PUT /v1/products/42 HTTP/1.1
If-Match: "33a64df551425fcc55e4d42a148795d9f2"
Content-Type: application/json

Sunucu, If-Match'i kaynağın mevcut ETag'i ile karşılaştırır. Eşleşme: güncellemeyi uygula, yeni bir ETag ile 200 dön. Eşleşme yok, başkası önce davrandı: 412 Precondition Failed ile reddet ve verilere dokunma. İstemci daha sonra yeniden getirir, değişikliklerini güncel sürüme tekrar uygular ve yeniden dener. Katı API'ler daha ileri gider ve If-Match'i atlayan herhangi bir PUT işleminde 428 Precondition Required döndürerek güvenlik kontrolünü zorunlu hale getirir.

ETag'ler var olduktan sonra eklemek size neredeyse hiçbir şeye mal olmaz ve sessiz bir veri bozulma hatasını açık, yeniden denenebilir bir HTTP durumuna dönüştürür.

CDN'ler ve proxy'ler bu başlıklarla ne yapar?

Paylaşılan önbellekler, kaynağınız ile istemcileriniz arasında yer alır ve aynı başlıkları kendi kurallarına göre okur.

Express örneği: ETag döndürme ve If-None-Match'i işleme

Express kendi başına zayıf ETag'ler belirler, ancak manuel işleme size güçlü ETag'ler ve 412 yazma yolunu sağlar:

import crypto from "node:crypto";
import express from "express";

const app = express();
app.use(express.json());

function etagFor(payload) {
  const hash = crypto.createHash("sha1")
    .update(JSON.stringify(payload))
    .digest("hex");
  return `"${hash}"`;
}

app.get("/v1/products/:id", async (req, res) => {
  const product = await db.products.find(req.params.id);
  const etag = etagFor(product);

  res.set("Cache-Control", "private, max-age=60, stale-while-revalidate=120");
  res.set("ETag", etag);

  if (req.get("If-None-Match") === etag) {
    return res.status(304).end();   // fingerprint matches: no body
  }
  res.json(product);
});

app.put("/v1/products/:id", async (req, res) => {
  const product = await db.products.find(req.params.id);
  const currentEtag = etagFor(product);
  const ifMatch = req.get("If-Match");

  if (!ifMatch) {
    return res.status(428).json({ error: "If-Match header required" });
  }
  if (ifMatch !== currentEtag) {
    return res.status(412).json({ error: "Resource changed since you fetched it" });
  }

  const updated = await db.products.update(req.params.id, req.body);
  res.set("ETag", etagFor(updated));
  res.json(updated);
});

304 dalının hala Cache-Control ve ETag başlıklarını gönderdiğine dikkat edin. RFC 9111'e göre, bir 304 depolanan yanıtın meta verilerini günceller, bu nedenle istemcinin kopyasını güncel tutması için gereken her şeyi yeniden gönderin.

Apidog'da önbellekleme davranışını doğrulama

Doğru görünen kod, ara yazılım ve proxy'ler devreye girdiğinde yine de yanlış önbellekleme yapabilir. Kod seviyesinde değil, HTTP seviyesinde test edin.

Apidog'da, manuel kontrol yaklaşık bir dakika sürer:

  1. GET /v1/products/42 gönderin ve yanıt başlıkları panelini açın. ETag ve Cache-Control'ün mevcut olduğunu ve ETag'in tırnak içine alındığını doğrulayın. ETag değerini kopyalayın.
  2. Aynı istekte, kopyalanan değerle birlikte `If-None-Match` başlığını ekleyin ve tekrar gönderin. Boş bir gövde ile 304 almalısınız. Hala 200 alıyorsanız, doğrulama katmanınız parmak izlerini karşılaştırmıyor demektir.
  3. Kaydı değiştirin, yeniden gönderin ve yeni bir ETag ile tekrar 200 aldığınızı doğrulayın.

Bunun her dağıtımdan sonra çalışmaya devam etmesi için, aynı akışı bir test senaryosuna bağlayın. İki isteği zincirleyin: ilki ETag'i yanıt başlıklarından bir değişkene çıkarır, ikincisi If-None-Match olarak geri gönderir ve durumun 304'e eşit olduğunu ve gövdenin boş olduğunu doğrular. Yazma yolu için üçüncü bir adım ekleyin: `"deadbeefcafe1234"` gibi kasten eski bir If-Match değeriyle bir PUT gönderin ve 412'yi doğrulayın. API iddiaları kılavuzumuz, durum kodları ve başlıklar için iddia sözdizimini kapsar.

Bu senaryoyu CI'da çalıştırın ve ETag'lerinizi sessizce çıkaran bir ara yazılım yükseltmesi, bant genişliği faturası yerine başarısız bir işlem hattı haline gelir. Apidog'u ücretsiz indirin ve senaryoyu kendi uç noktalarınıza karşı oluşturun; okumak, tıklayıp bir araya getirmekten daha uzun sürer.

Sıkça Sorulan Sorular

no-cache ve no-store arasındaki fark nedir?

no-store önbelleğe almayı tamamen yasaklar: diske veya belleğe hiçbir şey yazılmaz, bu nedenle her istek tam yanıtı indirir. no-cache depolamaya izin verir ancak her yeniden kullanımdan önce yeniden doğrulamayı zorlar, bu nedenle bir ETag ile eşleştirildiğinde hala 304 yanıtı ve yük tasarrufu sağlar. no-store'u yalnızca hassas veriler için kullanın. Her yerde kullanmak, bir API ekibinin yapabileceği en pahalı Cache-Control hatasıdır.

ETag'ler POST ile çalışır mı?

Çoğunlukla hayır ve tasarımdan dolayı. ETag'ler bir URL'deki kaynağın durumunu tanımlar ve POST genellikle kararlı durumu okumak yerine yeni bir şeyler oluşturur. Önbellekler pratikte POST yanıtlarını önbelleğe almaz. Yazmalar için önemli olan koşullu başlıklar, ETag'in kayıp güncellemeleri engellediği PUT, PATCH ve DELETE üzerindeki If-Match'tir. POST yanıtlarını önbelleğe alma eğilimindeyseniz, bu genellikle işlemin bir GET olması gerektiğinin bir işaretidir.

304 yanıtı API'mi daha hızlı hale getirir mi?

Aktarımları küçültür, ki bu aynı şey değildir. Sunucu hala isteği alır, kimlik doğrulamayı çalıştırır ve mevcut ETag'i hesaplar, bu nedenle kaynak CPU tasarrufları, bu parmak izini ne kadar ucuza türettiğinize bağlıdır. Kazançlar bant genişliğinde, mobil pil ömründe ve yavaş ağlarda oluşturma süresinde (time-to-render) ortaya çıkar. Önce ve sonra ölçün; API performans testi kılavuzumuz, farkı tahmin etmek yerine kanıtlayabilmeniz için gecikme (latency) ve verim (throughput) karşılaştırması yapmayı gösterir.

ETag mi yoksa Last-Modified mi kullanmalıyım?

Mümkün olduğunda ikisini de gönderin. ETag daha kesindir: saniye altı değişiklikleri ve bir zaman damgasının gözden kaçırdığı içerik seviyesi farklılıklarını yakalar ve her ikisi de geldiğinde If-None-Match, If-Modified-Since'e göre öncelik alır. Last-Modified, eski istemciler için bir geri dönüş olarak ve bazı önbelleklerin güncelliği tahmin etmek için kullandığı bir buluşsal yöntem olarak kullanışlı olmaya devam eder. Yalnızca birini gönderiyorsanız, ETag'i gönderin.

API Tasarım-Öncelikli Yaklaşımı Apidog'da Uygulayın

API'leri oluşturmanın ve kullanmanın daha kolay yolunu keşfedin