Veritabanı yük testi: PostgreSQL, MySQL, SQL Server ve Oracle'da sorgu performansı
Bir uygulamanın yük altında yavaşlamasının en sık nedeni veritabanıdır, ama uygulama üzerinden yapılan bir testte bunu ayırt etmek zordur: yavaşlık uygulamanın kodunda mı, connection pool'unda mı, yoksa sorgunun kendisinde mi? Veritabanını doğrudan yüklemek bu soruyu daraltır. Veritabanı yük testi uygulamanın en sık ve en ağır sorgularını gerçekçi parametrelerle, hedef eşzamanlılıkta çalıştırır ve sorar: sorgular bu yükte hangi gecikmeyle dönüyor, bağlantı sınırı nerede, ve kilitler ya da veri büyüklüğü işi ne zaman bozuyor.
Neyi ölçüyoruz?
- Sorgu gecikmesi: her sorgu türü için ayrı ayrı p95 ve p99. Tek bir anahtarla okuma ile bir rapor sorgusunun gecikmesini aynı ortalamada toplamak ikisini de gizler (p95 ve p99 rehberi).
- Throughput: saniyede tamamlanan sorgu ya da işlem sayısı ve bunun eşzamanlılıkla nasıl değiştiği. Belli bir noktadan sonra daha fazla eşzamanlı sorgu daha fazla throughput getirmez, yalnız gecikmeyi büyütür.
- Bağlantılar: veritabanının bağlantı sınırı (PostgreSQL'de
max_connectionsgibi) ve bağlantı başına bellek. Pool'dan bağlantı beklemek de gecikmeye eklenir. - Kilitler ve çekişme: aynı satırları güncelleyen eşzamanlı yazmalar birbirini bekler; deadlock'lar ve kilit zaman aşımları ancak gerçek eşzamanlılıkta görünür.
- Kaynaklar: CPU, disk I/O, önbellek (buffer pool) isabet oranı ve varsa replica gecikmesi. Bunları veritabanının kendi izleme araçlarından yük basamaklarıyla birlikte okuyun.
Yük modelini kurmak
Sorguları uygulamanın sorgu istatistiklerinden seçin (PostgreSQL'de pg_stat_statements, MySQL'de performance schema, SQL Server'da Query Store gibi): en sık çalışanlar ve toplamda en çok zaman harcayanlar. Eşzamanlılığı uygulamanın gerçek pool boyutlarından hesaplayın: 6 instance'ın her biri 20 bağlantılı bir pool kullanıyorsa veritabanı en fazla 120 eşzamanlı sorgu görür. Bir VU bir bağlantı gibi düşünülebilir; gecikmeyi büyüdükçe görmek için VU'ları basamaklarla artırın (kırılma noktası testi).
Sık yapılan hatalar
- Küçük test verisi. Bin satırlık bir tablo tamamen bellekte durur ve her sorgu hızlı görünür. Test veritabanının boyutu ve veri dağılımı canlıya yakın olmalı; aksi halde sorgu planları da farklı olur.
- Hep aynı parametre. Aynı kimliği tekrar tekrar sorgulamak önbelleği ölçer, veritabanını değil. Parametreleri rastgele ya da gerçek kimliklerden oluşan bir CSV dosyasından seçin.
- Isınmayı ölçmek. Soğuk bir veritabanının ilk dakikaları diskten okur. Ramp-up'ı ve ilk dakikaları sonuçtan ayrı değerlendirin ya da sabit bölümü yeterince uzun tutun.
- Yük üreticisinin bağlantıları. Yük birden fazla makineden üretiliyorsa her makinenin pool'u toplanır: 4 makine × 50 bağlantı = 200 bağlantı. Bu sayı veritabanının sınırını aşarsa ölçtüğünüz şey sorgular değil bağlantı reddi olur.
- Canlı veriye yazmak. Yazma testlerini canlı veritabanında değil, canlının bir kopyasında yapın; yazmaların temizliğini önceden planlayın.
Spitfire ile
Önce Bağlantılar'da bir veritabanı bağlantısı ekleyin: PostgreSQL, MySQL (MariaDB dahil), SQL Server ya da Oracle; sunucu, port, veritabanı (Oracle'da servis adı), kullanıcı adı, parola, sürücü parametreleri (TLS için PostgreSQL'de sslmode=require, SQL Server'da encrypt=true gibi) ve en fazla açık bağlantı (varsayılan 50). Parola şifreli saklanır, testte yalnız bağlantının adı geçer. Her runner'daki VU'lar bağlantı başına tek bir connection pool'u paylaşır; en fazla açık bağlantı sayısı runner başınadır.
SQL adımı tek bir sorgu çalıştırır. Sorgu sabit bir metindir; değerler veritabanının kendi yer tutucularıyla parametre olarak verilir: PostgreSQL'de $1, MySQL'de ?, SQL Server'da @p1, Oracle'da :1. Parametreler {{$randInt 1 100000}}, çıkarılmış değişkenler ya da CSV veri dosyasından değerler içerebilir; sorgu metninin içine şablon yazılamaz, böylece SQL enjeksiyonu olmaz. Bir okumanın süresi pool'dan bağlantı beklemeyi, sorguyu ve bütün satırların alınmasını kapsar. Satırlar JSON nesneleri dizisi olarak kontrollere ve değişken çıkarmaya gider ($[0].status gibi); ilk 100 satır tutulur (ayarlanabilir), satır sayısı ise hepsini kapsar, rowCount kontrolüyle ve rows metriğinin eşikleriyle sınanabilir. Hatalar türüne göre ayrılır: zaman aşımı, bağlantı reddi, yetki, bulunamayan tablo, kısıt ihlali, sözdizimi ve salt okunur ihlali.
Okumalar gerçekten salt okunur. Spitfire bir sorgunun okuma mı yazma mı olduğunu kendisi anlar ve okuma sayamadığı her şeyi yazma sayar: INSERT, UPDATE, DELETE, SELECT … FOR UPDATE, SELECT … INTO, prosedür çağrıları ve yazma içeren çok cümleli sorgular. PostgreSQL ve MySQL'de okumalar salt okunur bir transaction içinde çalışır ve geri alınır; böylece yan etkili bir fonksiyon gibi gizli yazmaları veritabanının kendisi reddeder. Oracle sorgu içinde veri değiştirmeyi zaten reddeder; SQL Server'da salt okunur bir veritabanı kullanıcısı kullanın. Yazmalar adımda açıkça onaylanmadan test kaydedilmez, onaylanmışsa test her başlatıldığında ayrıca onay ister ve onay denetim kaydına yazılır (güvenlik beyanı). Onaylı yazmalar transaction'sız, tek tek commit edilir.
30 eşzamanlı kullanıcının her iterasyonda bir siparişi kimliğiyle okuduğu ve son günün saatlik özetini aldığı bir PostgreSQL testi; tek sipariş okumasının p95'i 20 ms'nin, özet sorgusununki 300 ms'nin, hata oranı %0,1'in altında kalmalı. Tam hali örnekler sayfasında indirilebilir; test spitfire validate ile doğrulandı.
{
"name": "PostgreSQL okuma yükü",
"scenarios": [
{ "name": "okuma",
"executor": { "type": "ramping-vus", "startVUs": 0,
"stages": [ { "duration": "1m", "target": 30 }, { "duration": "5m", "target": 30 },
{ "duration": "30s", "target": 0 } ] },
"steps": [
{ "id": "order", "name": "Sipariş getir", "protocol": "sql", "connection": "orders-db",
"sql": { "query": "SELECT id, status, total FROM orders WHERE id = $1",
"params": ["{{$randInt 1 100000}}"] },
"checks": [ { "type": "rowCount", "op": "lte", "value": 1 } ] },
{ "id": "report", "name": "Günlük özet", "protocol": "sql", "connection": "orders-db",
"sql": { "query": "SELECT date_trunc('hour', created_at) AS h, count(*) FROM orders WHERE created_at > now() - interval '1 day' GROUP BY 1 ORDER BY 1" },
"thinkTime": { "min": "500ms", "max": "1s" } }
] }
],
"thresholds": [
{ "metric": "req_duration", "filter": { "step": "order" }, "expr": "p(95)<20" },
{ "metric": "req_duration", "filter": { "step": "report" }, "expr": "p(95)<300" },
{ "metric": "req_failed", "expr": "rate<0.001" }
]
}Koşu sırasında adım bazında sorgu hızı, p95, p99, dönen satır sayıları ve hata türleri canlı izlenir. Veritabanının kendi metriklerini (bağlantılar, kilit beklemeleri, önbellek isabeti, replica gecikmesi) bir Prometheus exporter'ıyla topluyorsanız Observability'te bir Prometheus bağlantısına ekleyin; koşu sayfasının Backend sekmesi onları yük basamaklarıyla hizalı gösterir. Aynı testi farklı ortamlarda koşturmak için ortam başına başka bir bağlantı seçebilirsiniz (örneğin orders-db yerine orders-db-staging). Veritabanının önünde bir önbellek varsa onu da yüklemek için Redis yük testi rehberine bakın.
Spitfire tek komutla Docker'a ya da Kubernetes'e kurulur; ücretsiz sürümde bütün test özellikleri ve protokoller açıktır.