wrk ile HTTP Benchmark Testi ve Sonuç Analizi

Yük testleri söz konusu olduğunda herkesin aklına hemen JMeter ya da k6 geliyor. Oysa basit ama son derece güçlü bir araç olan wrk, özellikle HTTP servislerini hızlıca stres altına almak için mükemmel bir tercih. Kurulumu dakikalar içinde tamamlanıyor, Lua ile özelleştirilebiliyor ve sonuçları yorumlamak için doktora derecesine gerek yok. Bu yazıda wrk ile nasıl gerçekçi benchmark testleri kurgulanır, çıktılar nasıl yorumlanır ve elde edilen verilerle ne yapılır, bunları konuşacağız.

wrk Nedir ve Neden Tercih Edilir?

wrk, C ile yazılmış, event-driven mimariye dayanan bir HTTP benchmarking aracıdır. Tek bir makineden binlerce eş zamanlı bağlantı açabilir, bu da onu kaynak kullanımı açısından son derece verimli kılar. JMeter gibi araçlar GUI’ye ve JVM’e bağımlıyken wrk terminal üzerinde çalışır ve çok daha az sistem kaynağı tüketir.

Öte yandan wrkin bazı kısıtlamaları da vardır. Yalnızca HTTP/1.1 destekler, HTTP/2 yoktur. Karmaşık kullanıcı senaryoları ya da adım adım test akışları için Locust veya k6 daha uygun olacaktır. Ama tek bir endpoint’in gerçek kapasitesini ölçmek istiyorsanız, wrk bu iş için biçilmiş kaftandır.

Kurulum

Ubuntu/Debian tabanlı sistemlerde:

sudo apt-get update
sudo apt-get install -y wrk

Eğer paket deposunda eski bir sürüm varsa ya da CentOS/RHEL kullanıyorsanız, kaynaktan derleme daha sağlıklı olur:

sudo yum groupinstall -y "Development Tools"
sudo yum install -y openssl-devel git

git clone https://github.com/wg/wrk.git
cd wrk
make
sudo cp wrk /usr/local/bin/

macOS kullanıcıları için:

brew install wrk

Kurulumu doğrulamak için:

wrk --version

Temel Kullanım ve Parametreler

wrkin sözdizimi oldukça sade:

wrk [options] <url>

Temel parametreler şunlardır:

  • -t veya –threads: Kullanılacak thread sayısı. Genellikle CPU çekirdek sayısı kadar ya da iki katı olarak ayarlanır.
  • -c veya –connections: Toplam eş zamanlı bağlantı sayısı. Her thread bu bağlantıları paylaşır.
  • -d veya –duration: Testin süresi. 30s, 2m, 1h gibi biçimler kabul edilir.
  • -H veya –header: HTTP başlığı eklemek için kullanılır.
  • -s veya –script: Lua script dosyası belirtmek için kullanılır.
  • –latency: Gecikme dağılımını detaylı gösterir (mutlaka kullanın).
  • –timeout: İstek zaman aşımı süresi.

Basit bir test çalıştıralım:

wrk -t4 -c100 -d30s --latency http://localhost:8080/api/health

Bu komut 4 thread, 100 eş zamanlı bağlantı ve 30 saniyelik süreyle bir test başlatır.

Çıktıyı Okumak ve Yorumlamak

wrk çıktısı ilk bakışta sade görünse de her satırın arkasında önemli bilgiler yatmaktadır. Tipik bir çıktı şöyle görünür:

Running 30s test @ http://localhost:8080/api/health
  4 threads and 100 connections

  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    23.45ms   12.31ms  245.67ms   89.23%
    Req/Sec     1.12k   134.56     1.45k    71.00%

  Latency Distribution
     50%   19.23ms
     75%   28.45ms
     90%   41.12ms
     99%   98.76ms

  134521 requests in 30.10s, 18.45MB read
  Socket errors: connect 0, read 2, write 0, timeout 5
  Requests/sec:   4468.47
  Transfer/sec:    628.23KB

Şimdi bu çıktıyı parçalayalım:

Latency (Gecikme): Ortalama değer her zaman yanıltıcı olabilir. Standart sapmanın (Stdev) yüksek olması, servisin tutarsız davrandığının işaretidir. Yukarıdaki örnekte ortalama 23ms iken max 245ms’e çıkmış, bu ciddi bir spike demek.

Latency Distribution: Bu kısım altın değerindedir. P50, P75, P90, P99 değerlerine bakın. P99’un P50’nin 5 katından fazla olması genellikle sorunlu bir duruma işaret eder.

Req/Sec: Thread başına saniyedeki istek sayısı. +/- Stdev değeri burada da tutarlılığı gösterir.

Socket errors: Sıfır olması idealdir. timeout değeri özellikle kritik, sunucunun istekleri karşılayamadığının göstergesidir.

Requests/sec: Genel throughput. Bu değeri farklı konfigürasyonlar ve sunucu ayarları arasında karşılaştırmak için kullanın.

