Ders 06 / 25
HTTP Önbellekleme
Yanıtın kendi önbellek kurallarını taşıması: ETag ve Last-Modified doğrulayıcılarının koşullu isteklerle 304 üretmesi, gövde aktarımının sıfıra inmesi, kaynak değiştiğinde doğrulayıcının değişmesi ve public, private, max-age, no-store ile yeniden doğrulayarak sunma yönergelerinin kaynak türüne göre seçilmesi.
İçindekiler
Önceki derslerin bütün önbellekleri uygulamanın içindeydi: anahtar kodda üretiliyor, girdi kodda konuyor, geçersizleştirme kodda tetikleniyordu. Yanıt istemciye gönderildiği anda bu denetim biter. İlk derste “denetlenemez” diye anılan katmanlar — tarayıcı önbelleği, aradaki paylaşılan önbellekler, kenar katmanı — kodun ne düşündüğünü bilmez; yalnız yanıtın başlıklarında yazana bakar.
Bu yüzden HTTP’nin kendi önbellek sözlüğü vardır ve iki soruyu ayrı ayrı yanıtlar: bir yanıt ne kadar süre taze sayılır, tazeliği bittiğinde saklanan kopyanın hâlâ geçerli olup olmadığı kaynağa gövde indirmeden nasıl sorulur. Bu ders iki soruyu da gerçek istekler üzerinde yanıtlar.
Doğrulayıcı Üreten Bir Sunucu
Aşağıdaki sunucu kütüphane API’sinin dört uç noktasını sunar. Her yanıt gövdesinin özetinden bir doğrulayıcı (validator) üretilir ve koşullu istekler bu değere göre yanıtlanır. Yanıtlardaki tarih başlığı sabit tutulmuştur; böylece ölçümün çıktısı her koşuda aynıdır.
// http-onbellek.mjs — kutuphane API'si: dogrulayicilar ve onbellek denetim yonergeleri import { createServer } from "node:http"; import { createHash } from "node:crypto"; const kitaplar = new Map([[7, { kitapId: 7, baslik: "Kum: Borges Seçkisi", yazar: "Derleme", rafta: 3 }]]); const stok = { subeId: 1, raftaki: 137 }; const gecmis = { uyeId: 4, toplam: 12 }; const DEGISIM = new Map([[7, new Date("2025-07-01T09:00:00Z")]]); // son degisiklik anlari const etiket = (govde) => `"${createHash("sha256").update(govde).digest("hex").slice(0, 16)}"`; function yanitla(istek, yanit, { govde, denetim, sonDegisim = null, vary = null }) { const metin = JSON.stringify(govde); const etag = etiket(metin); const basliklar = { "cache-control": denetim, etag }; if (sonDegisim) basliklar["last-modified"] = sonDegisim.toUTCString(); if (vary) basliklar.vary = vary; const gelenEtag = istek.headers["if-none-match"]; const gelenTarih = istek.headers["if-modified-since"]; const etagUyuyor = gelenEtag !== undefined && gelenEtag.split(",").map((e) => e.trim()).includes(etag); const tarihUyuyor = !etagUyuyor && gelenTarih !== undefined && sonDegisim !== null && Math.floor(sonDegisim.getTime() / 1000) <= Math.floor(Date.parse(gelenTarih) / 1000); if (etagUyuyor || tarihUyuyor) { yanit.writeHead(304, basliklar); return yanit.end(); } yanit.writeHead(200, { ...basliklar, "content-type": "application/json; charset=utf-8", "content-length": Buffer.byteLength(metin) }); yanit.end(metin); } const sunucu = createServer((istek, yanit) => { yanit.sendDate = false; // ciktinin belirlenimci kalmasi icin const yol = new URL(istek.url, "http://yerel").pathname; if (istek.method === "POST" && yol === "/kitap/7") { // kitap guncellenir: dogrulayici degisir const k = kitaplar.get(7); k.rafta -= 1; DEGISIM.set(7, new Date("2025-07-02T14:30:00Z")); yanit.writeHead(204, { "cache-control": "no-store" }); return yanit.end(); } if (yol === "/kitap/7") { // seyrek degisen genel kaynak return yanitla(istek, yanit, { govde: kitaplar.get(7), denetim: "public, max-age=60", sonDegisim: DEGISIM.get(7), vary: "Accept-Language" }); } if (yol === "/sube/1/stok") { // sik degisen genel kaynak return yanitla(istek, yanit, { govde: stok, denetim: "public, max-age=5, stale-while-revalidate=30" }); } if (yol === "/uye/4/gecmis") { // kisiye ozel kaynak return yanitla(istek, yanit, { govde: gecmis, denetim: "private, max-age=30" }); } if (yol === "/odunc/onay") { // hic saklanmamasi gereken yanit return yanitla(istek, yanit, { govde: { onay: "U-4711", kalan: 2 }, denetim: "no-store" }); } yanit.writeHead(404, { "cache-control": "no-store" }); yanit.end(); }); sunucu.listen(8547, "127.0.0.1", () => console.log("kutuphane API 127.0.0.1:8547"));
Sunucuyu başlatan ve ölçen blok ayrıdır. Doğrulayıcı ile son değişiklik tarihi yanıttan okunur, sonraki isteklere koşul başlığı olarak konur.
# Yanitlarin onbellek basliklari ve kosullu isteklerin sonucu node http-onbellek.mjs & sunucu=$! sleep 0.5 K=http://127.0.0.1:8547 basliklar() { curl -sS -D - -o /dev/null "$@" | tr -d '\r' | grep -iE '^(HTTP/|etag|last-modified|cache-control|vary)'; } olc() { curl -sS -o /dev/null -w "durum=%{http_code} indirilen=%{size_download} bayt\n" "$@"; } echo "== 1. ilk istek: dogrulayicilar ve yonergeler" basliklar "$K/kitap/7" ETAG=$(curl -sS -D - -o /dev/null "$K/kitap/7" | tr -d '\r' | awk '/^[Ee]tag:/ {print $2}') TARIH=$(curl -sS -D - -o /dev/null "$K/kitap/7" | tr -d '\r' | sed -n 's/^[Ll]ast-[Mm]odified: //p') echo "== 2. ayni kaynak kosullu istenirse" olc "$K/kitap/7" olc -H "If-None-Match: $ETAG" "$K/kitap/7" olc -H "If-Modified-Since: $TARIH" "$K/kitap/7" echo "== 3. kaynak degisince eski dogrulayici tutmuyor" curl -sS -o /dev/null -X POST "$K/kitap/7" olc -H "If-None-Match: $ETAG" "$K/kitap/7" basliklar "$K/kitap/7" | grep -iE '^(etag|last-modified)' echo "== 4. kaynak turune gore yonergeler" for yol in /kitap/7 /sube/1/stok /uye/4/gecmis /odunc/onay; do printf '%-16s %s\n' "$yol" "$(curl -sS -D - -o /dev/null "$K$yol" | tr -d '\r' | sed -n 's/^[Cc]ache-[Cc]ontrol: //p')" done kill $sunucu
kutuphane API 127.0.0.1:8547 == 1. ilk istek: dogrulayicilar ve yonergeler HTTP/1.1 200 OK cache-control: public, max-age=60 etag: "c5397b0c31b92e65" last-modified: Tue, 01 Jul 2025 09:00:00 GMT vary: Accept-Language == 2. ayni kaynak kosullu istenirse durum=200 indirilen=73 bayt durum=304 indirilen=0 bayt durum=304 indirilen=0 bayt == 3. kaynak degisince eski dogrulayici tutmuyor durum=200 indirilen=73 bayt etag: "211739eb1d385801" last-modified: Wed, 02 Jul 2025 14:30:00 GMT == 4. kaynak turune gore yonergeler /kitap/7 public, max-age=60 /sube/1/stok public, max-age=5, stale-while-revalidate=30 /uye/4/gecmis private, max-age=30 /odunc/onay no-store
Tazelik ve Doğrulama
Çıktının ikinci bölümü iki farklı işi ayırıyor. Koşulsuz istek 200 döndürdü ve 73 bayt
indirildi. Aynı kaynak If-None-Match başlığıyla istendiğinde yanıt 304 oldu ve indirilen
gövde sıfır bayta indi. Üçüncü satırda aynı sonuç If-Modified-Since ile alındı.
304 Not Modified yanıtının kazandırdığı şey gövdedir, gidiş-dönüş değil. İstek yine sunucuya gitti, sunucu yine doğrulayıcıyı hesapladı. Kazanç, gövde büyüdükçe büyür: yetmiş üç baytlık bir kitap kaydında ihmal edilebilir, elli kilobayttan bir liste yanıtında belirleyicidir.
Tazelik ise doğrulamadan önce gelir. max-age=60 yazan bir yanıt altmış saniye boyunca
taze sayılır ve o süre içinde önbellek sunucuya hiç sormaz. Süre dolduğunda kopya
atılmaz, bayat duruma geçer ve bir sonraki istekte doğrulanır. Doğrulama 304 dönerse
kopya yeniden taze sayılır. İki mekanizma birlikte çalışır: max-age gidiş-dönüşü, doğrulayıcı
gövdeyi ortadan kaldırır.
Üçüncü bölüm doğrulayıcının anlamını kanıtlıyor. Kitap POST isteğiyle güncellendikten sonra elde tutulan eski doğrulayıcıyla yapılan koşullu istek 304 değil 200 döndü ve gövde yeniden indirildi. Yeni yanıtın hem etiketi hem son değişiklik tarihi değişmiştir. Doğrulayıcı, kaynağın içeriğine bağlı olduğu için bu davranış kendiliğinden doğrudur.
İki doğrulayıcı arasında bir ayrım vardır. Son değişiklik tarihinin çözünürlüğü bir saniyedir: aynı saniye içinde iki kez değişen bir kayıt için eski kopya geçerli sanılabilir. Gövde özetinden üretilen etiketin böyle bir sınırı yoktur. Buna karşılık etiketin hesaplanması gövdenin üretilmesini gerektirir; tarih ise çoğu zaman kaydın kendi sütunundan okunur ve sorgu çalıştırmadan yanıtlanabilir.
Yönergelerin Anlamı
Son bölümdeki dört satır, dört farklı kaynak türü için verilmiş dört karardır.
public, max-age=60 — kitap ayrıntısı her kullanıcıya aynı döner ve seyrek değişir.
public, yanıtın paylaşılan önbelleklerde saklanabileceğini söyler.
public, max-age=5, stale-while-revalidate=30 — şube stok sayısı sık değişir. Beş saniye
taze sayılır; sonraki otuz saniye boyunca bayat kopya sunulmayı sürdürür ve önbellek arka
planda yenilemeyi yapar. Bu, önceki derste süreç içinde kurulan davranışın HTTP’deki
karşılığıdır; aradaki fark, kararı artık uygulamanın değil aradaki önbelleğin uygulamasıdır.
private, max-age=30 — üyenin ödünç geçmişi kişiye özeldir. private, yanıtın yalnız son
kullanıcının kendi önbelleğinde saklanabileceğini, paylaşılan bir önbelleğe konamayacağını
söyler. Anahtar Tasarımı dersindeki kiracı sızıntısının HTTP karşılığı budur ve tek satırlık
bir başlıkla önlenir.
no-store — ödünç onayı hiçbir yerde saklanmamalıdır. Bu yönerge sıkça no-cache ile
karıştırılır: no-cache saklamayı yasaklamaz, yalnız kullanmadan önce doğrulamayı zorunlu
kılar. Saklanmaması gereken yanıt için doğru yönerge no-store’dur.
Paylaşılan önbellekler için ayrı bir süre verilebilir: s-maxage, yalnız paylaşılan
önbellekleri bağlar ve max-age’i geçersiz kılar. Kenar katmanında uzun, tarayıcıda kısa bir
süre istendiğinde kullanılan yol budur.
Vary ve Paylaşılan Anahtar
Kitap yanıtındaki Vary: Accept-Language başlığı, paylaşılan önbelleğin anahtarını
değiştirir. Anahtar Tasarımı dersinde önbellek anahtarının yanıtı değiştiren her girdiyi
taşıması gerektiği ölçülmüştü; HTTP’de bu girdiler istek başlıklarıdır ve Vary onları adıyla
bildirir. Başlık verilmezse önbellek anahtar olarak yalnız adresi kullanır ve Türkçe isteyen
bir kullanıcıya İngilizce yanıt sunulabilir.
Vary başlığının tehlikeli iki değeri vardır. Vary: Cookie ya da Vary: Authorization
yazan bir yanıt teknik olarak doğrudur ama pratikte önbelleklenemez: her kullanıcının
çerezi farklı olduğu için her istek ayrı bir anahtara düşer ve isabet oranı sıfıra iner. Bu,
Anahtar Tasarımı dersinde ölçülen “fazla alan” satırının aynısıdır. Kişiye özel yanıtlarda
doğru çözüm Vary değil private’tır.
Özet
- HTTP önbelleklemesi iki ayrı mekanizmadır:
max-ageyanıtın ne kadar süre taze sayılacağını, doğrulayıcı ise bayat kopyanın hâlâ geçerli olup olmadığını belirler. - Koşullu istek 304 döndürdüğünde indirilen gövde 73 bayttan 0 bayta indi; gidiş-dönüş yine yapıldı. Kazanç gövdededir ve gövde büyüdükçe artar.
- Kaynak güncellendiğinde gövde özetinden üretilen etiket ve son değişiklik tarihi değişti; eski doğrulayıcıyla yapılan istek 304 yerine 200 aldı.
- Son değişiklik tarihinin çözünürlüğü bir saniyedir; gövde özetinden üretilen etiketin böyle bir sınırı yoktur ama üretilmesi gövdenin hesaplanmasını gerektirir.
publicpaylaşılan önbelleğe izin verir,privateyalnız son kullanıcının önbelleğine,no-storehiçbir yere.no-cachesaklamayı değil, kullanmadan önce doğrulamamayı yasaklar.
Sonraki Adım
Konu boyunca her ders bir sayı üretti: kaynağa giden sorgu sayısı, bayat okuma sayısı, isabet ve ıska, indirilen bayt. Bu sayılar tek tek anlamlıydı ama hiçbiri “önbellek işe yarıyor mu” sorusunu tek başına yanıtlamaz. Anahtar Tasarımı dersinde en yüksek isabet oranı yanlış gerçekleştirime aitti; bir başka kurulumda yüksek isabet oranı, hiç okunmayan ucuz anahtarlardan gelebilir. Sonraki ders bu sayıları bir ölçüt kümesine bağlar: isabet oranı nasıl hesaplanır, neyi gizler, ıska cezası ile birleştiğinde ortalama yanıt süresine ne yapar ve önbellek kapasitesi değiştikçe isabet oranı nasıl davranır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.