Problem
Məhsul səhifəsi eyni kataloq sətrini dəyişdiyindən min dəfələrlə çox oxuyur. Hər baxış sorğu icra edir və pik yükdə baza darboğaza çevrilir. Keş bahalı işin nəticəsini çağıran tərəfə yaxın saxlayır ki, təkrar oxumalar həmin işi atlasın. Bu, mübadilədir: sürət qazanır və yükü azaldırsınız, amma bəzi oxuyucuların bir qədər köhnə data görə biləcəyini və yeni, sıradan çıxa bilən komponenti qəbul edirsiniz.
Əlavə etməzdən əvvəl daha ucuz variantları yoxlayın. Sorğu düzgün indeksdən istifadə edirmi? ORM N+1 select göndərirmi? Pis sorğunun üzərindəki keş qüsuru gizlədir və mürəkkəblik əlavə edir.
Cache-aside
Məntiq tətbiqdədir. Oxuyarkən əvvəl keşə baxır; miss olduqda bazadan yükləyir, dəyəri TTL ilə saxlayır və qaytarır. Yazarkən bazanı yeniləyir və keş qeydini silir (və ya yeniləyir).
public Product get(long id) { String key = "product:v1:" + id; Product cached = redis.get(key, Product.class); if (cached != null) return cached; Product p = repository.findById(id).orElseThrow(); redis.set(key, p, Duration.ofMinutes(10)); return p;}@Transactionalpublic void update(Product p) { repository.save(p); redis.delete("product:v1:" + p.getId());}Cache-aside ən geniş yayılmış pattern-dir, çünki sadədir və keş boş və ya əlçatmaz olsa da sistem düzgünlüyünü itirmir. Spring-in @Cacheable və @CacheEvict annotasiyaları eyni ideyanı deklarativ həyata keçirir.
Read-through və write-through
Read-through modelində tətbiq yalnız keş qatı ilə danışır, çatışmayan dəyərləri isə keşin özü mənbədən yükləyir. Çağıran kod sadələşir, amma loader dəstəkləyən keş komponenti lazımdır.
Write-through modelində hər yazı keşə gedir, keş isə sinxron şəkildə bazaya yazır. Yeni yazılmış datanın oxunması keşdən gəlir və ikisi uyğun qalır, lakin hər yazı hər iki yaddaşın qiymətini ödəyir və heç vaxt oxunmaya bilən data keşlənir. Write-behind (write-back) keşə yazdıqdan sonra təsdiq edir və sonra saxlayır; sürətlidir, amma keş boşaldılmadan çökərsə data itirmə riski var, ona görə diqqətli düşünmək tələb edir.
TTL və invalidation
TTL (time to live) təhlükəsizlik şəbəkəsidir: invalidation qaçsa belə, köhnə data nəhayət yox olur. Onu vərdişdən yox, biznesin köhnəlmə həddindən seçin. Ödəniş səhifəsində göstərilən qiymət sərt siyasət tələb edir; "populyar məqalələr" siyahısı dəqiqələrlə köhnə ola bilər. Birlikdə yaranan çox açarın birlikdə bitməməsi üçün TTL-ə kiçik təsadüfi jitter əlavə edin.
Invalidation çətindir, çünki iki yazı (baza və keş) var və aralarında ortaq transaction yoxdur. Adi seçim commit-dən sonra qeydi silməkdir. Commit ilə silmə arasındakı xəta TTL bitənə qədər köhnə qeyd buraxır, buna görə TTL məcburi qalır. Daha iki incəlik: yeniləmədə set yox, silmə seçin, çünki paralel yeniləmələr köhnə dəyəri sona yaza bilər; silməni transaction daxilində yox, commit-dən sonra edin, əks halda paralel oxuyucu köhnə dəyəri yenidən keşə qoya bilər.
Versiyalı açarlar (product:v1:...) format dəyişəndə versiyanı artırmaqla hər şeyi etibarsız etməyə imkan verir.
Stampede
Isti açarın vaxtı bitəndə yüzlərlə sorğu eyni anda miss alır və hamısı onu yenidən qurmaq üçün bazaya yönəlir. Bu cache stampede-dir. Azaltma yolları:
- Single flight: dəyəri yalnız bir çağıran yenidən qurur, digərləri gözləyir və ya nəticədən istifadə edir. Bir proses daxilində bu açar başına kiliddir; instansiyalar arasında qısa Redis kilidi (
SET key value NX PX ...) işləyir, çöküb getmiş qurucunun başqalarını bloklamaması üçün timeout ilə. - Yenilənərkən köhnəni ver: dəyərin içində məntiqi bitmə vaxtı saxlayın, köhnə dəyəri qaytarın və arxa planda yeniləyin.
- Yuxarıdakı kimi TTL jitter və məlum isti açarların əvvəlcədən isidilməsi.
- Negativ keşləmə: "tapılmadı" nəticəsini qısa müddət keşləyin ki, mövcud olmayan id sorğuları hər dəfə bazaya çatmasın.
Nəyi keşləməməli
- Dəqiq cari olmalı data: avtorizasiya üçün istifadə olunan hesab balansı, satın alma anında stok sayı.
- Ortaq açar altında istifadəçiyə aid data; bu, bir istifadəçinin datasını digərinə sızdırır. Dəyər istifadəçidən asılıdırsa, açara istifadəçini və ya tenant-ı daxil edin.
- Ucuz dəyərlər. Baza indeksli axtarışa Redis-ə şəbəkə sıçrayışının qiymətinə yaxın cavab verirsə, keş heç nə qazandırmır.
- Çoxlu kiçik isti qeydi sıxışdıran böyük obyektlər.
- İtkisi zərər verən hər şey: keş verilənlər bazası deyil. Məzmununu birdəfəlik hesab edin.
Redis strukturları qısaca
- String: serializasiya olunmuş obyekt və ya sayğac (
INCRatomardır). - Hash: sahələrini ayrı-ayrı yenilədiyiniz qeyd.
- Set / Sorted set: üzvlük, liderlər cədvəli, zamana görə sıralı indekslər (
ZADD,ZRANGE). - List: sadə növbələr.
- Müddət bitməsi: istənilən açarda
EXPIRE/PX. Yaddaş limiti və eviction siyasəti (məsələnallkeys-lru) təyin edin ki, keş yazıların uğursuz olması ilə yox, qeydləri çıxarmaqla zəifləsin.
Tipik səhvlər
- Keşi həqiqət mənbəyi saymaq.
- TTL-in olmaması; səhv data kimsə fərq edənə qədər yaşayır.
- Lazy əlaqələri olan entity-ləri keşləyib serializasiya sürprizləri almaq. DTO keşləyin.
- Hit ratio-nu izləməmək. Aşağı hit ratio-lu keş qənaət etdiyindən çox xərcləyir.
- Keş qəzasının servisi də yıxmasına icazə vermək; timeout istifadə edin və limitlərlə qorunan bazaya fallback edin.
Güzəştlər
Əlavə etdiyiniz hər qat (brauzer, CDN, tətbiq, Redis) köhnə datanın gizlənə biləcəyi yerləri artırır. Qatların sayını kiçik saxlayın və hər data növü üçün qəbul etdiyiniz maksimum köhnəlməni müəyyən edin. Prosesdaxili lokal keşlər ən sürətlidir, amma instansiyalar arasında fərqlənir; ortaq Redis keşi instansiyalar arasında uyğundur, lakin şəbəkə sıçrayışı və əməliyyat asılılığı əlavə edir.
Ölçmək və nəzarət etmək
Keşin dəyəri yalnız ölçüldükdə görünür. Ən azı üç göstərici izləyin: hit ratio, keşdən və mənbədən gələn cavabların gecikməsi, və eviction sayı. Hit ratio aşağıdırsa, açarlar çox unikaldır və ya TTL çox qısadır; eviction çoxdursa, keşin yaddaşı çatmır. Keşlə bağlı hər qərarı bu rəqəmlərlə əsaslandırın, hissə əsasında yox.
Dəyişiklikdən sonra yoxlama üçün sadə test yazın: yaz, oxu, yenidən yaz, yenidən oxu və ikinci oxumanın yeni dəyəri qaytardığını təsdiqlə. Bu test invalidation-ın unudulduğu xətaları tutur, onlar isə keş xətalarının ən geniş yayılmış növüdür.
Kiçik bir qayda
Keş əlavə etməzdən əvvəl bir cümlə yazın: "Bu data ən çox N dəqiqə köhnə ola bilər, çünki ...". Cümləni tamamlaya bilmirsinizsə, hələ keşləməyə hazır deyilsiniz. Tamamlaya bilirsinizsə, həmin cümlə sizin TTL-iniz və invalidation siyasətinizdir.
Yoxlama siyahısı
- Yavaş sorğu əvvəlcədən araşdırılıb və indekslənib.
- Hər keşlənmiş dəyərin TTL-i və bəyan olunmuş köhnəlmə tolerantlığı var.
- Yazılar commit-dən sonra invalidation edir.
- İsti açarlarda stampede qorunması var.
- İstifadəçiyə aid datanın açarında istifadəçi var.
- Hit ratio və gecikmə izlənir.
Növbəti addım
Keşləmədən əvvəl SQL indekslərindən və N+1 probleminden başlayın. Keşlənmiş cavablar əməliyyatı təkrarlardan qoruyursa, idempotency pattern-ləri ilə müqayisə edin. Sistem dizaynını daha geniş araşdırmaq üçün senior backend roadmap-ına baxın.