Gerçek Dünya Senaryosu: Nginx Önünde Node.js Servisi

Diyelim ki bir e-ticaret platformunun ürün listeleme API’sini ölçmek istiyorsunuz. Sunucunuz Nginx reverse proxy arkasında Node.js çalıştırıyor ve veritabanı olarak PostgreSQL var. Bu senaryoda authentication header’ı gerekiyor.

wrk -t8 -c200 -d60s --latency 
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." 
  -H "Accept: application/json" 
  -H "Content-Type: application/json" 
  http://api.example.com/v1/products?page=1&limit=20

Bu testi 3-4 kez çalıştırın ve sonuçların tutarlı olduğunu doğrulayın. İlk çalıştırma sonuçları genellikle “warm-up” etkisiyle biraz farklı olabilir; özellikle JIT derlemesi olan runtime’lar için bu geçerlidir.

POST İstekleri ve Lua Scriptleri

wrkin asıl gücü Lua scriptleriyle ortaya çıkar. POST isteği göndermek ya da her istekte farklı veri kullanmak için Lua’ya ihtiyaç duyarsınız.

Basit bir POST isteği için:

-- post_test.lua
wrk.method = "POST"
wrk.body   = '{"username": "testuser", "password": "testpass123"}'
wrk.headers["Content-Type"] = "application/json"

Çalıştırmak için:

wrk -t4 -c50 -d30s --latency -s post_test.lua http://api.example.com/auth/login

Daha gelişmiş bir senaryo: Her istekte farklı kullanıcı ID’si gönderme:

-- dynamic_request.lua
local counter = 0
local user_ids = {101, 205, 333, 418, 509, 612, 721, 834, 945, 1000}

