HTTP Önbellekleme ile API Bant Genişliğini Azaltma: Cache-Control, ETag ve 304
API'niz muhtemelen aynı JSON'ı günde binlerce kez gönderiyor. Bir istemci GET /v1/products/42 isteğinde bulunuyor, 18 KB yanıt alıyor ve beş dakika sonra hiçbir şey değişmemiş olmasına rağmen aynı 18 KB'yi tekrar indiriyor. Bu süreçte bant genişliği, serileştirme ve veritabanı okuması için yeniden ödeme yapıyorsunuz.
HTTP bu sorunu zaten çözüyor. Cache-Control, yanıtın ne kadar süreyle güncel kabul edileceğini belirtir. ETag ise kaynağın parmak izini taşır. Birlikte, tekrarlanan istekleri boş gövdeli 304 Not Modified yanıtlarına dönüştürürler. Aynı ETag'ler, yazma işlemlerinde kayıp güncellemeleri önlemek için de kullanılabilir.
Bu kılavuzda HTTP önbelleklemenin üç katmanını, 304 gidiş-dönüşünü, no-cache ile no-store arasındaki farkı ve çalışan Express kodunu inceleyeceğiz. Ayrıca React'ta API yanıtlarını önbelleğe alma yaklaşımının sunucu tarafındaki karşılığını göreceksiniz.
HTTP önbelleklemenin üç katmanı
API önbelleklemesi üç ayrı karardan oluşur:
1. Güncellik (Freshness)
İstemci, sunucuya danışmadan yanıtı ne kadar süreyle kullanabilir?
Cache-Control: max-age=60
İstemci, 60 saniye boyunca yanıtı yerel önbelleğinden sunar. Bu, ağ trafiği oluşturmayan en ucuz önbellek isabetidir; ancak istemci, süre dolana kadar yapılan değişiklikleri göremez.
2. Doğrulama (Validation)
Yanıt eskidiğinde istemcinin tüm gövdeyi yeniden indirmesi gerekmez. Önceki parmak izini göndererek kaynağın değişip değişmediğini sorar:
If-None-Match: "33a64df551425fcc55e4d42a148795d9f2"
Kaynak değişmemişse sunucu, boş gövdeli 304 Not Modified döndürür. ETag ve If-None-Match kesin doğrulama sağlar. Last-Modified ve If-Modified-Since ise saniye hassasiyetine sahip, zaman damgası tabanlı alternatiftir.
3. Geçersiz kılma (Invalidation)
Veriler değiştiğinde eski kopyalar nasıl sona erer?
- Özel istemci önbellekleri
max-agesüresiyle otomatik olarak sona erer. - Paylaşılan önbellekler ve CDN'ler açık temizleme (
purge), kısa TTL veyastale-while-revalidategibi direktiflere ihtiyaç duyabilir. - Yanlış invalidation, güncel olmayan verilerin uzun süre sunulmasına neden olur.
Güncellik en fazla tasarrufu sağlar, doğrulama güncelliğin kaçırdığı istekleri yakalar, invalidation ise ikisini doğru tutar. Çoğu API'nin üçüne de ihtiyacı vardır.
304 Not Modified gidiş-dönüşü
Bir ürün uç noktası için tam akış şöyledir.
İlk istek
İstemcide önbelleğe alınmış bir yanıt yoktur:
GET /v1/products/42 HTTP/1.1
Host: api.example.com
İlk yanıt
Sunucu gövdeyi ve önbellekleme meta verilerini döndürür:
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 sunucuya hiç istek göndermez.
İkinci istek
60 saniye sonra önbellek kopyası eskir ve istemci yeniden doğrulama yapar:
GET /v1/products/42 HTTP/1.1
Host: api.example.com
If-None-Match: "33a64df551425fcc55e4d42a148795d9f2"
İkinci yanıt
Sunucudaki ETag aynıysa:
HTTP/1.1 304 Not Modified
Cache-Control: private, max-age=60
ETag: "33a64df551425fcc55e4d42a148795d9f2"
Bu yanıtta gövde yoktur. İstemci 18 KB yerine yalnızca birkaç yüz baytlık başlık alır, önbellekteki gövdeyi 60 saniye daha güncel kabul eder ve kullanıcıya sunar.
Kaynak değişmiş olsaydı sunucu, yeni gövde ve yeni ETag içeren normal bir 200 OK döndürürdü. 304, bir hata değil, önbelleğe alma talimatıdır. Daha fazla ayrıntı için 304 Not Modified açıklamamıza bakabilirsiniz.
Koşullu GET hâlâ bir gidiş-dönüş ve ETag hesaplama maliyeti oluşturur. Tasarruf edilen kısım yük transferi ve istemci tarafındaki yeniden ayrıştırmadır. Mobil istemcilerin sorguladığı büyük liste uç noktalarında bu yaklaşım API çıkış trafiğini rutin olarak %60–%90 azaltabilir.
API'ler için önemli Cache-Control direktifleri
Cache-Control belgelerinde çok sayıda direktif bulunur. JSON API'lerinde genellikle şu beş direktif yeterlidir.
no-store ve no-cache
Bu ikisi en sık karıştırılan direktiflerdir:
-
no-store: Yanıt hiçbir önbelleğe yazılmamalıdır. Jetonlar, bankacılık verileri ve kalıcı olmaması gereken PII için kullanın. -
no-cache: Yanıt saklanabilir; ancak her yeniden kullanımdan önce kaynakla doğrulanmalıdır.
no-cache bir ETag ile kullanıldığında istemcilerin eski veriyi göstermesini engellerken, değişmemiş yanıtlar için 304 tasarrufu sağlar. Her yanıta “güvenli olmak için” no-store eklemek ise koşullu istekleri tamamen devre dışı bırakır.
private
Yanıtın yalnızca son kullanıcının istemcisinde önbelleğe alınabileceğini belirtir:
Cache-Control: private
Kullanıcıya göre değişen, özellikle kimliği doğrulanmış API yanıtlarında kullanılmalıdır. Aksi durumda yanlış yapılandırılmış bir proxy, bir kullanıcının verisini başka bir kullanıcıya sunabilir.
max-age
Güncellik süresini saniye cinsinden belirler. Çoğu okuma uç noktası için 30–300 saniye iyi bir başlangıç aralığıdır. Amaç, istekleri bir gün boyunca ortadan kaldırmak değil; ani yüklenmeleri ve sorgulama döngülerini azaltmaktır.
stale-while-revalidate
Kullanıcıya hızlı yanıt verirken arka planda yenileme yapılmasını sağlar:
Cache-Control: max-age=60, stale-while-revalidate=300
Bu direktif, önbelleğin eski kopyayı 5 dakika daha sunmasına ve aynı anda kaynağı yenilemesine izin verir. Cloudflare ve Fastly gibi CDN'ler ile modern tarayıcılar bu davranışı destekler.
Kimliği doğrulanmış bir okuma uç noktası için makul bir varsayılan:
Cache-Control: private, max-age=60, stale-while-revalidate=120
ETag: "9f8b2c41aa73e0d5"
Tam davranışsal spesifikasyon için RFC 9111 belgesine bakın. Bu belge, RFC 7234'ün yerini alan güncel HTTP önbellekleme standardıdır.
Güçlü ve zayıf ETag'ler
ETag'ler güçlü veya zayıf olabilir. Zayıf ETag, W/ önekiyle belirtilir.
Güçlü ETag
ETag: "33a64df551425fcc"
Bayt bazında eşitlik vaat eder. Aynı güçlü ETag'e sahip iki yanıtın baytları aynıdır. Bu nedenle güçlü ETag'ler:
- Bayt aralığı istekleri için güvenlidir.
-
If-Matchile eşzamanlılık kontrolünde kullanılabilir.
Zayıf ETag
ETag: W/"33a64df551425fcc"
Anlamsal eşitlik vaat eder. Alan sırası veya zaman damgası gibi önemsiz bayt farklılıkları olabilir; ancak yanıtın anlamı aynıdır.
Sıkıştırma ara yazılımı bu ayrımı etkileyebilir. Nginx ve bazı framework'ler yanıtı sıkıştırırken güçlü ETag'i zayıf ETag'e dönüştürür; çünkü sıkıştırılmış baytlar artık özgün gövdeyle aynı değildir. Proxy arkasındaki If-Match kontrolleri beklenmedik şekilde başarısız oluyorsa, uygulama sunucusunun gönderdiği ETag'de sonradan eklenen W/ önekini kontrol edin.
Varsayılan olarak sıkıştırılmamış gövde üzerinden hesaplanan güçlü ETag kullanın. Zayıf ETag'leri yalnızca aynı verinin bilerek farklı gösterimlerini sunduğunuzda tercih edin. Daha fazla bilgi için ETag belgelerine bakabilirsiniz.
ETag oluşturma: gövde hash'i ve sürüm sütunu
İki yaygın strateji vardır.
Yanıt gövdesinin hash'i
Yanıtı serileştirip hash'leyin ve sonucu tırnak içine alın. MD5 veya SHA-1 burada kullanılabilir; bu bir parmak izidir, güvenlik sınırı değildir.
Avantajları:
- Doğrudur.
- Şema değişiklikleri gerektirmez.
Dezavantajı, 304 yanıtlarında bile tam yanıtı oluşturmanız gerekmesidir. Bant genişliğinden tasarruf edersiniz, ancak serileştirme veya veritabanı maliyetinden değil.
Sürüm sütunu veya updated_at
ETag'i doğrudan veritabanındaki bir sürümden türetin:
ETag: "42-v17"
Bu durumda koşullu istek, tam serileştirme yerine indeksli bir aramaya dönüşebilir. Ancak sürüm, yanıtı etkileyen her değişiklikte artırılmalıdır. Birleştirilmiş tablolardan gelen değişiklikleri kaçırmak eski 304 yanıtlarına ve görünmez veri tutarsızlıklarına yol açar.
Önce gövde hash'iyle başlayın. Profil oluşturma serileştirme maliyetinin önemli olduğunu gösterirse yoğun uç noktaları sürüm tabanlı ETag'lere taşıyın.
İyimser eşzamanlılık: If-Match ve 412
Okumalarda bant genişliğini azaltan aynı parmak izi, yazmalarda kayıp güncellemeleri önleyebilir.
Örneğin iki yönetici aynı ürünü açar. Yönetici A fiyatı değiştirir ve kaydeder. Yönetici B, eski kopyayı kullanarak açıklamadaki yazım hatasını düzeltir ve kaydederse A'nın fiyat değişikliğini yanlışlıkla silebilir.
İstemcinin en son gördüğü sürümü koşula bağlayın:
PUT /v1/products/42 HTTP/1.1
If-Match: "33a64df551425fcc55e4d42a148795d9f2"
Content-Type: application/json
Sunucu:
-
If-Matchdeğerini mevcut ETag ile karşılaştırır. - Eşleşiyorsa güncellemeyi uygular ve yeni ETag ile
200 OKdöndürür. - Eşleşmiyorsa güncellemeyi reddeder ve
412 Precondition Faileddöndürür.
İstemci, kaynağı yeniden getirip değişikliklerini güncel sürüme uygulayarak tekrar deneyebilir. Güvenliği zorunlu kılmak isteyen API'ler, If-Match olmadan gönderilen PUT isteklerinde 428 Precondition Required döndürebilir.
ETag'leri okuma yoluna ekledikten sonra If-Match desteği, sessiz veri bozulmasını açık ve yeniden denenebilir bir HTTP hatasına dönüştürür. Ayrıntılar için 412 Precondition Failed açıklamamıza bakabilirsiniz.
CDN'ler ve proxy'ler başlıkları nasıl işler?
Paylaşılan önbellekler kaynağınız ile istemcileriniz arasında yer alır ve aynı başlıkları kendi kurallarıyla yorumlar.
-
private, yanıtı CDN önbelleklemesinden hariç tutar. -
s-maxage=600, tarayıcıdakimax-agedeğerinden bağımsız olarak CDN'e özel TTL belirler. - CDN, kaynağınıza
If-None-Matchgöndererek yeniden doğrulama yapabilir. Kaynak304döndürürse CDN gövdeyi yeniden indirmeden kopyasını günceller. - Aynı URL farklı formatlar döndürüyorsa
Varybaşlığını doğru gönderin. JSON ve CSV sunan bir uç noktasındaVary: Acceptkullanılmalıdır. - Sıkıştırma sırasında ETag'lerin zayıflatılmadığını kontrol edin.
Express örneği
Express varsayılan olarak zayıf ETag'ler üretir. Manuel işleme, güçlü ETag ve 412 yazma kontrolü 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ında Cache-Control ve ETag başlıklarını yeniden gönderin. RFC 9111'e göre 304, depolanan yanıtın meta verilerini günceller. İstemcinin kopyasını güncel tutmak için gerekli başlıklar bu nedenle yanıtta bulunmalıdır.
Apidog ile önbellekleme davranışını doğrulama
Kod doğru görünse bile ara yazılım ve proxy'ler önbellekleme davranışını değiştirebilir. Testi yalnızca uygulama seviyesinde değil, HTTP seviyesinde de yapın.
Apidog ile manuel kontrol:
-
GET /v1/products/42gönderin. Yanıt başlıklarındaETagveCache-Contrololduğunu, ETag'in tırnak içine alındığını doğrulayın. - ETag değerini
If-None-Matchbaşlığıyla aynı isteğe ekleyip tekrar gönderin. Boş gövdeli304almalısınız. - Kaynağı değiştirin ve isteği yeniden gönderin. Yeni ETag içeren
200 OKaldığınızı doğrulayın. - Eski bir ETag ile PUT gönderin ve
412 Precondition Failedaldığınızı kontrol edin.
Bu akışı bir test senaryosuna bağlayarak CI'a ekleyebilirsiniz:
- İlk istekten ETag'i yanıt başlıklarından bir değişkene çıkarın.
- İkinci istekte bu değişkeni
If-None-Matcholarak gönderin. - Durum kodunun
304, gövdenin boş olduğunu doğrulayın. - Üçüncü istekte kasıtlı olarak eski bir
If-Matchdeğeri, örneğin"deadbeefcafe1234", kullanın ve412sonucunu doğrulayın.
API iddiaları kılavuzu, durum kodları ve başlıklar için assertion sözdizimini açıklar.
Apidog'u ücretsiz indirin ve senaryoyu kendi uç noktalarınıza karşı çalıştırın. Bu kontrol, ETag'leri sessizce bozan bir ara yazılım güncellemesini bant genişliği faturası yerine başarısız bir CI işlemi olarak yakalamanızı sağlar.
Sıkça Sorulan Sorular
no-cache ve no-store arasındaki fark nedir?
no-store, yanıtın diske veya belleğe hiçbir şekilde yazılmasını engeller. Bu nedenle her istek tam yanıtı indirir.
no-cache depolamaya izin verir, ancak her yeniden kullanımdan önce yeniden doğrulama gerektirir. ETag ile birlikte kullanıldığında 304 yanıtları sayesinde yük transferinden tasarruf edebilirsiniz.
no-store yalnızca hassas veriler için kullanılmalıdır. Her yanıtta kullanmak, en pahalı Cache-Control hatalarından biridir.
ETag'ler POST ile çalışır mı?
Genellikle hayır. ETag'ler bir URL'deki kaynak durumunu tanımlar; POST ise çoğunlukla yeni bir kaynak veya işlem oluşturur. Önbellekler pratikte POST yanıtlarını önbelleğe almaz.
Koşullu yazma işlemleri için PUT, PATCH ve DELETE isteklerinde If-Match kullanın. Bir POST yanıtını önbelleğe alma ihtiyacı duyuyorsanız, işlemin aslında GET olması gerekip gerekmediğini değerlendirin.
304 yanıtı API'yi hızlandırır mı?
Aktarılan veriyi küçültür; ancak sunucudaki her işi ortadan kaldırmaz. Sunucu yine isteği alır, kimlik doğrulamayı çalıştırır ve mevcut ETag'i hesaplar.
Kazançlar özellikle şuralarda görülür:
- Bant genişliği
- Mobil pil tüketimi
- Yavaş ağlarda oluşturma süresi
Önce ve sonra ölçüm yapın. API performans testi kılavuzu, gecikme ve throughput karşılaştırmalarını nasıl yapacağınızı gösterir.
ETag mi, Last-Modified mi kullanmalıyım?
Mümkünse ikisini de gönderin.
ETag daha kesindir; saniye altı değişiklikleri ve zaman damgasının kaçırabileceği içerik farklılıklarını yakalar. İkisi birden gönderildiğinde If-None-Match, If-Modified-Since karşısında önceliklidir.
Last-Modified, eski istemciler için geri dönüş mekanizması ve bazı önbelleklerin güncellik tahmini için hâlâ kullanışlıdır. Yalnızca birini seçmeniz gerekiyorsa ETag kullanın.
Top comments (0)