İçeriğe geç

İkinci Seviye Cache — Hangi Cache, Hangi Soruya Cevap Verir?

İleri 9 dk Sık karşılaşılır

Önce şunu oku: Entity equals ve hashCode — Aynı Sipariş mi? , Spring Cache — @Cacheable Kimin Cevabını Hatırlıyor?

30 saniyede özet

İkinci seviye cache, bir kez okunan kayıtları sonraki isteklerle paylaşır; ama yalnızca id ile okumayı hızlandırır. Hibernate'in görmediği bir değişiklik olursa da süre dolana kadar eski veriyi göstermeye devam eder.

Ekip veritabanı yükünü azaltmak için query cache’i açtı. Kategori sayfası beklenenin tersine yavaşladı. Bir hafta sonra da fiyat ekranı, başka bir servisin güncellediği fiyatları saatlerce eski gösterdi.

  1. Bayt: Veritabanı yükünü azaltmak için query cache'i açtık!

  2. Sen: Güzel, sayfa hızlandı mı?

  3. Bayt: Tersine, yavaşladı. Bir hafta sonra da fiyatlar saatlerce eski göründü.

  4. Bayt: Cache tam olarak neyi saklıyor, ve kimden habersiz kalıyor? Ona bakalım.

Masadaki kitap, salondaki kitap

Birinci seviye cache, persistence context’in kendisidir ve istekle birlikte biter. İki farklı istekte find(7) iki SELECT demektir.

second-level cachePersistence context'ler arasında paylaşılan, entity durumunu id'ye göre tutan Hibernate cache'i. İsteğe bağlıdır; bir sağlayıcı (JCache, Infinispan) ve entity başına işaret ister.Sözlükte gör → (L2) istekler arasında paylaşılır ve entity durumunu id’ye göre tutar. İsteğe bağlıdır: bir sağlayıcı (JCache, Infinispan) ve entity başına @Cache işareti ister.

L2 id ile okumaları hızlandırır: find, getReference, ilişki yüklemeleri. JPQL sorgusu ise hangi satırların sonuca gireceğini bilmek için yine veritabanına gider.

Kafam karıştı, daha basit anlat

Birinci seviye cache senin masandaki kitaptır, iş bitince rafa kalkar. İkinci seviye cache ise salondaki ortak raf: herkes oradan alabilir.

Hızlı kontrolBaşlangıç

Hibernate'in birinci seviye cache'i ile ikinci seviye (L2) cache'i arasındaki fark nedir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Product için L2 açık. Aynı kategori sorgusu (JPQL) iki farklı istekte çalıştırılıyor; query cache kapalı. İkinci istekte ne olur?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Query cache aslında ne saklar?

Query cache açık, Product için L2 kapalı. 20 ürün döndüren aynı sorgu ikinci kez çalışıyor. Kaç SQL gider? Cevabı göster

20. query cacheBir sorgunun sonucu olarak yalnızca id listesini saklayan cache. Sorgudaki tablolardan biri Hibernate üzerinden değişince geçersiz olur; entity'ler L2'de değilse N+1 üretir.Sözlükte gör → sonuçları değil, yalnızca id’leri saklar. Entity’ler L2’de olmadığı için her id ayrı bir SELECT ile yüklenir.

Masa, ortak salon ve raf numarası listesi: üç farklı cache.
Adım adım oku
  1. Birinci seviye cache masandaki kitaplardır: istek bitince hepsi rafa döner.
  2. L2 cache ortak okuma salonudur: bütün istekler oradaki kitabı veritabanına gitmeden okur.
  3. Başka bir servis veritabanındaki fiyatı doğrudan değiştirirse salon bundan haberdar olmaz ve eski baskıyı vermeye devam eder.
  4. Query cache yalnızca sonuçtaki kimlikleri, yani raf numaralarını saklar. L2 kapalıysa her kitap yine tek tek getirilir.

Query cache, sorgudaki tablolardan biri Hibernate üzerinden her değiştiğinde geçersiz olur. Sık yazılan bir tabloda neredeyse hiç hit almaz.

Kafam karıştı, daha basit anlat

Query cache cevabın kendisini değil, yalnızca hangi kitapların geldiğinin listesini saklar. Kitaplar rafta yoksa her birini ayrı ayrı getirmen gerekir.

Hızlı kontrolOrta

Query cache açıldı, ama sorgu sayısı düşmek yerine arttı: tek bir sorgu yerine 20 SELECT görülüyor. Neden?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Ülke listesi uygulama açıldıktan sonra hiç değişmiyor. L2 için en uygun eşzamanlılık stratejisi hangisi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin gör

Hangi cache, hangi soruya cevap verir?

Tohum 638400

Oynat ya da adımla.

Hız
Adım 0

Şu an ne oldu?

