• 29.08.2026 09:01:22
  • Admin Admin

Spring Data JPA sorgularında indeksin devreden çıkmasını üretime gitmeden yakalayın. PostgreSQL EXPLAIN JSON, pg_stat_statements, Testcontainers ve keyset sayfalama ile ölçülebilir sorgu planı sözleşmeleri kurun.

Spring Boot'ta Spring Data JPA Sorgu Planı Regresyon Testleri

Spring Data JPA için sorgu planı envanteri çıkarma

Şüpheli SQL'i gerçek bağ değerleriyle EXPLAIN (ANALYZE, BUFFERS, SETTINGS, FORMAT JSON) altında çalıştırın. ANALYZE sorguyu gerçekten yürüttüğü için üretimde yazan DML üzerinde kullanılmamalıdır; SELECT için bile kilit, önbellek ve I/O etkisini gözeterek önce read replica veya staging tercih edin. Plan içindeki Actual Rows ile Plan Rows oranı 10'u aşıyorsa, yanlış indeks seçimi bazen indeks eksikliği değil, bayat istatistik veya tenant dağılımındaki çarpıklık kaynaklıdır. Bu durumda önce ANALYZE orders çalıştırıp planı tekrar alın; doğrudan indeks eklemek, zaten yanlış tahmin edilen planı yalnızca maskeleyebilir.

EXPLAIN (ANALYZE, BUFFERS, SETTINGS, FORMAT JSON)
SELECT id, created_at, status
FROM orders
WHERE tenant_id = 'acme'
  AND status = 'OPEN'
ORDER BY created_at DESC, id DESC
LIMIT 50;

Spring Boot'ta Testcontainers ile plan sözleşmesi yazma

Bu testte Shared Read Blocks ilk çalıştırmada fiziksel I/O yerine konteynerin önbellek durumuna bağlı olabilir. Kararlı bir karşılaştırma için test fixture'ı yükledikten sonra ANALYZE orders çalıştırın, her ölçümden önce aynı sorguyu bir kez ısıtın ve ikinci EXPLAIN ANALYZE sonucunu doğrulayın. Daha da önemlisi, PostgreSQL prepared statement için generic plan seçebildiğinden JDBC ile tekil bind değeri ölçümü uygulamadaki hazırlanan ifade davranışından ayrışabilir. Tenant boyutları çok farklıysa test senaryolarına hem büyük hem küçük tenant ekleyin ve gerektiğinde SET plan_cache_mode = force_custom_plan ile custom plan farkını ayrıca görün.

Java microservices için offset yerine keyset sayfalama

Spring Data JPA tarafında cursor değerini hem zaman damgası hem kimlik olarak taşıyın. Tek başına createdAt < :cursor aynı milisaniyede üretilen kayıtları atlayabilir. Aşağıdaki JPQL, indeks sırasıyla aynı predicate ve order'ı kullanır; sonuç listesinden 50. öğenin createdAt ve id alanlarını bir sonraki istek için imzalı cursor içine koyun. Bu değişiklikten önce ve sonra aynı büyük tenant için EXPLAIN ANALYZE BUFFERS alın: kabul kriteri, sayfa numarası büyüdükçe Actual Rows ve buffer hit sayısının lineer yükselmemesidir.

@Query("""
  select o from Order o
  where o.tenantId = :tenantId
    and o.status = :status
    and (o.createdAt < :createdAt
      or (o.createdAt = :createdAt and o.id < :id))
  order by o.createdAt desc, o.id desc
  """)
List<Order> findNextPage(
    UUID tenantId, OrderStatus status,
    Instant createdAt, UUID id,
    Pageable pageable);

Hibernate ORM ve Spring Framework katmanlarında SQL sapmasını bulma

Plan regresyonu testinde sık yapılan hata, sadece uygulama SQL'ini doğrulayıp RLS policy, implicit cast ve collation etkisini dışarıda bırakmaktır. Örneğin tenant_id UUID iken parametreyi text'e cast eden native SQL, operatör sınıfını değiştirip indeks koşulunu filtreye dönüştürebilir. Test SQL'inde kolon ve bind tiplerini uygulamayla aynı tutun. PostgreSQL tarafında pg_stat_user_indexes.idx_scan ile yeni indeksin gerçekten tarandığını, pg_stat_user_tables.n_live_tup ile de testteki veri hacminin planı anlamlı kılacak seviyede olduğunu kontrol edin.

Java eğitimi için ölçülebilir plan regresyonu laboratuvarı

Java fullstack eğitimi kapsamındaki bir cursor API'si, istemciye ham createdAt,id değerlerini vermek yerine Base64URL kodlu ve HMAC imzalı token döndürmelidir; aksi halde kullanıcı cursor'u değiştirerek başka tenant'ın kayıt sırasını keşfedebilir. Aynı disiplin spring ai kullanan bir araç çağrısında da gereklidir: modelin ürettiği filtreler doğrudan SQL'e eklenmemeli, izinli alanlar enum ile eşlenmelidir. Spring MCP uygulamalarında model context protocol tool handler'ı için cursor doğrulaması ve tenant bağlamı, HTTP controller ile aynı service metodunda uygulanmalıdır. Böylece spring mcp üzerinden gelen bir tool çağrısı, sorgu planı testinden geçen keyset repository yolunu kullanır.

Sık Sorulan Sorular

Spring Data JPA sorgu planı regresyon testi nasıl yazılır?

Testcontainers ile PostgreSQL başlatın, gerçekçi veri dağılımını yükleyip ANALYZE çalıştırın ve JdbcTemplate üzerinden EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) çalıştırın. JSON içinden beklenen Index Name, Actual Rows ve Shared Read Blocks alanlarını doğrulayın; planın tamamını metin olarak assert etmeyin.

Java backend geliştirmede OFFSET sayfalama neden yavaşlar?

OFFSET, döndürülecek satır sayısı küçük olsa bile atlanacak her indeks girdisinin gezilmesini gerektirir. Tenant, status, created_at DESC, id DESC bileşik indeksiyle keyset predicate kullanın; EXPLAIN ANALYZE BUFFERS çıktısında 100. ve 10000. sayfanın buffer maliyetini karşılaştırın.

Hibernate ORM indeks kullanmıyorsa hangi araçla teşhis edilir?

Önce pg_stat_statements ile fiziksel blok okuyan queryid'yi bulun. Ardından Hibernate SQL ve bind loglarını yalnızca test profilinde açarak gerçek predicate'i alın, aynı bind tipleriyle EXPLAIN ANALYZE BUFFERS çalıştırın. lower(kolon), implicit cast ve RLS policy gibi indeks koşulunu filtreye çeviren ayrıntıları plandan kontrol edin.

Spring AI ve Spring MCP tool sorgularında keyset cursor nasıl korunur?

Cursor'a tenantId, createdAt, id ve son kullanma zamanını koyun; payload'u Base64URL ile taşıyıp sunucu tarafı HMAC anahtarıyla imzalayın. Model context protocol tool handler'ında token doğrulamasını repository'den önce yapın ve modelden gelen alan adlarını serbest SQL yerine izinli enum değerlerine dönüştürün.

AI / LLM Discovery

Bu makale Opendart Akademi Java eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt