Spring Boot eğitimi kapsamında HikariCP doygunluğunu Micrometer, JFR ve PostgreSQL metrikleriyle teşhis edin. Hibernate ORM transaction sınırlarını düzelterek bekleyen istekleri ve tail latency'yi ölçülebilir biçimde azaltın.
Spring Boot Eğitimi: JDBC Havuz Tükenmesini Ölçme ve Giderme
Spring Boot eğitimi ile HikariCP doygunluğunu doğru tanımlamak
JDBC havuz sorunu, sadece `active == maximumPoolSize` olduğunda başlamaz. Kritik sinyal, HikariCP'nin bağlantı bekleyen kuyruk uzunluğu olan `hikaricp.connections.pending` metriğinin sürekli sıfırdan büyük kalmasıdır. Micrometer ve Prometheus kullanıyorsanız `/actuator/metrics/hikaricp.connections.pending` çıktısını önce doğrulayın, sonra 5 dakikalık pencerede p95 değerini izleyin. `pending > 0` iken `active` yüksek ve `idle` sıfıra yakınsa uygulama bağlantı kapasitesine ulaşmıştır; `pending > 0` ancak `active` düşükse ayrı bir havuz, yanlış meter etiketi veya bağlantı oluşturma problemi araştırılmalıdır.
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.metrics.distribution.percentiles-histogram.http.server.requests=true
spring.datasource.hikari.pool-name=orders-pool
spring.datasource.hikari.maximum-pool-size=16
spring.datasource.hikari.minimum-idle=4
spring.datasource.hikari.connection-timeout=1000
spring.datasource.hikari.validation-timeout=500Prometheus'ta yalnızca ortalama istek süresine bakmayın. Aşağıdaki sorgu, havuz bekleyenleri istek p99'u ile aynı grafikte karşılaştırır. Önce yük testi altında bu iki serinin zaman korelasyonunu kaydedin; düzeltmeden sonra aynı test profiliyle `pending` p95'inin sıfıra yaklaşmasını ve `http_server_requests_seconds` p99'unun düşmesini karşılaştırın. Bu yaklaşım, CPU kaynaklı gecikmeyi veritabanı bağlantısı beklemesinden ayırır.
max_over_time(hikaricp_connections_pending{pool="orders-pool"}[5m])
histogram_quantile(0.99,
sum by (le) (rate(http_server_requests_seconds_bucket{uri="/orders"}[5m]))
)Bir java eğitimi veya java programlama eğitimi içinde bu metrikleri yalnızca property ezberi olarak değil, kuyruk teorisiyle ele almak gerekir: yaklaşık eşzamanlı bağlantı ihtiyacı `RPS x DB'de kalma süresi` ile tahmin edilir. Örneğin saniyede 80 sorgu ve bağlantı başına 120 ms kullanım, yaklaşık 9.6 eşzamanlı bağlantı üretir; 16 bağlantılık havuz başlangıç için makuldür. Ancak 120 ms'nin 80 ms'si uzak HTTP çağrısıysa havuzu büyütmek sorunu gizler, transaction sınırını düzeltmek gerekir.
Hibernate ORM ve Spring Data JPA ile bağlantının gereksiz tutulduğu yeri bulmak
Hibernate ORM kullanan bir Spring Data JPA serviste bağlantının ne kadar süre tutulduğunu görmek için Java Flight Recorder kaydı alın. JFR, thread park süreleri, socket okumaları ve JDBC olaylarını aynı zaman çizelgesinde incelemeyi sağlar. Üretimde düşük ek yükle 120 saniyelik kayıt almak için aşağıdaki komutu çalıştırın; oluşan `.jfr` dosyasını JDK Mission Control ile açıp `Java Application` altındaki `JDBC` ve `Thread Park` görünümlerini eşleyin.
jcmd $(pgrep -f 'orders-service.jar') JFR.start name=hikari-investigation settings=profile duration=120s filename=/tmp/hikari-investigation.jfrSık görülen hata, `@Transactional` metodu içinde JPA sorgusundan sonra ödeme, kimlik veya envanter HTTP çağrısı yapmaktır. Transaction aktifken Hibernate bağlantıyı almış olabilir ve uzak servis 800 ms beklerken havuzdaki slot tüketilmeye devam eder. Aşağıdaki ilk örnekte bağlantı uzak çağrı boyunca tutulabilir. İkinci örnekte veritabanı okuması kısa, uzak çağrı transaction dışı, nihai durum güncellemesi ise kısa bir transaction'dır. Özellikle java backend geliştirme yapan ekiplerde bu ayrım, pool size artırmaktan önce uygulanmalıdır.
// Kotu: DB baglantisi remote call boyunca elde kalabilir
@Transactional
public void reserve(UUID orderId) {
Order order = orderRepository.findById(orderId).orElseThrow();
paymentClient.authorize(order.total());
order.markReserved();
}
// Daha dar transaction pencereleri
public void reserve(UUID orderId) {
Money total = transactionTemplate.execute(status -> {
Order order = orderRepository.findById(orderId).orElseThrow();
return order.total();
});
PaymentResult result = paymentClient.authorize(total);
transactionTemplate.executeWithoutResult(status -> {
Order order = orderRepository.findById(orderId).orElseThrow();
order.applyPayment(result.authorizationId());
});
}Edge case: Yukarıdaki iki aşamalı akışta ilk okumadan sonra sipariş değişebilir. Bu nedenle entity'ye `@Version` ekleyin ve ikinci transaction'da optimistic locking hatasını iş kuralına göre tekrar deneyin veya isteği reddedin. Uzak çağrıyı transaction dışına almak bağlantı tutma süresini azaltır, fakat atomiklik sağlamaz; ödeme sağlayıcısının idempotency anahtarı ve durum makinesi ayrıca tasarlanmalıdır.
@Entity
class Order {
@Id UUID id;
@Version long version;
// ...
}Spring Framework transaction sınırları ve Open Session in View tuzağı
Spring Framework tabanlı MVC uygulamalarında `spring.jpa.open-in-view=true` ise persistence context HTTP yanıtı tamamlanana kadar yaşayabilir. Bu ayar her zaman bağlantıyı fiziksel olarak tutmaz, çünkü Hibernate connection handling yapılandırması ve sorgu anı belirleyicidir; buna rağmen controller veya JSON serileştirme sırasında lazy ilişki yüklenmesi tetiklenebilir. Bu durum sorguları servis katmanının dışına taşır ve yük altında bağlantı edinimini öngörülemez hale getirir. Önce staging ortamında OSIV'i kapatın, ardından `LazyInitializationException` üreten endpointleri explicit fetch planına taşıyın.
spring.jpa.open-in-view=false
spring.jpa.properties.hibernate.generate_statistics=true
spring.jpa.properties.hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS=100Bir liste endpointinde entity döndürmek yerine Spring Data JPA projection kullanın. Böylece Jackson'ın lazy collection'a dokunarak satır başına ek sorgu üretmesini engellersiniz. Aşağıdaki sorgu, `customer` alanını tek join ile seçer ve sadece API'nin ihtiyaç duyduğu kolonları taşır. Hibernate istatistiklerinde `prepared statement count` değerini test öncesi ve sonrası karşılaştırın: 100 sipariş için 101 statement yerine 1 statement beklenir.
public interface OrderListRow {
UUID getId();
String getCustomerName();
BigDecimal getTotal();
}
@Query("""
select o.id as id, c.name as customerName, o.total as total
from Order o join o.customer c
where o.status = :status
order by o.createdAt desc
""")
List<OrderListRow> findListRows(@Param("status") OrderStatus status);Bu konu bir java kursu veya java fullstack eğitimi içinde frontend kaynaklı gecikmelerle de ilişkilidir: istemci yavaş okurken response yazımı uzayabilir, ancak OSIV kapalıyken veritabanı erişimi controller'a sızmadığı için bu gecikme yeni lazy sorgu başlatmaz. API sözleşmesini DTO ile sabitlemek, hem serialization sırasında sorgu patlamasını hem de entity alanlarının istemeden dışarı açılmasını azaltır.
Java microservices için havuz boyutunu veritabanı kapasitesiyle sınırlandırmak
Havuz boyutunu CPU çekirdeği sayısına göre körlemesine belirlemeyin. Aynı PostgreSQL kümesine bağlanan her java microservices replikasının havuzu toplam bağlantı bütçesini tüketir. Örneğin PostgreSQL `max_connections=200`, operasyonel kullanım için 30 bağlantı ve iki ayrı uygulama grubu için 50 bağlantı ayrıldıysa, dört orders pod'u için teorik üst sınır `(200 - 30 - 50) / 4 = 30` olur. Buna rağmen başlangıç değeri olarak 30 seçmek zorunda değilsiniz; JFR'de veritabanı CPU doygunluğu veya lock wait görülüyorsa 12-16 gibi daha düşük havuzlar daha iyi kuyruk davranışı verebilir.
spring.datasource.hikari.maximum-pool-size=16
spring.datasource.hikari.minimum-idle=4
spring.datasource.hikari.max-lifetime=1740000
spring.datasource.hikari.keepalive-time=300000
spring.datasource.hikari.leak-detection-threshold=2000`max-lifetime`, veritabanı veya ağ katmanının bağlantıyı kapatma süresinden kısa olmalıdır; aksi halde havuz kapatılmış bir socket'i kiraya vermeyi deneyip hata üretebilir. `leak-detection-threshold` kalıcı bir performans ayarı değildir: stack trace üretimi maliyetlidir, olay incelemesinde geçici açın. PostgreSQL tarafında uygulama adını ve aktif beklemeleri sorgulayarak Hikari metriklerini doğrulayın.
select application_name, state, wait_event_type, wait_event,
count(*) as connections
from pg_stat_activity
where datname = current_database()
group by 1, 2, 3, 4
order by connections desc;Spring AI kullanan bir servis, bir LLM yanıtı sırasında araç çağrısı yapabilir; Spring MCP entegrasyonunda bu araçlar model context protocol üzerinden veritabanı kullanan internal endpointlere gidebilir. Bu akışta JDBC transaction'ını model yanıtı veya tool callback süresince açık bırakmayın. Model context protocol çağrısının timeout'unu HTTP istemcisinde sınırlayın, veritabanı okumasını kısa bir read-only transaction ile tamamlayın ve modele yalnızca DTO veya sıkıştırılmış sonuç aktarın. Aksi halde birkaç yavaş model isteği, normal sipariş trafiğinin Hikari kuyruğunu doldurabilir.
Önce-sonra testi: k6, Prometheus ve bağlantı bekleme bütçesi
Düzeltmenin etkisini aynı veri seti ve aynı eşzamanlılıkta ölçün. k6 ile 2 dakika 40 sanal kullanıcı çalıştırın; testten önce ve sonra şu dört değeri kaydedin: HTTP p95/p99, `hikaricp.connections.pending` maksimumu, PostgreSQL `wait_event_type` dağılımı ve hata oranı. Sadece throughput artmışsa başarılı saymayın; p99 düşerken `pending` sıfıra yakın kalmalı ve `connectionTimeout` hatası oluşmamalıdır.
import http from 'k6/http';
import { check } from 'k6';
export const options = {
vus: 40,
duration: '2m',
thresholds: {
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<400', 'p(99)<900']
}
};
export default function () {
const res = http.get(`${__ENV.BASE_URL}/orders?status=OPEN`);
check(res, { '200': r => r.status === 200 });
}Örnek karar kuralı kurun: değişiklik öncesinde p99 1.8 saniye, `pending` maksimumu 24 ve connection timeout oranı yüzde 3 ise; transaction daraltma sonrası p99 450 ms, `pending` maksimumu 0 ve timeout oranı yüzde 0.1 altına iniyorsa iyileşme doğrulanmıştır. Buna karşılık yalnızca `maximum-pool-size=16` değerini 48'e çıkarmak p99'u kısa süreli düşürüp PostgreSQL'de daha fazla lock contention yaratabilir. Bu nedenle değişiklik setini ayrı deneyler halinde uygulayın: önce transaction kapsamı, sonra fetch planı, en son havuz boyutu.
İ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ğitimi sırasında HikariCP connection timeout nasıl teşhis edilir?
Önce `/actuator/prometheus` üzerinden `hikaricp_connections_pending`, `hikaricp_connections_active` ve `hikaricp_connections_timeout_total` metriklerini alın. Aynı 5 dakikalık pencerede HTTP p99'u karşılaştırın. Ardından `jcmd
Spring Data JPA ve Hibernate ORM uygulamasında maximum-pool-size kaç olmalı?
Sabit bir sayı yoktur. Önce tüm pod'ların toplamı için veritabanı bağlantı bütçesini hesaplayın: `(max_connections - rezervler) / pod_sayisi`. Sonra k6 ile eş yükte 8, 12, 16 gibi aday değerleri deneyin. En düşük HTTP p99'u seçmek tek başına yeterli değildir; PostgreSQL CPU, lock wait ve `hikaricp.connections.pending` metrikleri de yükselmeyen en küçük değeri seçin.
Spring AI ve Spring MCP kullanan Java backend geliştirme servisinde JDBC bağlantısı neden tükenir?
Model veya tool çağrısı `@Transactional` metodun içinde yapılıyorsa, Hibernate sorgudan sonra edinilmiş bağlantı LLM yanıtı, model context protocol tool callback'i veya uzak HTTP timeout'u boyunca meşgul kalabilir. Veritabanı okumasını kısa `TransactionTemplate` bloğunda bitirin, modele entity yerine DTO verin ve tool istemcisinde kesin timeout tanımlayın. Bu düzenlemeden sonra `hikaricp.connections.pending` p95 değerini önce-sonra testinde karşılaş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.


