DEV Community

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

Posted on Originally published at apidog.com

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

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.

Apidog'u bugün deneyin

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
Enter fullscreen mode Exit fullscreen mode

İ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"
Enter fullscreen mode Exit fullscreen mode

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-age süresiyle otomatik olarak sona erer.
  • Paylaşılan önbellekler ve CDN'ler açık temizleme (purge), kısa TTL veya stale-while-revalidate gibi 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
Enter fullscreen mode Exit fullscreen mode

İ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
Enter fullscreen mode Exit fullscreen mode

İ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"
Enter fullscreen mode Exit fullscreen mode

İkinci yanıt

Sunucudaki ETag aynıysa:

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

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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-Match ile eşzamanlılık kontrolünde kullanılabilir.

Zayıf ETag

ETag: W/"33a64df551425fcc"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Sunucu:

  1. If-Match değerini mevcut ETag ile karşılaştırır.
  2. Eşleşiyorsa güncellemeyi uygular ve yeni ETag ile 200 OK döndürür.
  3. Eşleşmiyorsa güncellemeyi reddeder ve 412 Precondition Failed dö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ıdaki max-age değerinden bağımsız olarak CDN'e özel TTL belirler.
  • CDN, kaynağınıza If-None-Match göndererek yeniden doğrulama yapabilir. Kaynak 304 döndürürse CDN gövdeyi yeniden indirmeden kopyasını günceller.
  • Aynı URL farklı formatlar döndürüyorsa Vary başlığını doğru gönderin. JSON ve CSV sunan bir uç noktasında Vary: Accept kullanı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);
});
Enter fullscreen mode Exit fullscreen mode

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:

  1. GET /v1/products/42 gönderin. Yanıt başlıklarında ETag ve Cache-Control olduğunu, ETag'in tırnak içine alındığını doğrulayın.
  2. ETag değerini If-None-Match başlığıyla aynı isteğe ekleyip tekrar gönderin. Boş gövdeli 304 almalısınız.
  3. Kaynağı değiştirin ve isteği yeniden gönderin. Yeni ETag içeren 200 OK aldığınızı doğrulayın.
  4. Eski bir ETag ile PUT gönderin ve 412 Precondition Failed aldığı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-Match olarak gönderin.
  • Durum kodunun 304, gövdenin boş olduğunu doğrulayın.
  • Üçüncü istekte kasıtlı olarak eski bir If-Match değeri, örneğin "deadbeefcafe1234", kullanın ve 412 sonucunu 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)