Kavram ve yöntem · Blog
Yük Testi - Hedeflenen yük gerçekten üretildi mi?
Panoda 1.200 sanal kullanıcı görmek, saniyede yüksek istek üretildiğini göstermemektedir; think time, tur süresi ve düşen turlar sonucu doğrudan değiştirmektedir. Bu yazı, k6 ile HTTP API yük testinin nasıl boyutlandırıldığını, çalıştırıldığını ve sonucun hangi sırayla yorumlandığını anlatmaktadır: önce yük üreticisi kanıtlanmakta, sonra sistem yorumlanmaktadır.
Yük testinin en yaygın hatası, panoda bir sayı görmekle o yükün gerçekten üretildiğini kanıtlamayı karıştırmaktır. “1.200 VU çalıştı” cümlesi tek başına hiçbir şey söylemez: bu VU’lar think time yüzünden saniyede yalnızca 30 istek üretiyor olabilir; dropped_iterations hedef geliş hızının hiç tutturulamadığını gösteriyor olabilir; ya da darboğaz test edilen sistemde değil, yük üreticinin CPU’sunda olabilir. Her üç durumda da rapordaki “sistem 1.200 kullanıcıyı kaldırdı” cümlesi yanlıştır.
Bu playbook’un tek bir yönlendirici ilkesi var ve sonuç yorumlamanın her adımını bu ilke belirler: Önce yük üreticisini kanıtla, sonra sistemi yorumla. Bir metriğin var olması, arkasındaki yükün gerçek olduğunu göstermez; tıpkı bir kontrolün tanımlı olmasının onu uyguladığını göstermemesi gibi. Aşağıda HTTP API yük testlerinin k6 ile nasıl kapsamlandırılacağını, boyutlandırılacağını, çalıştırılacağını ve en önemlisi sonuçlarının nasıl geçerli biçimde yorumlanacağını bulacaksınız. Ayrıca farklı source IP ihtiyacı için Red in Pulse iki yöntem ele alınır: küçük/orta testlerde sunucuya atanmış IPv6 adreslerini k6 VU’larına bağlamak, daha geniş IP ve coğrafi yol çeşitliliğinde API Gateway endpoint havuzu kullanmak.
Tüm örnekler yalnızca yazılı olarak izin verilmiş test ortamları içindir; gerçek hedef, IP, alan adı veya credential içermez.
Not: Yük testinin ortak dili İngilizcedir; VU, RPS, latency, throughput gibi terimleri sektör standardı olduğu için olduğu gibi kullanıyoruz, ama her birini aşağıda Türkçe ve örnekle açıklıyoruz. Bu bölümü okumadan formüllere geçmeyin sonraki her hesap bu kavramların üzerine kuruludur.
Başlamadan: temel kavramlar
Oturum (session). Bir kullanıcının sisteme girip işini yapıp ayrılana kadar geçen tek bir ziyaretidir. Aynı kişi gün içinde üç kez girip çıkarsa bu üç oturumdur. Örnek: bir kullanıcı uygulamayı açtı, üç ekran gezdi, bir form gönderdi ve kapattı bu 4 dakikalık tek bir oturumdur.
Tepe saat (peak hour). Trafiğin en yüksek olduğu 60 dakikalık dilim. Kapasiteyi günlük ortalama değil, bu zirve belirler; ortalamaya göre boyutlandırırsanız zirvede sistem çöker. “Tepe saat oturum sayısı 18.000” demek: en yoğun saatte sisteme 18.000 ziyaret düştü demektir.
VU Virtual User (sanal kullanıcı). k6’nın gerçek kullanıcıyı taklit etmek için çalıştırdığı eşzamanlı "sanal kullanıcı"dır. 500 VU demek, aynı anda 500 sanal kullanıcının senaryoyu tekrar tekrar koşturması demektir. Örnek: tepe saatte sistemde aynı anda ortalama 1.200 aktif oturum varsa, hedefimiz bunu taklit eden 1.200 VU’dur.
Iteration (tur). Bir VU’nun senaryonun tamamını bir kez baştan sona koşmasıdır. Senaryonuz “listeye bak → detay aç → form gönder” ise, bu üç adımın bir turu bir iteration’dır. Bir iteration genelde birden fazla HTTP isteği üretir bu ayrım kritik (aşağıda RPS’te göreceksiniz).
Think time (düşünme süresi). Gerçek kullanıcı butonlara aralıksız basmaz; okur, yazar, duraklar. Senaryoya koyduğumuz sleep() bu insani duraklamayı taklit eder. Örnek: kullanıcı bir sayfayı 3 saniye inceleyip sonra tıklıyorsa, senaryoya ~3 saniyelik think time konur. Think time’ı unutmak, gerçekte olmayan bir yoğunluk yaratır ve sonucu şişirir.
RPS Requests Per Second (saniyedeki istek) / Throughput (iş hacmi). Sistemin birim zamanda kaç isteği işlediğidir. Yük testinin “gerçek yoğunluk” ölçüsü budur. En sık yapılan hata VU ile RPS’i karıştırmaktır bunlar aynı şey değildir:
Örnek: 1.200 VU, her turda 6 istek gönderiyor, bir tur 240 saniye sürüyor.
Beklenen RPS = 1.200 × 6 / 240 = 30 RPS
Yani 1.200 VU görmek, saniyede yüksek istek üretildiğini kanıtlamaz; think time ve tur süresi sonucu doğrudan değiştirir. “Kaç kullanıcı” ile “saniyede kaç istek” ayrı ayrı hedeflenir ve ayrı ayrı doğrulanır.
Latency (gecikme). Bir isteğin gönderildiği andan yanıtın alındığı ana kadar geçen süredir; kullanıcının hissettiği “hız” budur. Tek bir sayı değil, bir dağılımdır bu yüzden ortalamaya değil yüzdeliğe (percentile) bakarız. p95 = 750 ms demek: isteklerin %95’i 750 ms’nin altında tamamlandı, %5’i daha yavaştı.
Tail latency (kuyruk gecikmesi). Dağılımın en yavaş ucundaki (p95, p99, p99.9) isteklerdir. Neden önemli olduğunu bir örnek anlatır:
1.000 isteğin 990'ı : ~100 ms
Kalan 10'u : ~5.000 ms
Ortalama ≈ 149 ms → "gayet hızlı" görünür
p99 ≈ 5.000 ms → gerçek: her 100 kullanıcıdan biri 5 saniye bekledi
Ortalama sistemin iyi günkü hâlini, tail ise kötü anındaki hâlini anlatır ve kullanıcı memnuniyeti kötü anlarda kaybedilir. Bu yüzden kararı avg ile değil, p95 ve p99’u birlikte okuyarak veririz.
Check ve threshold (kontrol ve eşik). İkisi karıştırılır ama işleri farklıdır. Check, bir yanıtın işlevsel olarak doğru olup olmadığına bakar (status 200 mü, gövdede id var mı) ama tek başına testi başarısız yapmaz. Threshold, testin geçti mi/kaldı mı kararını veren kuraldır (ör. “GET p95 < 1000 ms olmalı”). Örnek: checks: rate>0.99 ve http_req_duration p(95)<1000 biri doğruluğu, diğeri hız sınırını denetler. CI/CD’yi durduran ve FAIL üreten şey threshold’dur.
Kapalı ve açık model (closed / open). İki farklı yük üretme mantığı; hangisini seçtiğiniz testin sorusunu belirler.
- Kapalı model (
ramping-vus): VU sayısı sabittir. Sistem yavaşlarsa turlar uzar ve RPS düşer. Soru: “Şu kadar eşzamanlı kullanıcıyı kaldırıyor mu?” Benzetme: sabit sayıda müşteri, biri işini bitirmeden yenisi girmez. - Açık model (
constant-arrival-rate): geliş hızı sabittir. Sistem yavaşlasa da saniyede aynı sayıda istek gelmeye devam eder; bunu karşılamak için gereken VU artar. Soru: “Saniyede şu kadar isteği kaldırıyor mu?” Benzetme: kapıdan sabit hızda müşteri giriyor, içerisi dolsa da giriş yavaşlamıyor.
Gerçek kullanıcı sayısını doğrulamak için kapalı, sabit bir throughput/RPS hedefini zorlamak için açık model kullanılır.
Ramp-up / plateau / ramp-down (yükselme / plato / inme). Bir koşunun üç evresi: yükü kademeli artırdığınız ramp-up, hedef yükte sabit tuttuğunuz plateau (plato) ve yavaşça indirdiğiniz ramp-down. SLA kararı yalnızca plato aralığından verilir çünkü ramp sırasındaki cold start ve inme evresi ortalamayı yanıltır.
dropped_iterations (düşen turlar). Açık modelde, sistem (veya yük üretici) hedeflenen geliş hızını yetiştiremediği için başlatılamayan turlardır. Sıfırdan büyükse, hedef yük hiç üretilememiş demektir; bu durumda sonuç kapasite iddiası olarak kullanılamaz.
Bu on iki kavram, playbook’un geri kalanının sözlüğüdür. Bundan sonraki formüller ve tablolar, bu terimleri bildiğiniz varsayımıyla ilerler.
1. Playbook’un girdileri ve çıktıları
Bir koşu başlamadan önce beş girdi hazır olmalı: doldurulmuş kapsam formu, onaylanmış kullanıcı/RPS profili, endpoint ve GET/POST dağılım matrisi, threshold ve durdurma kriterleri, her koşu için ayrı run card.
Koşu bittiğinde dört çıktı üretilmeli: k6 agregasyon özeti, zaman damgalı ham metrik çıktısı, uygulama ve altyapı metrikleriyle korelasyon, ve PASS/FAIL/PARTIAL/BLOCKED kararı içeren karşılaştırma raporu.
Bu ikisi arasındaki her şey boyutlandırma, çalıştırma, yorumlama aynı disipline hizmet eder: sonuç, ancak yükün gerçekten üretildiği kanıtlandıktan sonra bir kapasite ifadesine dönüşür.
2. Boyutlandırma: sayıları nereden alırsınız?
100, 1.000 ve 5.000 birer test basamağıdır sistemin gerçek nominal yükü oldukları varsayılmaz. Gerçek hedef, mümkünse üretim telemetrisindeki veya RUM verisindeki en yoğun saat üzerinden hesaplanır. Günlük ortalama, kısa süreli zirveleri gizlediği için kapasite hedefi olarak kullanılmaz.
2.1 Nominal VU: Little’s Law’un yük testi karşılığı
Eş zamanlı kullanıcı = geliş hızı × sistemde kalma süresi
Nominal VU = tepe saatteki oturum sayısı × ortalama oturum süresi (sn) / 3.600
Örnek: 18.000 oturum/saat × 240 sn / 3.600 = 1.200 VU. Bu değer yalnızca mevcut tepe yükü temsil eder. Büyüme ve emniyet payı ayrıca ve görünür biçimde uygulanır:
Planlanan VU = nominal VU × büyüme katsayısı × emniyet katsayısı
Örnek: 1.200 × 1,25 × 1,20 = 1.800 VU
Katsayılar sessizce sonuca gömülmez. Rapor 1.200 mevcut nominal, 1.500 büyüme hedefi, 1.800 emniyetli doğrulama ayrımını korur. Veri yoksa 100/1.000/5.000 seviyeleri keşif basamağı olarak çalıştırılır ve üretim kapasitesi oldukları iddia edilmez.
2.2 Hacimden VU hesabı
Oturum sayısı yerine tepe saat sayfa/işlem hacmi biliniyorsa:
Saatlik iteration = tepe saat işlem sayısı / iteration başına işlem sayısı
VU = saatlik iteration × ortalama iteration süresi (sn) / 3.600
Örnek: 100.000 sayfa/saat, iteration başına 5 sayfa, ortalama 135 sn → 20.000 iteration/saat → 750 VU. Kritik uyarı: “sayfa” API isteğiyle eş anlamlı değildir. Bir ekran açılışı birden çok API çağrısı üretebilir; iteration başına gerçek HTTP istek sayısı k6 senaryosunda ayrıca ölçülür.
2.3 VU, iteration rate ve RPS aynı şey değildir
Bu, playbook’un en sık atlanan noktasıdır:
Beklenen RPS = VU × iteration başına istek / ortalama iteration süresi (sn)
Gerekli VU = hedef RPS × ortalama iteration süresi / iteration başına istek
Iteration rate = hedef RPS / iteration başına istek
1.200 VU’nun 240 saniyelik bir iteration içinde 6 istek göndermesi ortalama 30 RPS üretir. Yani “1.200 VU gördük” cümlesi, throughput hakkında hiçbir şey kanıtlamaz; think time ve pacing sonucu doğrudan değiştirir.
Kapalı model (ramping-vus) ile açık modelin (constant-arrival-rate) farkı da buradan doğar: kapalıda VU sabittir, sistem yavaşlarsa RPS düşer; açıkta geliş hızı sabittir, sistem yavaşladıkça gerekli VU artar. Arrival-rate testi için başlangıç tahmini:
Gerekli aktif VU ≈ iteration rate × p95 iteration süresi (sn)
preAllocatedVUs bu tahminin ölçülmüş bir güvenlik payıyla üzerinde seçilir; maxVUs kontrolsüz kaynak tüketimini engelleyen üst sınırdır. Koşuda dropped_iterations oluşuyorsa, hedef geliş hızının üretilemediği açıkça belirtilmeden sonuç kapasite kabul edilmez.
2.4 Çok iş akışlı boyutlandırma
Toplam VU doğrudan endpoint yüzdelerine dağıtılmaz. Önce gerçek iş akışları tanımlanır; her birinin kendi süresi, think time’ı, istek sayısı ve trafik payı kaydedilir.
| İş akışı | Trafik payı | Ort. süre | İstek/iter | VU/rate |
|---|---|---|---|---|
| Salt okunur gezinme | ||||
| Arama/listeleme | ||||
| Oturum açma | ||||
| Veri oluşturma/güncelleme | ||||
| Uzun işlem/polling |
Kapalı modelde ilk dağılım iş akışı VU = toplam VU × trafik payı ile yapılır; süreleri farklı akışlarda istek dağılımı bozulabileceği için oluşan request-tag oranları ölçülüp scenario VU’ları yeniden kalibre edilir. Açık modelde her iş akışına ayrı arrival-rate scenario vermek çoğu zaman daha kontrollüdür.
2.5 Tek kullanımlık veri ve hesap havuzu
Her VU benzersiz hesap kullanıyorsa yalnızca peak VU kadar veri hazırlamak yetmez ramp, plato ve iteration tekrarları hesaba katılır:
Iteration sayısı ≈ VU × dilim süresi / ortalama iteration süresi
Veri satırı = iteration sayısı × iteration başına tüketilen satır
Doğrusal 0 → tepe VU rampında ortalama yük ≈ tepe VU / 2; her ramp basamağının VU × süre alanı ayrı toplanır. Havuza ek paylar eklenir: load generator’lar arası dengesiz dağılım için %20–25 rezerv, retry/erken sonlanma için hata rezervi, setup/teardown/smoke için ayrı veri, tekrar çalıştırma için temiz ayrı set. Benzersizlik gerektiren veri generator’lar arasında önceden bölüştürülür, çalışma anında ortak dosyadan yarışarak tüketilmez.
2.6 Bant genişliği: yük üretici darboğaz olmamalı
Ortalama Mbps = RPS × (ort. request byte + ort. response byte) × 8 / 1.000.000
Toplam GB = toplam iteration × iteration başına transfer byte / 1.000.000.000
Toplam VU-dakika = (tepe VU × ramp dk / 2) + (tepe VU × plato dk)
Hesap upload/download’ı ayrı göstermeli, protokol/header/TLS ek yükünü ölçümle doğrulamalı, varsa edge-origin ve bölgeler arası transferi ayrıca ele almalıdır. Sıkıştırma, cache hit oranı ve hata yanıtı boyutu toplam maliyeti değiştirir.
2.7 Hesaplama çalışma kartı
Her ana koşudan önce doldurulur:
Veri penceresi ve timezone :
Tepe saat oturum sayısı :
Ortalama / p95 oturum süresi :
İş akışları ve trafik payları :
Iteration başına istek :
Ortalama / p95 iteration süresi :
Hesaplanan nominal VU :
Büyüme / emniyet katsayısı :
Planlanan VU :
Hedef ortalama / peak RPS :
Gerekli test hesabı / veri :
Tahmini upload / download :
Seçilen executor :
Varsayımlar ve veri boşlukları :
Hesaplayıcı çıktısı üretim telemetrisinin yerine geçmez; yalnızca açık varsayımlarla test planına girdi sağlar. (Referans metodoloji: Web Performance yük testi hesaplayıcısındaki tepe saat/oturum süresi, test case büyüklüğü ve bant genişliği modelleri; SmartBear’ın erişilebilen Load Testing 101 yayınıyla çapraz doğrulanmıştır.)
3. Yük seviyeleri, süreler ve geçiş kuralı
Seviyeler tek koşuda arka arkaya uygulanmaz; her seviye ayrı koşulur ve kendi içinde kademeli artırılır. Bunlar doğrulama basamağıdır, Bölüm 2’deki hesaplanmış kapasitenin yerine geçmez. Hesaplanan hedef iki basamak arasındaysa o değere özel profil oluşturulur.
| Seviye | Ramp-up | Sabit yük | Ramp-down | Toplam |
|---|---|---|---|---|
| 100 | 2 dk | 10 dk | 3 dk | 15 dk |
| 1.000 | 5 dk | 15 dk | 5 dk | 25 dk |
| 5.000 | 10 dk | 20 dk | 5 dk | 35 dk |
Geçiş kuralı: Smoke PASS → 100 PASS → 1.000 PASS → 5.000. Bir seviye başarısızsa bir üstüne geçilmez. Koşular arası bekleme: 100 sonrası ≥5 dk, 1.000 sonrası ≥10 dk, 5.000 sonrası ≥15 dk ve sistem baz çizgisine dönmediyse süre dolsa bile yeni koşu başlamaz.
k6 stage tanımı seviyeleri koda gömer, böylece --vus/--duration ile kazara override edilmez:
const loadProfiles = {
'100': [
{ duration: '30s', target: 25 }, { duration: '30s', target: 50 },
{ duration: '30s', target: 75 }, { duration: '30s', target: 100 },
{ duration: '10m', target: 100 }, { duration: '3m', target: 0 },
],
'1000': [
{ duration: '1m', target: 100 }, { duration: '1m', target: 250 },
{ duration: '1m', target: 500 }, { duration: '1m', target: 750 },
{ duration: '1m', target: 1000 }, { duration: '15m', target: 1000 },
{ duration: '5m', target: 0 },
],
'5000': [
{ duration: '2m', target: 500 }, { duration: '2m', target: 1000 },
{ duration: '2m', target: 2000 }, { duration: '2m', target: 3500 },
{ duration: '2m', target: 5000 }, { duration: '20m', target: 5000 },
{ duration: '5m', target: 0 },
],
};
4. Executor seçimi: neyi kanıtlamak istiyorsunuz?
Executor seçimi bir stil tercihi değil, testin sorusunu belirler.
ramping-vus eş zamanlı kullanıcı sayısını doğrular. Response süresi arttıkça iteration uzar ve RPS düşebilir; sonuç yalnızca VU üzerinden yorumlanamaz.
api_users: { executor: 'ramping-vus', stages: loadProfiles[__ENV.LOAD_LEVEL || '100'], gracefulRampDown: '30s' }
constant-arrival-rate belirli bir iteration/istek geliş hızını korur. Bir iteration birden fazla request üretiyorsa rate doğrudan RPS değildir; iteration rate = hedef RPS / iteration başına request hesaplanır. dropped_iterations > 0 ise hedef geliş hızı korunamamıştır; yetersiz preAllocatedVUs sonucu yük üretici kaynaklı bozar. Arrival-rate kendi pacing’ini yaptığı için iteration sonuna yapay sleep() eklenmez.
throughput: { executor: 'constant-arrival-rate', rate: 500, timeUnit: '1s',
duration: '15m', preAllocatedVUs: 800, maxVUs: 1200 }
| Amaç | Executor |
|---|---|
| 100/1.000/5.000 eş zamanlı kullanıcı | ramping-vus |
| Sabit kullanıcı | constant-vus |
| Sabit iteration/RPS hedefi | constant-arrival-rate |
| Artan throughput/stress | ramping-arrival-rate |
| Kullanıcı başına belirli iteration | per-vu-iterations |
5. GET/POST dağılımı ve endpoint matrisi
Trafik profili gerçek kullanıcı davranışını temsil etmeli. Ana profil (%75 GET / %25 POST) ve yazma ağırlıklı profil (%40 GET / %60 POST) ayrı tanımlanır.
| ID | Method | İşlem grubu | Ağırlık |
|---|---|---|---|
| GET-01 | GET | Health/readiness | %5 |
| GET-02 | GET | Liste/arama | %25 |
| GET-03 | GET | Detay | %20 |
| GET-04 | GET | Kullanıcı/hesap durumu | %15 |
| GET-05 | GET | Geçmiş/son durum | %10 |
| POST-01 | POST | Oluşturma/gönderme | %15 |
| POST-02 | POST | Güncelleme/iptal | %7 |
| POST-03 | POST | Batch/toplu işlem | %3 |
Yazma ağırlıklı profil önce 100, sonra 1.000 kullanıcıyla çalışır; 5.000 yazma testi ancak veri temizliği, idempotency, queue ve database kapasitesi doğrulandıktan sonra yapılır. Her endpoint için path, auth, request/response boyutu, cache, idempotency ve cleanup bir matrise yazılır çünkü boyutlandırma ve maliyet hesapları bu değerlerden beslenir.
6. k6 senaryo şablonu
Aşağıdaki şablon, request-type bazlı threshold, düşük-cardinality tag ve custom metric disiplinini bir arada gösterir. Endpoint ve payload’lar örnektir; güncel API sözleşmesiyle değiştirilmeden test çalıştırılmaz.
import http from 'k6/http';
import exec from 'k6/execution';
import { check, sleep } from 'k6';
import { Counter, Rate, Trend } from 'k6/metrics';
const BASE_URL = __ENV.BASE_URL;
const RUN_ID = __ENV.RUN_ID || 'local-run';
const LOAD_LEVEL = __ENV.LOAD_LEVEL || '100';
const TRAFFIC_PROFILE = __ENV.TRAFFIC_PROFILE || 'read-heavy';
const PATH_MODE = __ENV.PATH_MODE || 'direct-ipv4';
if (!BASE_URL) throw new Error('BASE_URL zorunludur');
const status429 = new Counter('status_429');
const businessErrors = new Rate('business_error_rate');
const getDuration = new Trend('business_get_duration', true);
const postDuration = new Trend('business_post_duration', true);
export const options = {
scenarios: {
api_users: {
executor: 'ramping-vus',
stages: loadProfiles[LOAD_LEVEL],
gracefulRampDown: '30s',
tags: { run_id: RUN_ID, path_mode: PATH_MODE },
},
},
thresholds: {
checks: ['rate>0.99'],
http_req_failed: [{ threshold: 'rate<0.01', abortOnFail: true, delayAbortEval: '2m' }],
'http_req_duration{request_type:GET}': ['p(95)<1000', 'p(99)<2000'],
'http_req_duration{request_type:POST}': ['p(95)<2000', 'p(99)<3000'],
business_error_rate: ['rate<0.01'],
},
summaryTrendStats: ['avg', 'med', 'p(90)', 'p(95)', 'p(99)', 'max'],
};
function requestParams(name, requestType) {
return {
headers: { 'Content-Type': 'application/json', 'X-Test-Run-Id': RUN_ID },
tags: { name, request_type: requestType, path_mode: PATH_MODE },
timeout: '10s',
};
}
function recordResult(res, requestType) {
const ok = check(res, {
[`${requestType} status başarılı`]: (r) => r.status >= 200 && r.status < 300,
}, { request_type: requestType });
if (res.status === 429) status429.add(1);
businessErrors.add(!ok);
(requestType === 'GET' ? getDuration : postDuration).add(res.timings.duration);
}
function runPost() {
const iteration = exec.scenario.iterationInTest;
const idempotencyKey = `${RUN_ID}-${exec.vu.idInTest}-${iteration}`;
const payload = JSON.stringify({ testRunId: RUN_ID, idempotencyKey, value: 'synthetic' });
const params = requestParams('POST_create', 'POST');
params.headers['Idempotency-Key'] = idempotencyKey;
const res = http.post(`${BASE_URL}/v1/resources`, payload, params);
recordResult(res, 'POST');
// Gerçek senaryoda response schema ve iş sonucu ayrıca doğrulanmalıdır.
}
function runGet() {
const res = http.get(`${BASE_URL}/v1/resources`, requestParams('GET_list', 'GET'));
recordResult(res, 'GET');
}
export default function () {
const getRatio = TRAFFIC_PROFILE === 'write-heavy' ? 0.40 : 0.75;
Math.random() < getRatio ? runGet() : runPost();
sleep(Math.random() * 2 + 1);
}
Check ile threshold farkı kritiktir: check işlevsel doğruluğu ölçer ama tek başına testin exit code’unu başarısız yapmaz; threshold başarı/başarısızlık kriteridir ve CI/CD kararı için gereklidir. Custom metrikler Counter (GET/POST/429/retry toplamı), Rate (business error/doğruluk), Trend (özel latency), Gauge (queue depth) olarak ayrılır. Her request en az name, request_type, operation, path_mode, run_id tag’ini taşımalı; dinamik ID veya tam URL asla name olarak kullanılmaz yüksek cardinality endpoint karşılaştırmasını bozar.
7. Çalıştırma
Yük profili kod içinde olduğundan koşular arasında yalnızca LOAD_LEVEL ve RUN_ID değişir:
# Smoke yük üreticiyi ve senaryoyu doğrula
k6 run --vus 5 --duration 1m -e BASE_URL="https://api.example.invalid" \
-e RUN_ID="smoke-001" -e PATH_MODE="direct-ipv4" scenario.js
# 100 kullanıcı
k6 run --summary-mode=full \
--summary-trend-stats="avg,med,p(90),p(95),p(99),max" \
--out json=results/lt-100-timeseries.json \
-e BASE_URL="https://api.example.invalid" -e RUN_ID="lt-100-direct-ipv4" \
-e LOAD_LEVEL="100" -e TRAFFIC_PROFILE="read-heavy" -e PATH_MODE="direct-ipv4" scenario.js
Terminal özeti yalnızca genel görünüm içindir; ramp, plateau ve recovery analizi zaman serisi çıktısından yapılır. Agregasyon özetini ayrıca dosyaya almak için handleSummary kullanılır:
import { textSummary } from 'https://jslib.k6.io/k6-summary/0.1.0/index.js';
export function handleSummary(data) {
const runId = __ENV.RUN_ID || 'local-run';
return {
stdout: textSummary(data, { indent: ' ', enableColors: true }),
[`results/${runId}-summary.json`]: JSON.stringify(data, null, 2),
};
}
8. Source IP: iki yöntem, tek disiplin
Önce ayrı, sonra eş zamanlı. Yollar sırayla ölçülür (Direct IPv4 → Direct IPv6 → API Gateway IPv4 → API Gateway IPv6 → dual-stack), her yol önce 100 kullanıcıyla; başarılı yollar 1.000’e taşınır. 1.000 seviyesi geçtikten sonra production-benzeri karma trafik çalıştırılır (telemetri varsa oranlar onunla değiştirilir).
8.1 Küçük testlerde doğrudan IPv6 (--local-ips)
k6 VU’ları yerel source IP’lere dağıtabilir; ancak bu seçenek işletim sistemine IP eklemez adreslerin önceden interface’e atanmış ve route edilmiş olması gerekir. Uygun kullanım: 100 kullanıcı baseline, sınırlı 1.000 testi, IPv4/IPv6 karşılaştırması, kaynak-IP bazlı rate-limit davranışının yetkili doğrulaması, tek generator’da source port kapasitesini genişletme.
sudo ip -6 addr add <ipv6-address-1>/<prefix-length> dev <interface>
ip -6 route get <target-ipv6> from <ipv6-address-1>
k6 run --local-ips="<ipv6-1>,<ipv6-2>,<ipv6-3>" \
-e BASE_URL="https://ipv6-api.example.invalid" -e RUN_ID="lt-100-direct-ipv6" \
-e LOAD_LEVEL="100" -e PATH_MODE="direct-ipv6" scenario.js
Dağıtım davranışı: IP’ler VU’lara sıralı atanır; listede tekrarlanan IP daha çok VU alır; her request için rastgele IP seçildiği varsayılmaz; VU sayısından az IP kullanılabilir. Dikkat: bir /64 bloğu bütünüyle vermek yerine yalnızca atanmış alt kümeyi kullanın; keep-alive’i koruyun (kapatmak source port baskısını yapay büyütür); worker CPU/FD/socket kapasitesini izleyin. Ve açıkça: source-IP çeşitliliği rate-limit veya WAF atlatmak için kullanılmaz.
8.2 Geniş IP ihtiyacında API Gateway havuzu
IPv6 bloğu yönlendirilemiyorsa, birden çok onaylı bölgeden yol ölçülecekse veya tek generator’ın ağ yoluna bağımlılık azaltılacaksa API Gateway endpoint havuzu tercih edilebilir. Stage sayısı ile benzersiz source IP arasında bire bir ilişki varsayılmaz gerçek dağılım hedef loglarından ölçülür.
import exec from 'k6/execution';
import { SharedArray } from 'k6/data';
const gateways = new SharedArray('api-gateways', () =>
open('./config/api-gateways.txt').split('\n')
.map((l) => l.trim()).filter((l) => l && !l.startsWith('#')));
if (gateways.length === 0) throw new Error('API Gateway listesi boş');
export function gatewayBaseUrl() {
const strategy = __ENV.GATEWAY_STRATEGY || 'sticky';
if (strategy === 'round-robin')
return gateways[exec.scenario.iterationInTest % gateways.length];
return gateways[(exec.vu.idInTest - 1) % gateways.length]; // sticky per VU
}
Ana yük testinde sticky per VU önerilir (connection/TLS reuse korunur); her request’te endpoint değiştirmek bağlantı kurulum maliyetini artırır ve gerçek kullanıcı davranışını bozar. Havuz kullanılırken doğrulanması gerekenler: endpoint’ler yalnızca onaylı hedefe proxy ediyor ve açık/genel proxy oluşmuyor; path/query/body/header korunuyor; auth header’ları loglarda maskeleniyor; run ID gateway ve hedef loglarında görünüyor; quota/throttle doğrulandı; 429 kaynağı gateway/origin olarak ayrılabiliyor; test sonrası geçici API/stage/deployment temizleniyor. Amaç güvenlik kontrolünü atlatmak değil, source IP çeşitliliğinin kapasite testine etkisini kontrollü ölçmektir.
9. Sonuçları yorumlama: önce üreticiyi kanıtla
Bu bölüm playbook’un kalbidir. Sistem sonucunu yorumlamadan önce k6’nın istenen yükü ürettiğini kanıtlayın.
9.1 Yük üreticisi geçerlilik kontrolü
| Metrik | Yorum |
|---|---|
vus / vus_max |
Aktif ve ayrılan VU |
iterations / iteration_duration |
Tamamlanan iş akışı ve think-time dahil süre |
http_reqs |
Üretilen HTTP request sayısı |
dropped_iterations |
Arrival-rate hedefinde başlatılamayan iteration |
data_sent/received |
Worker ağ hacmi |
Sonucu kabul etmeden önce şunları doğrulayın: hedef concurrency gerçekten oluştu mu; hedef RPS üretildi mi; gerçekleşen iteration süresi boyutlandırmadaki değere yakın mı; iş akışı/request-tag dağılımı planlanan yüzdeyi korudu mu; hesap/veri tüketimi tahminle uyumlu mu; data_sent/received neden sapıyor; worker CPU/network doygun mu; dropped_iterations var mı; vus_max sınırına dayanıldı mı. Yük üretici kapasitesi yetersizse sonuç, hedef sistem kapasitesi olarak raporlanmaz.
Gerçekleşen değerler plateau aralığı için yeniden hesaplanır:
Gerçek ortalama RPS = http_reqs / ölçüm süresi (sn)
Gerçek iteration rate = iterations / ölçüm süresi (sn)
Gerçek istek/iteration = http_reqs / iterations
Plan ile gerçekleşen arasındaki fark redirect, retry, başarısız check, erken sonlanan iteration, response süresi artışı, think time veya scenario dağılımıyla açıklanır. Açıklanamayan fark varsa koşu karşılaştırılabilir kabul edilmez.
9.2 Threshold, check ve latency ayrıştırması
Terminal özetindeki THRESHOLDS ilk karar noktasıdır: yeşil kriter karşılandı, kırmızı test başarısız, threshold yoksa metriğe otomatik iyi/kötü denemez. checks yalnızca HTTP başarısını değil işlevsel doğruluğu ölçmeli status, schema, gerekli alanlar, POST’un backend’de gerçekten oluşması, idempotent tekrarın duplicate üretmemesi. HTTP 200 alıp yanlış iş sonucu üretmek performans başarısı değildir.
Latency tek sayı değildir; nerede kaybedildiğini alt metrikler söyler: http_req_blocked (socket/pool), connecting (TCP), tls_handshaking (TLS), waiting (TTFB gateway/origin işlem süresinin ana göstergesi), receiving (body indirme). blocked yüksekse generator pool sorunu; connecting/tls yüksekse ağ/DNS/IPv6 fallback/reuse; waiting yüksekse integration ve origin bağımlılıkları; receiving yüksekse response boyutu ve bandwidth incelenir.
9.3 Percentile, ramp/plateau ve korelasyon
Karar avg’ye göre verilmez ortalama tail latency’yi gizler. p(95), p(99) ve max birlikte okunur; max tekil outlier’dır, tek başına kapasite kararı vermez. k6 terminal özeti tüm koşuyu tek agregasyonda birleştirdiği için SLA değerlendirmesi mutlaka plateau zaman aralığı üzerinden yapılır; ramp’taki cold start ayrı raporlanır, recovery ramp-down sonrasında ölçülür.
Her latency/hata artışı, altyapı metrikleriyle aynı zaman ekseninde karşılaştırılır: CPU/memory, network throughput/connection, thread/event-loop saturation, DB connection/query latency, cache hit/miss, queue depth/consumer lag, autoscaling olayı, gateway/integration latency, WAF/rate-limit olayları. Korelasyon olmadan “darboğaz database” veya “gateway yavaş” yazılmaz.
Hatalar da sahibine göre sınıflandırılır: istemci (timeout/DNS/socket/TLS → yük/ağ ekibi), gateway (429/5xx/integration timeout → platform), güvenlik (WAF 403/rate block → güvenlik), uygulama (origin 4xx/5xx), veri (duplicate/nonce/validation), bağımlılık (DB/cache/queue). 429 doğrudan hata veya başarı sayılmaz beklenen throttling davranışına göre ayrı değerlendirilir.
10. Geçici eşikler ve durdurma kriterleri
Resmî SLO/SLA gelene kadar başlangıç eşikleri:
| Yük | GET p95 | POST p95 | Hata | 5xx | Beklenmeyen 429 |
|---|---|---|---|---|---|
| 100 | ≤500 ms | ≤1.000 ms | <%0,1 | <%0,05 | 0 |
| 1.000 | ≤750 ms | ≤1.500 ms | <%0,5 | <%0,1 | <%0,1 |
| 5.000 | ≤1.000 ms | ≤2.000 ms | <%1 | <%0,5 | <%0,5 |
Yol karşılaştırması için: IPv6 p95, IPv4’ten %10’dan fazla kötü olmamalı; başarı oranı farkı 0,2 puanı aşmamalı; gateway ek p95 maliyeti %20 veya 100 ms’den büyük olanı aşmamalı; endpoint dağılım farkı %15 altında olmalı; 429 kaynağı ayrıştırılabilmeli. Bu eşikler kurumun resmî değerleriyle değiştirilir.
Test şu durumlarda durdurulur: 5xx iki dakika %2’yi aşarsa; p95 iki dakika hedefin iki katını aşarsa; veri bütünlüğü/duplicate görülürse; queue kontrolsüz büyürse; DB/origin kritik kapasiteye ulaşırsa; dropped_iterations sürekli artıp worker sınırı doğrulanırsa; worker CPU/network/socket doygunsa; beklenmeyen geniş 403/429 oluşursa; IPv6 sürekli fallback/reset üretirse; maliyet/kota alarmı tetiklenirse; kapsam dışı adrese trafik gittiği görülürse.
11. Rapor ve karşılaştırma
Her koşu, hedef ile gerçekleşeni yan yana koyan bir kartla raporlanır çünkü playbook’un tüm iddiası bu iki sütunun tutarlılığında yaşar:
Run ID / Test amacı / Adres yolu / Source IP yöntemi / Executor:
Nominal VU hesabı / Büyüme-emniyet katsayısı:
Concurrent user hedef/gerçekleşen · RPS hedef/gerçekleşen:
İstek-iteration hedef/gerçekleşen · Iteration süresi hedef/gerçekleşen:
Ramp / plateau / ramp-down · GET-POST hedef/gerçekleşen:
Hesap-veri planlanan/tüketilen · Upload-download tahmini/gerçekleşen:
GET p50/p95/p99 · POST p50/p95/p99 · HTTP/business error · 429/403/5xx · dropped:
Gateway/integration latency · Origin saturation · DB-cache-queue · autoscaling · recovery:
Source IP sayısı · IPv4/IPv6 oranı · IP başına dağılım:
Threshold kararı · İş doğruluğu · Genel: PASS/FAIL/PARTIAL/BLOCKED · Darboğaz · Aksiyon · Yeniden test:
| Run | VU | RPS | Yol | GET p95 | POST p95 | Hata | 429 | Dropped | Recovery | Karar |
|---|---|---|---|---|---|---|---|---|---|---|
| Direct-v4-100 | 100 | IPv4 | ||||||||
| Gateway-v4-100 | 100 | Gateway | ||||||||
| Mixed-1000 | 1.000 | Karma | ||||||||
| Mixed-5000 | 5.000 | Karma |
Son söz
İyi bir yük testinin değeri, panoda kaç VU göründüğünde değil; o yükün gerçekten üretildiğini, iş akışı dağılımının korunduğunu ve sonucun altyapı metrikleriyle açıklanabildiğini ne kadar sağlam kanıtladığında ortaya çıkar. En yararlı rapor uzun bir grafik listesi değil; hedeflenen yükün doğrulandığı, sistemin hangi noktada ve neden doyduğu, darboğazın hangi katmana ait olduğu ve düzeltmenin nasıl yeniden ölçüleceği açıkça yazılmış olan rapordur. Çünkü sonuçta değerlendirdiğimiz şey, gösterilen metrik değil; üretilen yüktür.
Bu playbook, Red in Pulse ekibinin performans ve kapasite değerlendirmelerinde izlediği yaklaşımın çerçevesidir: yükü telemetriden boyutlandıran, yük üreticisini sonuçtan önce doğrulayan, source IP ve edge davranışını yalnızca yetkili ve kontrollü biçimde ölçen, test ortamını ve maliyeti başladığı hâl kadar temiz bırakan bir metodoloji. API’lerinizin gerçek kapasitesini panoda görünen sayıyı değil, üretilen yükün kanıtını bu titizlikte ölçtürmek isterseniz, kapsam görüşmesi için bizimle iletişime geçebilirsiniz.
Yazıda Geçen Kavramlar ve Kısaltmalar
k6'nın gerçek kullanıcıyı taklit etmek için eşzamanlı çalıştırdığı sanal kullanıcıdır. 500 VU, senaryoyu aynı anda tekrar tekrar koşturan 500 sanal kullanıcı anlamına gelmektedir.
Sistemin birim zamanda işlediği istek sayısıdır ve yük testinin gerçek yoğunluk ölçüsüdür. VU sayısıyla aynı şey değildir: beklenen RPS, VU sayısının tur başına istekle çarpılıp ortalama tur süresine bölünmesiyle bulunmaktadır.
Gecikme dağılımının en yavaş ucundaki isteklerdir (p95, p99, p99.9). Ortalama sistemin iyi günkü hâlini, kuyruk ise kötü anındaki hâlini anlatmaktadır; kullanıcı memnuniyeti kötü anlarda kaybedilmektedir.
Kapalı modelde VU sayısı sabittir ve sistem yavaşlarsa RPS düşmektedir; açık modelde geliş hızı sabittir ve sistem yavaşladıkça gereken VU artmaktadır. Hangisinin seçildiği testin sorduğu soruyu belirlemektedir.
Açık modelde, hedeflenen geliş hızı yetiştirilemediği için hiç başlatılamayan turlardır. Sıfırdan büyükse hedef yük üretilememiş demektir ve sonuç kapasite iddiası olarak kullanılamamaktadır.