Cache Invalidation: İki Zor Ders
“Bilgisayar bilimlerinde yalnızca iki zor şey vardır: cache invalidation ve isim vermek.” Bu söz genellikle Netscape'ten Phil Karlton'a atfedilir. Aynı yıl içinde iki üretim olayından sonra bunu şaka olarak görmeyi bıraktık.
Birinci olay: veri ifşasına dönüşen cache anahtarı
“Hızlansın diye cache ekleyelim.” Kullanıcıya özel veri — ad, e-posta adresi, sipariş geçmişi — içeren bir API yanıtını cache'ledik. Cache anahtarı kullanıcı ID'sini içermiyordu. Altı saat boyunca o endpoint'e gelen her kullanıcı ilk kullanıcının verisini gördü.
Başta bunu bir doğruluk hatası olarak kaydettik. Değildi: bir veri ifşası olayıydı. Bir kişinin kişisel verisi herkese sunulmuştu, bu da bizi doğrudan gizlilik incelemesi alanına soktu. Cache'i temizlemek, erişim loglarından kimin neyi görmüş olabileceğini yeniden kurmak ve bildirim yükümlülüklerimizi değerlendirmek zorunda kaldık — anahtardaki tek bir eksik alanın yol açtığı bir günlük hukuk ve mühendislik işi. Kullanıcıya özel herhangi bir şeyi cache'liyorsanız, hatalı bir anahtarı performans hatası değil güvenlik arıza modu olarak ele alın ve diğer her ifşa gibi güvenlik operasyonları sürecinizden geçirin.
İkinci olay: izleyemediğimiz invalidation fan-out'u
Aylar sonra, farklı bir sistem. Profil güncellemesindeki invalidation mantığımız kapsamlı görünüyordu:
- Kullanıcı cache'ini geçersiz kıl
- Kullanıcının gönderi cache'ini geçersiz kıl
- Takipçilerin feed'lerini geçersiz kıl
- Arama indeksini ve öneri cache'ini geçersiz kıl
Unuttuklarımız: kullanıcının başkalarının gönderilerindeki yorumları (eski görünen ad), bildirim geçmişi (eski avatar) ve e-posta önizleme cache'i (eski biyografi). Kullanıcılar günlerce kendilerinin “hayalet” versiyonlarını bildirdi.
Fark ettiğimiz şey
Bir sistem büyüdükçe, bir verinin cache'lendiği yerlerin sayısı sizin onları takip etme kabiliyetinizden daha hızlı büyür. İki olay da aynı hastalıktı: kimse her cache'i sayamıyordu, dolayısıyla kimse anahtarlar ya da invalidation hakkında eksiksiz akıl yürütemiyordu.
Şimdi ne yapıyoruz
- Merkezi bir cache kaydı (registry). Her cache — Redis, CDN, süreç içi — bir kayıt alır: sahibi, anahtar şeması, TTL, invalidation tetikleyicileri, kabul edilebilir bayatlık. Yeni bir cache bu kayıt olmadan tasarım incelemesinden geçemez. Yaptığımız en yüksek kaldıraçlı tek değişiklik bu.
- Versiyonlu cache anahtarları. Bir versiyon önekini artırmak, tek tek anahtar avına çıkmadan koca bir namespace'i emekliye ayırır.
- Global temizleme yerine hedefli invalidation. Kullanıcı verisi değiştiğinde her şeyi boşaltmak güvenli görünür, ama bayatlığı thundering herd ile takas eder: tüm istekler aynı anda miss olur ve veritabanına izdiham yapar. Versiyon önekleri, jitter'lı TTL'ler ve istek birleştirme, invalidation'ın kendi başına bir kesintiye dönüşmesini önler.
- Gerçekten önemi olmayan yerlerde kabul edilen bayatlık — anlık kararla değil, kayıtta belgelenmiş olarak.
Herhangi bir cache eklemeden önce sorduğumuz sorular
- Veri kullanıcıya özel mi ve anahtar bunu kanıtlıyor mu?
- Ne sıklıkla değişiyor ve bayat verinin maliyeti ne?
- Bugün her invalidation yolunu sayabiliyor muyuz?
- Anahtar yanlışsa etki alanı (blast radius) ne?
Ders: Bir verinin cache'lendiği her yeri listeleyemiyorsanız, onu güvenilir biçimde geçersiz kılamazsınız. Ve en hızlı cache hâlâ ihtiyaç duymadığınız cache'tir — bir cache gecikme bile ekleyebilir — bu yüzden önce sorguyu optimize edin ve yalnızca ölçtüğünüzü cache'leyin.
Bu tür olaylar önlenebilir. AWS Yönetilen Hizmetler ekibimiz müşteri ortamlarını yapay zekâ destekli izleme ve nöbetteki kıdemli mühendislerle 7/24 gözetir — böylece bu tür hikâyeler arşivde kalır.