HikariCP ile İleri Düzey Havuz Tasarımı
HikariCP havuzlarının kapasite, zaman aşımı, yaşam döngüsü, doğrulama ve gözlemlenebilirlik ayarlarını üretim yükü altında değerlendirir. Amaç düşük gecikme ile güvenilir kaynak kullanımını birlikte sağlamaktır.
HikariCP ayarlarının amacı mümkün olan en fazla JDBC bağlantısını açık tutmak değildir. Amaç, veritabanının işleyebileceği eş zamanlılığı aşmadan bağlantı edinme süresini sınırlamak, kuyruk büyümesini görünür tutmak ve hata sırasında sistemin öngörülebilir biçimde davranmasını sağlamaktır.
Bağlantı havuzu, veritabanının kapasitesini artırmaz. İstemci tarafındaki eş zamanlılığı veritabanına aktarır ve bağlantı kurma maliyetini tekrar kullanımla azaltır. Bu nedenle "maximumPoolSize" yükseltildiğinde uygulama daha hızlı çalışmak zorunda değildir. Büyük havuz, veritabanında daha fazla oturum, daha fazla etkin sorgu, daha yoğun kilit rekabeti ve daha yüksek bağlam değiştirme maliyeti oluşturabilir.
Bu ayrım, çok sayıda veri kaynağı bulunan sistemlerde daha belirgindir. Örneğin 82 ayrı "DataSource" için her biri 10 bağlantılı havuz tanımlandığında teorik üst sınır 820 fiziksel bağlantıdır. Aynı veri kaynaklarının beş ayrı kopyayla temsil edilmesi, havuz sayısını 410'a ve teorik bağlantı üst sınırını 4.100'e çıkarır. Bu sayılar uygulama trafiğinden önce Oracle "PROCESSES", "SESSIONS", RAC servis dağılımı, ağ soketleri ve işletim sistemi kaynakları üzerinde baskı oluşturur.
Havuz boyutunun belirlenmesi
Havuz boyutu saatlik istek sayısından çıkarılamaz. Bir sistem saatte milyonlarca istek alırken aynı anda yalnızca birkaç bağlantıya ihtiyaç duyabilir. Belirleyici olan, bağlantının ne kadar süre işgal edildiği ve bu işgalin ne kadar eş zamanlı gerçekleştiğidir.
İlk yaklaşım Little Yasası ile kurulabilir:
L = λW"L", aynı anda meşgul olması beklenen bağlantı sayısını gösterir. "λ", saniyedeki veritabanı işlemi sayısıdır. "W" ise bir işlemin bağlantıyı elinde tuttuğu ortalama süredir.
Saniyede 500 veritabanı işlemi yapan ve bağlantıyı ortalama 20 ms tutan bir sistem için ortalama eş zamanlılık şöyledir:
L = 500 x 0,020 = 10Bu sonuç doğrudan "maximumPoolSize=10" anlamına gelmez. Ortalama değer, ani yükleri ve uzun kuyruk kuyruğunu göstermez. P95 ve P99 işlem süreleri, sorgu dağılımı, transaction süresi ve trafik patlamaları ayrıca ölçülmelidir.
HikariCP belgeleri de büyük havuzun otomatik olarak daha yüksek performans üretmediğini vurgular. Veritabanının çekirdek, disk ve sorgu yürütme kapasitesini aşan bağlantılar çalışmak yerine kaynak bekler. Bazı yüklerde havuz küçültüldüğünde gecikmenin belirgin biçimde azalabilmesi bu nedenle şaşırtıcı değildir.
Pratik boyutlandırma şu ölçümlerle yapılır:
- Etkin bağlantı sayısının zaman dağılımı
- Boş bağlantı sayısı
- Bağlantı bekleyen iş parçacığı sayısı
- "getConnection()" bekleme süresi
- Transaction ve sorgu sürelerinin P95 ve P99 değerleri
- Oracle oturum ve işlem sınırları
- RAC düğümlerindeki servis dağılımı
- Yük artarken veritabanı CPU ve disk kuyruğunun davranışı
Havuz sürekli doluysa çözüm her zaman havuzu büyütmek değildir. Önce bağlantının neden uzun süre tutulduğu araştırılmalıdır. Yavaş SQL, geniş transaction sınırı, ağ üzerinden yapılan işlem sırasında bağlantının açık tutulması, sonuç kümesinin geç tüketilmesi ve ORM kaynaklı N+1 sorguları aynı belirtiyi üretebilir.
Minimum bağlantı ve dinamik büyüme
"minimumIdle", boşta tutulacak asgari bağlantı sayısını belirler. "minimumIdle" belirtilmezse HikariCP varsayılan olarak bunu "maximumPoolSize" ile aynı değerde ele alır. Böylece havuz sabit boyutluya yakın davranır. HikariCP belgeleri, ani yüklerde bağlantı oluşturma gecikmesini önlemek için sabit boyutlu havuzu çoğu durumda uygun varsayılan olarak görür.
Bu tercih her sistem için doğru değildir. Çok sayıda seyrek kullanılan veri kaynağında sabit havuz ciddi bağlantı israfı oluşturur. 82 Oracle şemasının yalnız küçük bir bölümü aynı anda kullanılıyorsa her havuzu başlangıçta tam kapasite açmak gereksizdir. Bu tür yapılarda düşük "minimumIdle", ölçülü "maximumPoolSize" ve kontrollü bağlantı oluşturma daha uygun olabilir.
Dinamik büyümenin bedeli ilk isteklerde bağlantı kurma gecikmesidir. Oracle bağlantısı yalnız TCP soketinin açılmasından oluşmaz. Listener yönlendirmesi, Oracle Net görüşmesi, kimlik doğrulama, oturum kurulumu ve varsa TLS veya gelişmiş güvenlik adımları tamamlanır. Yük anında birden fazla havuzun eş zamanlı büyümesi, bağlantı fırtınası oluşturabilir.
"idleTimeout" yalnızca "minimumIdle < maximumPoolSize" olduğunda anlamlıdır. Havuz, boş bağlantıları "minimumIdle" altına düşürmez. Boş bağlantının tam olarak ayarlanan milisaniyede kapatılması da garanti edilmez. Housekeeper taramasına bağlı zaman sapması bulunur.
Zaman aşımı katmanları
HikariCP sistemlerinde tek bir timeout yoktur. Birbirinden farklı başarısızlıkları sınırlayan birkaç zaman aşımı birlikte tasarlanmalıdır.
"connectionTimeout", uygulama kodunun havuzdan bağlantı almak için ne kadar bekleyeceğini belirler. Havuz doluysa ve süre aşılırsa "SQLException" oluşur. Bu değer, kullanıcı isteğinin toplam zaman bütçesinden kısa olmalıdır. Aksi halde HTTP isteği veya üst katman işlemi sonlanmış olsa bile iş parçacığı bağlantı beklemeye devam edebilir.
"validationTimeout", bağlantının canlılığını denetlemek için ayrılan süredir. "connectionTimeout" değerinden küçük olmalıdır. Doğrulama sorgusunun veya "Connection.isValid()" çağrısının ağda uzun süre asılı kalmasını tek başına her zaman engellemez. JDBC sürücüsünün soket ve ağ zaman aşımı da yapılandırılmalıdır.
"maxLifetime", fiziksel bağlantının havuzda yaşayabileceği azami süreyi sınırlar. Bu değer veritabanı, güvenlik duvarı, NAT, yük dengeleyici veya ağ cihazlarının bağlantıyı kapatma süresinden kısa seçilir. Amaç, bağlantının dış katman tarafından sessizce öldürülmesinden önce HikariCP tarafından kontrollü biçimde yenilenmesidir.
"keepaliveTime", uzun süre kullanılmayan bağlantının ağ yolunda canlı tutulmasına yardımcı olur. "maxLifetime" değerinden küçük olmalıdır. Keepalive, aktif olarak uygulamaya verilmiş bağlantıda çalışmaz. Yalnızca havuzun denetimindeki boş bağlantılar üzerinde uygulanır.
"connectionTimeout=2000" ve "validationTimeout=1000" gibi kısa değerler, düşük gecikmeli kurum içi sistemlerde makul bir başlangıç olabilir. Ancak bu değerler ölçüm yapılmadan evrensel ayar olarak kullanılamaz. RAC düğüm geçişi, uzak ağ, yoğun listener veya güvenlik görüşmesi iki saniyeyi aşabiliyorsa uygulama gereksiz biçimde bağlantı hatası üretir.
Timeout zinciri şu sırayı korumalıdır:
JDBC sorgu zaman aşımı < transaction zaman aşımı < uygulama isteği zaman aşımı
Bağlantı edinme süresi de bu toplam bütçenin yalnız bir bölümünü tüketmelidir. Veritabanı çağrısı 30 saniye sürebilir durumdayken "connectionTimeout" değerini 30 saniye yapmak, aşırı yükte kuyrukların uzun süre bellekte kalmasına yol açar.
Oracle ağ hataları ve hızlı toparlanma
HikariCP yalnız kendi denetimindeki bağlantıları yönetebilir. Uygulama bir bağlantıyı havuzdan aldıktan sonra bu bağlantı Oracle JDBC çağrısında takılırsa HikariCP işlemi zorla kurtaramaz. Ağ bölünmesi sırasında gönderilen TCP paketine yanıt gelmezse çağrı işletim sistemi zaman aşımına kadar asılı kalabilir. HikariCP'nin hızlı toparlanma belgesi, bu nedenle JDBC sürücüsü düzeyinde soket zaman aşımı tanımlanmasını gerekli görür.
Oracle JDBC tarafında bağlantı kurma ve okuma için farklı sınırlar bulunur. "CONNECT_TIMEOUT", "TRANSPORT_CONNECT_TIMEOUT", "oracle.net.CONNECT_TIMEOUT", "oracle.jdbc.ReadTimeout" ve "Connection.setNetworkTimeout()" aynı aşamayı kontrol etmez. Özelliklerin anlamı sürücü sürümüne ve bağlantı URL'sinin yapısına göre doğrulanmalıdır. Oracle, bağlantı zaman aşımının ADDRESS listelerindeki her adres veya IP için ayrı uygulanabildiğini belirtir.
RAC ve SCAN ortamında yeniden deneme ayarları daha dikkatli ele alınmalıdır. Tek bir SCAN adı birden fazla IP ve listener yoluna çözülebilir. Her adreste uzun bağlantı zaman aşımı kullanılırsa toplam başarısızlık süresi beklenenden çok daha uzun olur.
Pool katmanında kör yeniden deneme yapılmamalıdır. Transaction'ın sunucuya ulaşıp ulaşmadığı bilinmiyorsa aynı yazma işlemini tekrar çalıştırmak çift kayıt üretebilir. Yeniden deneme yalnızca işlemin idempotent olduğu veya transaction sonucunun güvenilir biçimde belirlenebildiği durumlarda uygulanmalıdır.
Transaction sınırı ve bağlantı durumu
HikariCP, bağlantı geri verildiğinde belirli JDBC durumlarını sıfırlamaya çalışır. "autoCommit", "readOnly", transaction isolation, katalog ve bazı diğer durumlar bunlara dahildir. Açık transaction varsa bağlantı iade edilirken rollback uygulanabilir. Açık bırakılmış "Statement" nesneleri de proxy katmanında izlenerek kapatılır.
Bu koruma, hatalı transaction tasarımını güvenli hale getirmez. Bağlantı yalnız SQL çalışırken elde tutulmalıdır. Transaction içinde HTTP çağrısı, dosya erişimi, yapay zeka çıkarımı veya uzun CPU işlemi yapılmamalıdır. Bu işlemler veritabanı bağlantısını kullanmasa bile havuz kapasitesini tüketir.
"spring.jpa.open-in-view=false" ayarı, web isteği boyunca persistence context ve bağlantı kullanımının istemeden genişlemesini önlemek için önemlidir. Transaction sınırı servis katmanında açıkça belirlenmelidir. Lazy yükleme gereksinimi sorgu planı, DTO projeksiyonu veya açık fetch stratejisiyle çözülmelidir.
Isolation seviyesi SQL komutuyla değiştirilmemelidir. HikariCP değişikliği ancak JDBC "Connection.setTransactionIsolation()" çağrısı üzerinden güvenilir biçimde algılayabilir. SQL ile değiştirilen isolation seviyesi bağlantı havuza döndüğünde sıfırlanmayabilir ve sonraki kullanıcıya taşabilir.
"autoCommit=false" kullanıldığında başarılı işlemde "commit", hata yolunda "rollback" açık olmalıdır. Spring transaction yönetimi bu işlemleri üstlenebilir. Aynı bağlantı üzerinde framework transaction'ı ile elle "commit()" veya "rollback()" çağrılarını karıştırmak transaction durumunu belirsizleştirir.
Virtual thread sınırı
Virtual thread, JDBC bağlantısı bekleyen iş parçacığının platform thread'i işgal etmesini azaltabilir. Veritabanı bağlantı sayısını artırmaz. On binlerce virtual thread aynı anda "getConnection()" çağırdığında havuz yine "maximumPoolSize" kadar bağlantı verir. Kalan çağrılar havuz kuyruğunda bekler.
Bu nedenle virtual thread kullanılan sistemlerde havuz, doğal bir eş zamanlılık sınırlayıcısı haline gelir. Ancak sınırsız sayıda bekleyen virtual thread kabul edilebilir bir backpressure mekanizması değildir. Bekleyen görevler heap üzerinde durum taşır, istek zaman aşımı sonrasında gereksiz iş yapabilir ve hata anında büyük bir uyanma dalgası oluşturabilir.
Üst katmanda bounded queue, semaphore, admission control veya load shedding gerekir. HikariCP kuyruğu son savunma hattı olmalıdır. Veritabanının kapasitesi 20 eş zamanlı transaction ise uygulamanın 20.000 görevi havuz önünde bekletmesi sistemi daha dayanıklı yapmaz.
HikariCP'nin housekeeper, connection adder ve benzeri iç görevleri platform thread üzerinde çalışabilir. Bunları virtual thread'e çevirmek beklenen bir optimizasyon değildir. Asıl kazanç, uygulama isteği iş parçacıklarının bloklayan JDBC çağrıları sırasında platform thread tüketmemesidir.
Leak detection ve pool locking
"leakDetectionThreshold", bağlantı belirlenen süreden uzun süre uygulamada kaldığında tanı amaçlı stack trace üretir. Bu özellik bağlantıyı geri almaz, transaction'ı sonlandırmaz ve kaçağı düzeltmez. Yalnızca bağlantının nerede alındığını bildirir.
Çok düşük eşik, normal uzun transaction'ları kaçak gibi raporlar. Yoğun üretim sisteminde stack trace üretimi ve log hacmi ek maliyet doğurur. Bu nedenle 2.000 ms gibi düşük değerler tanılama sırasında yararlı olabilir, fakat sorguları birkaç saniye süren üretim sisteminde sürekli açık tutulmamalıdır. Eşik, normal P99 transaction süresinin üzerinde seçilmeli veya sorun çözüldükten sonra kapatılmalıdır.
Gerçek bağlantı kaçağında "active" bağlantı sayısı yükselir ve geri düşmez. Yavaş sorguda ise bağlantılar geç de olsa havuza döner. Bu iki durum zaman serisiyle ayrılır.
Bir iş parçacığının aynı anda birden fazla bağlantı edinmesi pool locking riski doğurur. "Tn" iş parçacığının her biri en fazla "Cm" bağlantı tutuyorsa kilitlenmeyi önlemek için verilen alt sınır şöyledir:
poolSize = Tn x (Cm - 1) + 1
HikariCP belgeleri bu formülü açıklarken asıl çözümün havuzu büyütmek değil, aynı iş akışının birden fazla bağlantı edinmesini ortadan kaldırmak olduğunu belirtir.
Gözlemlenebilirlik ve arıza teşhisi
HikariCP ayarı log dosyasındaki yapılandırma çıktısına bakılarak doğrulanamaz. Pool davranışı zaman serisi olarak izlenmelidir. En az şu ölçümler gerekir:
- "active"
- "idle"
- "pending"
- "max"
- bağlantı edinme süresi
- bağlantı kullanım süresi
- bağlantı oluşturma süresi
- timeout sayısı
"active=max" ve "pending>0" kalıcı hale geliyorsa pool exhaustion vardır. Veritabanı CPU'su düşükse bağlantılar uygulama katmanında gereğinden uzun tutuluyor olabilir. Veritabanı CPU'su ve disk beklemesi yüksekse havuzu büyütmek sorunu ağırlaştırabilir.
Thread dump içinde "HikariPool.getConnection()" beklemeleri havuz kıtlığını gösterir. "OracleStatement.execute", "T4C...", "TimeoutSocketChannel.read" veya "NIOPacket" çağrılarında yığılma varsa sorun havuzdan çok Oracle yürütme veya ağ katmanındadır. "HouseKeeper", "KeepaliveTask" ve "isConnectionAlive" üzerinde yoğun bekleme görülmesi, doğrulama çağrılarının ağda geciktiğini veya çok sayıda havuzun aynı anda bakım yaptığını gösterebilir.
Tek anlık thread dump yeterli değildir. Ardışık dump'lar, Java Flight Recorder, wall-clock profiler ve Oracle AWR veya ASH verileri aynı zaman aralığında karşılaştırılmalıdır. CPU profili yalnız çalışan kodu gösterir. JDBC soketinde bekleyen thread'ler düşük CPU tükettiği için CPU profillerinde önemsiz görünebilir.
Çok sayıda "DataSource" bulunan yapılarda metrikler pool adı, şema ve servis bilgisiyle etiketlenmelidir. Etiket sayısı denetlenmezse yüksek cardinality oluşur. Her SQL metnini veya kullanıcı kimliğini metrik etiketi yapmak uygun değildir.
Yaşam döngüsü ve yapılandırma disiplini
Spring Boot, JDBC veya JPA starter kullanıldığında HikariCP'yi varsayılan havuz olarak seçebilir. Birden fazla "DataSource" elle tanımlandığında otomatik yapılandırmanın hangi bean için çalıştığı açıkça kontrol edilmelidir. Kendi "DataSource" bean'i tanımlandığında bazı otomatik yapılandırma davranışları devre dışı kalır.
Her "HikariDataSource" uygulama kapanırken "close()" ile kapatılmalıdır. Aksi halde housekeeper thread'leri ve fiziksel bağlantılar hot deploy veya test çalışmaları arasında sızabilir. HikariCP bu yaşam döngüsü gereksinimini açıkça belirtir.
"initializationFailTimeout", uygulamanın veritabanı erişilemezken başlatılıp başlatılmayacağını belirleyen politikanın parçasıdır. Kritik bir servis veritabanı olmadan iş yapamıyorsa fail-fast tercih edilebilir. Birden fazla bağımsız veri kaynağı bulunan sistemde tek şemanın erişilememesi bütün uygulamayı durdurmamalıysa havuzların yaşam döngüsü ayrı yönetilebilir. Bu karar teknik bir varsayılan değil, servis sözleşmesidir.
HikariCP "PreparedStatement" önbelleğini havuz katmanında tutmaz. Önbellek gerekiyorsa JDBC sürücüsünün bağlantı başına veya sunucu tarafındaki mekanizması kullanılmalıdır. Havuz katmanında genel statement cache, aynı SQL'in her bağlantı için ayrı yürütme durumu taşıması nedeniyle beklenmedik bellek maliyeti oluşturabilir. HikariCP belgeleri de hazırlanan ifade önbelleğini sürücüye bırakır.
HikariCP'nin düşük gecikmesi "ConcurrentBag", hızlı erişim yolları, proxy nesneleri ve JIT dostu kod yapısı gibi iç optimizasyonlardan gelir. Proje, bazı sıcak metotları JIT inline sınırlarında tutmak için bytecode düzeyinde düzenlemeler yaptığını açıklar. Bu ayrıntılar havuz ayarı değildir. Uygulamadaki yavaş SQL'i veya yanlış transaction sınırını telafi etmez.
HikariCP için doğru üretim yaklaşımı birkaç sabit ayarı kopyalamak değildir. Havuz sayısı, bağlantı üst sınırı, Oracle oturum kapasitesi, transaction süresi, ağ zaman aşımı ve üst katman eş zamanlılığı birlikte modellenmelidir. "maximumPoolSize=10", "minimumIdle=2", "connectionTimeout=2000", "validationTimeout=1000" ve "autoCommit=false" bazı sistemlerde iyi bir başlangıç noktası olabilir. Bu değerler ancak yük testi, soak testi ve üretim metrikleriyle doğrulandığında gerçek bir yapılandırmaya dönüşür.
Havuz, aşırı yükü gizlememelidir. Beklemeyi sınırlamalı, arızayı erken görünür kılmalı ve veritabanını istemci tarafındaki sınırsız eş zamanlılıktan korumalıdır. HikariCP'nin mühendislik değeri en çok burada ortaya çıkar.