Spring Boot uygulamalarında sanal thread kullanırken JDBC havuzu, Hibernate sorguları ve ölçümleme birlikte tasarlanmalıdır. Bu yazı, java backend geliştirme ekipleri için JFR ve k6 ile kapasite planını uygular.
Spring Boot’ta Sanal Thread ve JDBC Havuzu Kapasite Planlaması
Spring Boot eğitimi perspektifiyle sanal thread ve bağlantı havuzu sınırı
Sanal thread etkinleştirmek, veritabanına eşzamanlı sınırsız istek göndermek anlamına gelmez. HTTP isteklerini taşıyan sanal thread sayısı yüzbinlere çıkabilse de her sorgu fiziksel JDBC bağlantısı alır; aynı anda çalışan SQL sayısını HikariCP'nin maximumPoolSize değeri sınırlar. İlk kapasite hesabını veritabanının ölçülmüş güvenli eşzamanlı sorgu limitiyle yapın; örneğin PostgreSQL tarafında uygulamaya ayrılmış 24 bağlantı varsa havuzu 20-22 aralığında başlatıp kalan bağlantıları migration, yönetim ve acil erişim için bırakın.
# application.yml
spring:
threads:
virtual:
enabled: true
datasource:
hikari:
maximum-pool-size: 22
minimum-idle: 4
connection-timeout: 750
validation-timeout: 250
max-lifetime: 1740000
keepalive-time: 300000
management:
endpoints:
web:
exposure:
include: health,metrics,prometheusconnection-timeout için 30 saniyelik varsayılanı körlemesine bırakmayın: havuz doluyken sanal thread bekler, ancak çağıran katmanın 1 saniyelik HTTP zaman aşımı varsa geç dönen iş zaten yararsızdır. 750 ms yalnızca uçtan uca deadline buna izin veriyorsa anlamlıdır. Havuzun gerçek durumunu tahmin yerine Actuator ile inceleyin: curl 'http://localhost:8080/actuator/metrics/hikaricp.connections.pending' ve hikaricp.connections.active metriklerini aynı yük testi zaman penceresinde kaydedin. Sürekli pozitif pending, CPU eksikliğinden çok bağlantı veya sorgu tutma süresinin darboğaz olduğuna işaret eder.
Bu ayrım, java eğitimi veya java programlama eğitimi sırasında sık atlanan üretim detayıdır: servlet iş parçacığı havuzu büyütülerek çözülebilen kuyruk, JDBC havuzu büyütülerek çözülmez. Havuzu veritabanının CPU/IO doygunluğunun üstüne çıkarmak, daha fazla paralel sorgu yerine disk kuyruğu ve lock wait üretir. pg_stat_activity, pg_stat_statements ya da kullanılan veritabanının eşdeğer sorgu görünümünde p95 yürütme süresi ve bekleme nedenini kontrol edin; havuz boyutunu ancak bu iki ölçümden sonra değiştirin.
Java backend geliştirmede pinning ve bloklayan sınırları JFR ile bulmak
Sanal thread, destekleyen bloklama noktalarında taşıyıcı platform thread'ini boşaltabilir; fakat bir sanal thread uzun süre monitor altında, yerel (native) çağrıda veya sürücüye özgü bloklayan bir yolda kalırsa pinning görülebilir. Bu davranış JDK ve sürücü kombinasyonuna bağlı olduğundan kod incelemesi yeterli değildir. Yük altında iki dakikalık Java Flight Recorder kaydı alın ve Java Mission Control içinde jdk.VirtualThreadPinned, jdk.JavaMonitorEnter ve jdk.SocketRead olaylarını aynı zaman çizelgesinde inceleyin.
# PID yerine Java sürecinin gerçek PID'sini yazın
jcmd 42173 JFR.start name=virtual-thread-check settings=profile filename=/tmp/virtual-thread-check.jfr duration=120s
# Kayıt tamamlandıktan sonra:
jmc /tmp/virtual-thread-check.jfrAşağıdaki desen, bir cache kilidi elde tutulurken JDBC çağrısı yapıldığı için kritik bölgeyi gereksizce uzatır. Sorun yalnızca kilit rekabeti değildir: kilit kuyruğundaki tüm istekler bağlantı alma ve SQL süresini dolaylı olarak artırır. Kilit altında yalnızca bellek içi durumu değiştirin; I/O'yu kilidin dışına taşıyın.
private final ReentrantLock cacheLock = new ReentrantLock();
Order loadOrder(UUID id) {
cacheLock.lock();
try {
Order cached = cache.get(id);
if (cached != null) return cached;
} finally {
cacheLock.unlock();
}
Order loaded = repository.findById(id).orElseThrow(); // JDBC: kilit dışında
cacheLock.lock();
try {
return cache.computeIfAbsent(id, ignored -> loaded);
} finally {
cacheLock.unlock();
}
}Bu değişikliği doğrularken yalnızca RPS'e bakmayın. Önce ve sonra aynı veri seti ve aynı k6 senaryosunda JFR'deki pinning olay sayısını, hikaricp.connections.pending tepe değerini ve HTTP p95'i karşılaştırın. Pinning sıfıra inse bile pending yükseliyorsa kök neden büyük olasılıkla kilit değil, uzun SQL veya N+1 sorgudur. Deneyimli ekiplerin yaygın hatası, JFR kaydını boşta almak veya yalnızca geliştirme veritabanında çalıştırmaktır; rekabet ve havuz kuyruğu oluşmadan bu olayların çoğu görünmez.
Hibernate ORM ve Spring Data JPA ile bağlantı tutma süresini kısaltmak
Hibernate ORM kullanırken sanal thread'lerin yüksek istek eşzamanlılığı, N+1 sorgusunun etkisini büyütür: tek bir endpoint 1 ana sorgu ve 30 ilişki sorgusu çalıştırıyorsa, 200 eşzamanlı istekte havuz üzerinde yaklaşık 6.200 sorguluk basınç oluşabilir. İlişkiyi her yerde EAGER yapmak çözüm değildir; gereksiz graph yükler. Spring Data JPA tarafında endpointin ihtiyacı olan ilişki için hedefli @EntityGraph kullanın.
public interface OrderRepository extends JpaRepository<Order, UUID> {
@EntityGraph(attributePaths = {"lines", "lines.product"})
@Query("select o from Order o where o.id = :id")
Optional<Order> findDetailById(UUID id);
}Sorgu sayısını SQL logundan değil, testte istatistikle doğrulayın. Hibernate'in geliştirme veya test profilinde istatistiklerini açıp aynı endpoint çağrısında prepareStatementCount değerini assertion'a bağlamak N+1 regresyonunu CI'da yakalar.
# application-test.yml
spring:
jpa:
properties:
hibernate.generate_statistics: true
hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS: 100
open-in-view: falseopen-in-view: false özellikle java microservices servislerinde önemlidir: açık persistence context'in HTTP yanıtı serileştirilene kadar yaşaması, lazy yüklemeyi transaction sınırı dışına sarkıtabilir ve bağlantı tutma süresini öngörülemez yapar. Servis katmanında açık bir salt-okunur transaction ve DTO dönüşümü tercih edin.
@Transactional(readOnly = true)
public OrderView detail(UUID id) {
Order order = repository.findDetailById(id).orElseThrow();
return OrderView.from(order); // gerekli ilişkiler EntityGraph ile yüklendi
} Bu yaklaşım spring framework transaction proxy'sinin yalnızca public, proxy üzerinden çağrılan metotlarda çalıştığını da hesaba katar; aynı sınıftaki this.detail(...) çağrısı @Transactional interceptor'ını tetiklemez.Java microservices yük testi ve Spring AI ile ölçüm araçlarını sınırlamak
Kapasite değişikliğini kanıtlamak için önce mevcut yapılandırmayla sabit bir senaryo çalıştırın, sonra yalnızca tek değişkeni (örneğin N+1 düzeltmesi veya havuz boyutu) değiştirin. k6 betiği p95/p99 sürelerini ve hata oranını üretirken Prometheus'tan aynı zaman aralığındaki Hikari metriklerini alın; iki farklı veri hacmiyle yapılan testleri karşılaştırmak geçerli bir önce-sonra sonucu vermez.
import http from 'k6/http';
import { check } from 'k6';
export const options = {
scenarios: { steady: { executor: 'constant-arrival-rate', rate: 180,
timeUnit: '1s', duration: '3m', preAllocatedVUs: 80, maxVUs: 1200 } },
thresholds: { http_req_failed: ['rate<0.01'], http_req_duration: ['p(95)<250'] }
};
export default function () {
const r = http.get(`${__ENV.BASE_URL}/orders/${__ENV.ORDER_ID}`);
check(r, { '200': x => x.status === 200 });
}Sonuçları Little yasasıyla yorumlayın: 180 istek/s hızında veritabanı bölümünde ortalama 80 ms kalış, yaklaşık 14,4 eşzamanlı aktif iş demektir; 22 bağlantılık havuz bu durumda makul bir başlangıç noktasıdır. Aynı sorgu lock beklemesiyle 250 ms'ye çıkarsa beklenen eşzamanlılık 45'e yaklaşır ve pending yükselir. Bu durumda havuzu 45'e çıkarmak yerine pg_stat_statements ile en yüksek toplam süreli sorguyu, indeks planını ve transaction içindeki uzak HTTP çağrılarını inceleyin.
spring ai veya spring mcp kullanan bir operasyon asistanı tasarlıyorsanız, model context protocol aracına ham SQL ya da sınırsız Prometheus sorgusu vermeyin. Araç, izinli metriği ve sabit zaman penceresini kodda sınırlamalıdır; böylece model yalnızca teşhis için gerekli veriyi okuyabilir.
@Tool(description = "Son 5 dakikadaki bekleyen JDBC bağlantısı sayısını getirir")
public double jdbcPending() {
return meterRegistry.find("hikaricp.connections.pending")
.gauge() == null ? 0.0 : meterRegistry.find("hikaricp.connections.pending").gauge().value();
} Bu tür sınırlandırılmış araçlar, java kursu veya java fullstack eğitimi projelerinde de uygulanabilir bir pratik sağlar: modelden gelen serbest metin veritabanı yetkisine dönüşmez.İlgili Eğitim
YTÜSEM İlgili Eğitim
Java Spring Boot ReactJS FullStack Eğitimi (Yıldız Teknik Üniversitesi SEM)
Sık Sorulan Sorular
Spring Boot eğitiminde sanal thread kullanırken HikariCP kaç bağlantı olmalı?
Sayı CPU çekirdeğinden türetilmez; veritabanının ölçülmüş eşzamanlı sorgu kapasitesinden türetilir. Başlangıçta uygulamaya ayrılan DB bağlantılarının altında bir maximumPoolSize seçin, k6 ile sabit arrival-rate testi yapın ve hikaricp.connections.pending, DB lock wait ve p95 SQL süresini birlikte ölçün. Pending artarken DB CPU veya disk doygunsa havuzu büyütmeyin.
Java backend geliştirmede sanal thread pinning nasıl tespit edilir?
Üretime benzer yük altında jcmd PID JFR.start settings=profile duration=120s ile kayıt alın. Java Mission Control içinde jdk.VirtualThreadPinned olaylarının stack trace'lerini açın; monitor, native çağrı veya sürücü katmanında hangi metodun taşıyıcı thread'i tuttuğunu buradan bulun. Boşta alınan JFR kaydı anlamlı bir sinyal vermez.
Hibernate ORM ve Spring Data JPA ile N+1 sorgusu nasıl engellenir?
İhtiyaç duyulan endpoint için repository metoduna @EntityGraph(attributePaths = {...}) ekleyin veya projeksiyon/fetch join kullanın. Test profilinde hibernate.generate_statistics=true açarak çağrı başına prepareStatementCount değerini ölçün; hedef, sorgu sayısını tahmin etmek değil CI testinde sayısal olarak sabitlemektir.
Spring AI ve Spring MCP ile model context protocol aracı veritabanına doğrudan erişmeli mi?
Hayır. Salt-okunur ve dar kapsamlı araçlar tanımlayın; örneğin yalnızca hikaricp.connections.pending metriğini son beş dakika için döndüren bir @Tool metodu. Araca serbest SQL, bağlantı dizesi veya sınırsız metrik sorgu parametresi vermek hem veri sızıntısı hem de maliyetli sorgu riski oluşturur.
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.