Her istek yeni bir persistence context

Birinci seviye cache istekle birlikte biter. İstekler arasında paylaşılan tek şey, açıksa, L2 ve query cache'tir.

Görevler0/3

  • Query cache ile sorgu sayısını artıraçık

    İpucu

    Query cache açık, entity cache kapalı.

  • L2'den eski bir fiyat okuaçık

    İpucu

    Fiyatı Hibernate'in görmediği biri değiştirsin.

  • Uygulama fiyatı değiştirirken en az SQL ile güncel kalaçık

    İpucu

    İki cache birlikte.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

  1. Varsayılanla oynat. Query cache açık, L2 kapalı: ikinci sorgu 20 SELECT.
  2. L2’yi aç. İkinci sorgu ve tekrar eden find SQL’siz.
  3. Yazanı “Bu uygulama” yap. Cache güncel kaldı, toplam 3 SQL.
  4. Yazanı “Başka bir servis” yap. Son okuma eski fiyatı verdi.
Hızlı kontrolOrta

Fiyat yönetim servisi fiyatları doğrudan veritabanına yazıyor. Sipariş servisi bazen saatlerce eski fiyat gösteriyor. Hatalı satır hangisi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Hatalı satıra dokun, sonra kontrol et.

Product.java
Java 21UTF-8LF

Her veriyi L2'ye uygunluğuna göre ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

L2'ye uygun

Çok okunur, az ya da yalnızca bu uygulamadan yazılır.

    L2'ye koyma

    Sık değişir, başkası yazar ya da tutarlılık kritik.

      Tuzaklar

      Birden çok pod ve yerel L2. Her pod’un kendi kopyası vardır; bir pod’un yazması diğerlerinin kopyasını değiştirmez. Kümelenmiş bir sağlayıcı kullan ya da L2’yi değişmeyen veriyle sınırla.

      Native SQL ile yazmak. Hangi entity’nin etkilendiğini bildirmezsen Hibernate bütün L2 bölgelerini boşaltır.

      Her entity’yi cache’lemek. Sık değişen veri sürekli geçersiz kılınır; kazanç yok, eskime riski var.

      Kafam karıştı, daha basit anlat

      Her sunucunun kendi ortak rafı varsa, birinde değişen kitap ötekilerde eski kalır. Bu cache’i en iyi, pek değişmeyen verilerde kullan.

      Hızlı kontrolİleri

      Uygulama üç pod'da çalışıyor ve L2 sağlayıcısı her pod'da yerel bir bellek cache'i. Bir pod ürünü güncellediğinde diğerleri ne görür?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Aşağıdaki örnek bir bankanın referans verisinden ve L2’yi yalnızca uygun veriye açıyor: hiç değişmeyen para birimleri READ_ONLY, yalnızca bu uygulamanın yazdığı ürün ağacı READ_WRITE, query cache tek bir sık sorguda ve hepsi istatistikle doğrulanıyor.

      Derinleş · Bankacılık referans verisi için ikinci seviye cache 6 dosya · ~103 satır · ilk okumada atlayabilirsin
      Proje dosyaları

      build.gradle.kts Sağlayıcı: JCache arayüzüyle Ehcache.

      build.gradle.kts
      dependencies {
      implementation("org.springframework.boot:spring-boot-starter-data-jpa")
      implementation("org.hibernate.orm:hibernate-jcache")
      implementation("org.ehcache:ehcache:3.10.8:jakarta")
      }

      src/main/resources/ application.yml L2'yi aç, bölgeleri Ehcache dosyasına bağla, yalnızca işaretli entity'ler cache'lensin.

      src/main/resources/application.yml
      spring:
      jpa:
      properties:
      jakarta.persistence.sharedCache.mode: ENABLE_SELECTIVE # only entities marked @Cache
      hibernate:
      cache:
      use_second_level_cache: true
      use_query_cache: true
      region.factory_class: jcache
      javax.cache:
      provider: org.ehcache.jsr107.EhcacheCachingProvider
      uri: classpath:ehcache.xml
      generate_statistics: true # hit/miss counts; keep it on in staging

      src/main/resources/ ehcache.xml Bölge başına boyut ve süre. Zaman damgası bölgesi hiç süre dolmadan tutulmalı.

      src/main/resources/ehcache.xml
      <config xmlns="http://www.ehcache.org/v3">
      <cache alias="com.bank.reference.Currency">
      <heap unit="entries">500</heap> <!-- read-only data: no expiry needed -->
      </cache>
      <cache alias="com.bank.reference.ProductGroup">
      <expiry><ttl unit="minutes">30</ttl></expiry>
      <heap unit="entries">5000</heap>
      </cache>
      <cache alias="com.bank.reference.ProductGroup.children">
      <expiry><ttl unit="minutes">30</ttl></expiry>
      <heap unit="entries">5000</heap>
      </cache>
      <cache alias="default-query-results-region">
      <expiry><ttl unit="minutes">5</ttl></expiry>
      <heap unit="entries">1000</heap>
      </cache>
      <!-- Must outlive every query result it guards: no expiry. -->
      <cache alias="default-update-timestamps-region">
      <heap unit="entries">1000</heap>
      </cache>
      </config>

      src/main/java/com/bank/reference/ ReferenceEntities.java Değişmeyen veri (para birimleri) READ_ONLY; uygulamanın yazdığı veri (ürün ağacı) READ_WRITE; koleksiyon ayrıca işaretlenir. Bakiye gibi sık değişen veri L2'ye hiç girmez.

      src/main/java/com/bank/reference/ReferenceEntities.java
      // Two entities in one listing for brevity; in the project each has its own file.
      @Entity
      @Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
      class Currency { // ISO 4217 list: loaded once, never edited here
      @Id private String code; // "TRY", "EUR", …
      private String name;
      private int minorUnits; // 2 for TRY, 0 for JPY
      }
      @Entity
      @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
      class ProductGroup { // "Mevduat" > "Vadeli", "Vadesiz"; edited only in this app's product admin
      @Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
      private Long id;
      private String name;
      @ManyToOne(fetch = FetchType.LAZY)
      private ProductGroup parent;
      // The collection is a separate region and holds ids only:
      // the ProductGroup entities themselves must be cached too.
      @OneToMany(mappedBy = "parent")
      @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
      private List<ProductGroup> children = new ArrayList<>();
      protected ProductGroup() {}
      ProductGroup(String name) {
      this.name = name;
      }
      Long getId() { return id; }
      }

      src/main/java/com/bank/reference/ ProductGroupRepository.java Query cache yalnızca entity'leri de L2'de olan, nadiren değişen bir sorguda.

      src/main/java/com/bank/reference/ProductGroupRepository.java
      interface ProductGroupRepository extends JpaRepository<ProductGroup, Long> {
      // Product menu: every screen of the app asks for it; the tree changes a few times a year.
      // The query cache stores the ids; the entities come from the ProductGroup region.
      @QueryHints(@QueryHint(name = "org.hibernate.cacheable", value = "true"))
      @Query("select c from ProductGroup c where c.parent is null order by c.name")
      List<ProductGroup> findRoots();
      }

      src/test/java/com/bank/reference/ ProductGroupCacheTest.java Test: ikinci okumanın L2'den geldiğini istatistikle kanıtlar.

      src/test/java/com/bank/reference/ProductGroupCacheTest.java
      @SpringBootTest
      class ProductGroupCacheTest {
      @Autowired ProductGroupRepository productGroups;
      @Autowired EntityManagerFactory emf;
      @Autowired TransactionTemplate tx;
      @Test
      void secondRequestIsServedFromL2() {
      Long id = tx.execute(status -> productGroups.save(new ProductGroup("Mevduat")).getId());
      Statistics stats = emf.unwrap(SessionFactory.class).getStatistics();
      stats.clear();
      tx.executeWithoutResult(status -> productGroups.findById(id).orElseThrow()); // new persistence context
      tx.executeWithoutResult(status -> productGroups.findById(id).orElseThrow()); // and another
      assertThat(stats.getSecondLevelCacheHitCount()).isGreaterThanOrEqualTo(1);
      assertThat(stats.getEntityLoadCount()).isZero(); // no SELECT: put on save, hits on both reads
      }
      }

      Kendini sına

      Şimşek turu1/5

      Birinci seviye cache, persistence context'in kendisidir ve istekle biter.

      Soru 1/2İleri

      Query cache açık bir sorgu, siparişler tablosuna dakikada yüzlerce yazma yapılan bir sistemde hiç hit almıyor. Neden?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Aklında kalacak üç şey

      1. 1 Birinci seviye cache istekle birlikte biter. İstekler arasında id ile okumayı yalnızca isteğe bağlı L2 cache hızlandırır; JPQL sorguları yine veritabanına gider.
      2. 2 Query cache sonuçları değil id'leri saklar. Kayıtlar L2'de değilse her id için ayrı SELECT gider.
      3. 3 L2 yalnızca Hibernate üzerinden yapılan yazmaları bilir. Başka bir servisin ya da elle çalıştırılan SQL'in değiştirdiği veri L2'ye ulaşmaz.
      Sonraki kapı Tek sorguyla bin hesabı DORMANT yaptın. Az önce yüklediğin hesap 7 hâlâ ACTIVE diyor. Kim yalan söylüyor? Toplu UPDATE — Veritabanı Değişti, Nesnen Neden Haberi Yok? · 8 dk

      4 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı