GraphQL yük testi: operasyon başına gecikme, errors ve mutation'lar
GraphQL'de bütün istekler tek bir adrese gider: POST /graphql. Bir HTTP yük testi aracı bunu tek bir uç nokta olarak görür; oysa aynı adresin arkasında 5 ms süren bir alan sorgusu da, beş tabloyu birleştiren 2 saniyelik bir sorgu da vardır. Üstelik GraphQL sunucuları hatayı çoğu zaman HTTP 200 ile döner. Bu yüzden GraphQL yük testi iki şeyi doğru yapmalıdır: sonuçları operasyona göre ayırmak ve yanıtın içindeki hatayı görmek.
Neyi ölçüyoruz?
- Operasyon başına gecikme: her query ve mutation'ın p95 ve p99'u ayrı. Tek bir "/graphql p95" değeri ucuz ve pahalı sorguları karıştırır, hiçbirini doğru göstermez.
- Yanıttaki hatalar:
errorsdizisi dolu bir yanıt, HTTP durumu 200 olsa da başarısızdır. Yük altında en sık görülen GraphQL hataları resolver zaman aşımları ve alt servislerden gelen hatalardır; HTTP katmanında görünmezler. - Kısmi sonuçlar: GraphQL bir alan başarısız olurken diğerlerini döndürebilir (
dataile birlikteerrors). Bunun beklenen bir durum mu yoksa hata mı olduğuna siz karar verin ve testte açıkça yazın. - Sorgu maliyeti: iç içe alanlar (ürün → yorumlar → yazar) her seviyede veritabanına gidebilir (N+1). Gerçek istemcilerin gönderdiği sorguların derinliğini kullanın; daha sığ sorgular sistemi olduğundan hızlı gösterir.
Yük modelini kurmak
Operasyon karışımını gerçek trafikten alın: ön yüz bir sayfa açılışında hangi sorguları, hangi sırayla gönderiyorsa testteki kullanıcı yolculuğu da onu yapsın. Bir sorgunun sonucundaki kimliği sonraki sorguya değişkenle taşıyın; hep aynı kimliği sorgulamak önbelleği ölçer. Sistemin hangi hızda bozulduğunu görmek için yükü basamaklarla artırın ve ortalamaya değil yüzdeliklere bakın (p95 ve p99 rehberi).
Sık yapılan hatalar
- Yalnız HTTP durumuna bakmak. %0 hata oranı gösteren bir test, yanıtların yarısında
errorstaşıyor olabilir. - Mutation'ları fark etmeden çalıştırmak. Şemadan üretilen bir testte
createOrderher iterasyonda gerçek bir sipariş açar. Mutation'ları bilerek seçin ve test ortamında çalıştırın. - Persisted query ve önbellek. Sunucu ya da CDN sorguları önbelleğe alıyorsa, hep aynı değişkenlerle gönderilen sorgu yalnız önbelleği ölçer. Değişkenleri CSV verisiyle çeşitlendirin.
Spitfire ile
Spitfire'da her GraphQL operasyonu ayrı bir GraphQL adımıdır: uç noktanın adresi, sorgu, değişkenler (JSON, şablonlu: {"id": "{{userId}}"}) ve operasyon adı. Metrikler adım başına ayrıldığı için her operasyonun p95'i ve hata oranı ayrı görünür ve ayrı eşik alır. Yanıtta errors doluysa adım, HTTP 200 dönse bile başarısız sayılır ve ilk hata iletisi koşu sayfasındaki hata örneklerinde görünür. Kısmi sonuç beklenen bir durumsa errors içeren yanıtları başarısız sayma kutusunu işaretleyip check'lerle doğrulayın.
- Şemadan sorgu: adımda Şemayı yükle introspection sorgusunu adımın adresi ve header'larıyla gönderir. Listeden bir alan seçince sorgu, örnek değişkenler ve operasyon adı doldurulur; zorunlu argümanlar değişken olur.
- Şemadan test: API / HAR içe aktar'da adres olarak uç noktayı verin (ya da introspection çıktısını dosya olarak yükleyin); seçtiğiniz her query ve mutation bir adım olur (içe aktarma rehberi).
- Mutation koruması: belgede
mutationolan adım hedefte veri değiştirir sayılır; koşu, deneme ve zamanlama için yazma onayı gerekir ve onay denetim kaydına yazılır.
200 VU'nun ürün listesini açıp listedeki ilk ürünün ayrıntısına gittiği bir test. İlk sorgunun sonucundaki ürün kimliği ikinci sorguya değişkenle taşınır. Liste sorgusunun p95'i 250 ms'nin, ayrıntınınki 400 ms'nin, hata oranı (GraphQL errors dahil) %1'in altında kalmalı. Test spitfire validate ile doğrulandı.
{
"name": "GraphQL: katalog ve sepet",
"variables": { "api": "https://staging.example.com/graphql" },
"scenarios": [
{ "name": "alisveris",
"executor": { "type": "ramping-vus", "startVUs": 0,
"stages": [ { "duration": "2m", "target": 200 }, { "duration": "8m", "target": 200 },
{ "duration": "30s", "target": 0 } ] },
"steps": [
{ "id": "products", "name": "Products", "protocol": "graphql",
"graphql": { "url": "{{api}}",
"query": "query Products($first: Int!) { products(first: $first) { id name price } }",
"variables": "{\"first\": 20}", "operationName": "Products" },
"checks": [ { "type": "jsonPath", "path": "$.data.products[0].id", "op": "exists" } ],
"extract": [ { "var": "productId", "from": "jsonpath", "expr": "$.data.products[0].id" } ] },
{ "id": "product", "name": "Product", "protocol": "graphql",
"graphql": { "url": "{{api}}",
"query": "query Product($id: ID!) { product(id: $id) { id name reviews(first: 5) { rating } } }",
"variables": "{\"id\": \"{{productId}}\"}", "operationName": "Product" },
"thinkTime": { "min": "1s", "max": "3s" } }
] }
],
"thresholds": [
{ "metric": "req_duration", "filter": { "step": "products" }, "expr": "p(95)<250" },
{ "metric": "req_duration", "filter": { "step": "product" }, "expr": "p(95)<400" },
{ "metric": "req_failed", "expr": "rate<0.01" }
]
}GraphQL adımı HTTP ile aynı metrikleri üretir (req_duration, req_failed, bekleme, bağlantı ve TLS süreleri) ve aynı VU'nun HTTP adımlarıyla bağlantıları paylaşır. Test seçeneklerinde HTTP sürümü olarak HTTP/3 seçilirse GraphQL istekleri de QUIC üzerinden gider. Subscription'lar desteklenmez.
Spitfire tek komutla Docker'a ya da Kubernetes'e kurulur; ücretsiz sürümde bütün test özellikleri ve protokoller açıktır.