request = function()
  counter = counter + 1
  local user_id = user_ids[(counter % #user_ids) + 1]
  
  local body = string.format('{"user_id": %d, "action": "view"}', user_id)
  
  return wrk.format("POST", "/api/events", {
    ["Content-Type"] = "application/json",
    ["X-Request-ID"] = tostring(counter)
  }, body)
end

response = function(status, headers, body)
  if status ~= 200 then
    io.write("Non-200 response: " .. status .. "n")
  end
end
wrk -t4 -c100 -d60s --latency -s dynamic_request.lua http://api.example.com

Bağlantı Sayısını Kademeli Artırarak Kapasite Testi

Sunucunun hangi noktada doyuma ulaştığını bulmak için bağlantı sayısını kademeli artırın. Bunun için basit bir shell script yazabilirsiniz:

#!/bin/bash
# capacity_test.sh

URL="http://localhost:8080/api/products"
THREADS=4
DURATION="30s"
CONNECTIONS=(10 25 50 100 200 500)

echo "Connection | Req/sec | P99 Latency | Errors"
echo "----------|---------|-------------|-------"

for conn in "${CONNECTIONS[@]}"; do
  result=$(wrk -t${THREADS} -c${conn} -d${DURATION} --latency ${URL} 2>&1)
  
  rps=$(echo "$result" | grep "Requests/sec" | awk '{print $2}')
  p99=$(echo "$result" | grep "99%" | awk '{print $2}')
  errors=$(echo "$result" | grep "Socket errors" | grep -o "timeout [0-9]*" | awk '{print $2}')
  errors=${errors:-0}
  
  echo "${conn} | ${rps} | ${p99} | ${errors}"
  sleep 10
done

Bu testi çalıştırdığınızda genellikle şu pattern’i görürsünüz: Belirli bir noktaya kadar Req/sec artar, sonra platoya oturur ya da düşmeye başlar. P99 latency ise genellikle daha erken bozulmaya başlar. Kapasite sınırınız bu iki eşiğin kesiştiği noktalarda gizlidir.

Sonuçları Dosyaya Kaydetmek ve Karşılaştırmak

Benchmark sonuçlarını tarihiyle birlikte kaydetmek, özellikle deployment öncesi ve sonrası karşılaştırmalar için kritiktir:

#!/bin/bash
# benchmark_with_log.sh

TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
URL=${1:-"http://localhost:8080/api/health"}
OUTPUT_FILE="benchmark_${TIMESTAMP}.txt"

echo "Benchmark Testi: ${TIMESTAMP}" > ${OUTPUT_FILE}
echo "URL: ${URL}" >> ${OUTPUT_FILE}
echo "================================" >> ${OUTPUT_FILE}

wrk -t4 -c100 -d60s --latency ${URL} >> ${OUTPUT_FILE} 2>&1

echo "Sonuclar kaydedildi: ${OUTPUT_FILE}"
cat ${OUTPUT_FILE}

Dağıtım öncesi referans oluşturmak için bu scripti CI/CD pipeline’ınıza entegre edebilirsiniz. Her release öncesi çalışır ve bir önceki release ile Req/sec ya da P99 değerleri arasındaki fark belirli bir eşiği geçerse pipeline’ı durdurabilirsiniz.

Yaygın Hatalar ve Tuzaklar

wrk kullanırken sıkça karşılaşılan ve sinir bozucu olan birkaç durum var:

Thread sayısını çok yüksek ayarlamak: Thread sayısını CPU çekirdek sayısının çok üstüne çıkarmak genellikle sonucu iyileştirmez, aksine context switching overhead’i nedeniyle düşürür. Kural olarak 2 * CPU çekirdek sayısı değerinin üzerine çıkmayın.

Çok kısa süre: 10-15 saniyelik testler aldatıcı olabilir. Özellikle GC baskısı olan uygulamalarda (Java, .NET) 60 saniyenin altında test yapmak gerçekçi sonuçlar vermez. Garbage collection devreye girdiğinde yaşanan gecikmeler kısa testlerde yeterince görünmez.

Test eden makinenin kendisini darboğaza dönüştürmek: wrki ve test ettiğiniz servisi aynı makinede çalıştırmak sonuçları ciddi ölçüde etkiler. Mümkünse ayrı bir makine ya da konteynerden çalıştırın.

Tek bir endpoint ile genel kapasite ölçümü yapmak: Ürün listeleme endpoint’i 2000 Req/sec kaldırıyor diye sitenizin 2000 kullanıcıyı kaldıracağını düşünmek büyük yanılgıdır. Gerçek yük, farklı endpoint’lerin karışımından oluşur.

Keep-alive durumunu göz ardı etmek: wrk varsayılan olarak keep-alive kullanır. Eğer uygulamanız ya da önündeki proxy keep-alive’ı kapatıyorsa, her istek yeni bir bağlantı açar ve bu TCP handshake overhead’ini teste yansıtır. Gerçek kullanım senaryonuzu düşünerek seçim yapın.

wrk2 ile Constant Throughput Testi

Standart wrk aracı “coordinated omission” adı verilen bir probleme tabidir: Sunucu yavaşladığında yeni istek göndermeyi geciktirir ve böylece gerçek kuyruk etkisini gizler. wrk2 bu problemi çözer ve sabit throughput’ta test yapmanızı sağlar:

# wrk2 kurulumu
git clone https://github.com/giltene/wrk2.git
cd wrk2
make
sudo cp wrk /usr/local/bin/wrk2

# 1000 req/sec sabit throughput ile test
wrk2 -t4 -c100 -d60s -R1000 --latency http://localhost:8080/api/products

-R1000 parametresi saniyede 1000 istek gönder anlamına gelir. Bu mod, SLA testleri için çok daha doğru sonuçlar üretir. Örneğin “1000 req/sec yükünde P99 latency 100ms altında kalmalıdır” gibi bir SLA’yı doğrulamak için wrk2 kullanılmalıdır.

Sonuçları Grafana ile İzlemek

Gerçek dünyada wrk çıktısını terminalden okumak yeterli olmayabilir. Sonuçları bir veritabanına yazıp Grafana’da görselleştirmek mümkündür. Lua response hook’u kullanarak InfluxDB’ye metrik gönderebilirsiniz:

-- influx_reporter.lua
local influx_url = "http://influxdb:8086/write?db=wrk_metrics"
local start_time = os.time()

done = function(summary, latency, requests)
  local body = string.format(
    "wrk_results,test=api_load rps=%.2f,p50=%.2f,p99=%.2f,errors=%d %d",
    summary.requests / summary.duration * 1e6,
    latency:percentile(50) / 1000,
    latency:percentile(99) / 1000,
    summary.errors.status,
    os.time() * 1e9
  )
  
  os.execute(string.format(
    'curl -s -XPOST "%s" --data-binary "%s"',
    influx_url, body
  ))
end
wrk -t4 -c100 -d60s --latency -s influx_reporter.lua http://api.example.com/v1/products

Sonuç

wrk, özellikle hızlı hipotez doğrulama senaryoları için vazgeçilmez bir araç. “Bu Nginx konfigürasyon değişikliği gerçekten fark yarattı mı?”, “Yeni connection pool boyutum doğru mu?”, “Bu endpoint refactoring’den sonra daha mı yavaşladı?” gibi soruların cevabını dakikalar içinde alabilirsiniz.

Bununla birlikte wrkin sınırlarını da aklınızda tutun. HTTP/2 desteği yok, karmaşık kullanıcı akışları modellemiyor, gerçek tarayıcı davranışını simüle etmiyor. Bunlar için k6 ya da Locust’a geçmek gerekiyor. Ama bir API endpoint’inin ham kapasitesini ölçmek, konfigürasyon değişikliklerini A/B test etmek ya da CI/CD’ye basit bir regression testi eklemek istiyorsanız wrk mükemmel bir seçim.

En önemli nokta şu: Benchmark sonuçlarına izole bir şekilde bakmamalısınız. Aynı anda sunucu tarafında CPU, memory, disk I/O ve network metriklerini de izleyin. wrk size “ne kadar” sorusunun cevabını verir; darboğazın nerede olduğunu bulmak için sistem metriklerini de görmek zorundasınız.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir