gRPC yük testi: unary ve streaming çağrılar, HTTP/2 bağlantıları ve proto
gRPC servisleri çoğu zaman bir sistemin iç trafiğini taşır: API gateway'in arkasındaki sipariş, stok ya da ödeme servisleri birbirini gRPC ile çağırır. Bu çağrılar REST'e göre küçük ve hızlıdır, ama yük testi yaparken HTTP/1.1 alışkanlıkları yanıltır: gRPC uzun ömürlü HTTP/2 bağlantıları üzerinde çalışır, hata durumunu HTTP kodu değil status kodu taşır, mesajlar JSON değil protobuf'tur. Bu rehber bir gRPC yük testini doğru kurmak için bilmeniz gerekenleri anlatıyor.
Unary ve streaming
gRPC'de dört çağrı türü vardır: unary (bir istek, bir yanıt), server streaming (bir istek, bir dizi yanıt: fiyat akışı, uzun bir listenin parça parça gelmesi), client streaming (bir dizi mesaj, tek yanıt: toplu yükleme, sepete satır satır ekleme) ve bidirectional (bidi) streaming (iki taraf da aynı stream'de istediği an mesaj gönderir: sohbet, oyun, sürekli senkronizasyon). Servislerin büyük kısmı unary metotlardan oluşur: her çağrı bir istek gibi sayılır, gecikmesi ve status kodu ölçülür.
Streaming metotlarda tek bir süre yetmez; stream'in toplam süresi çoğu zaman sizin seçtiğiniz mesaj sayısı ve bekleme aralığıyla belirlenir, sunucunun hızını göstermez. Bakılacak ölçüler şunlardır:
- İlk mesaja kadar geçen süre: stream'in açılmasından ilk yanıta kadar. Kimlik doğrulama, abonelik kurulumu ve ilk sorgu burada görünür; server streaming'de kullanıcının beklediği süre budur.
- Mesaj başına gecikme: bidi'de bir mesajın gönderilmesinden yanıtına kadar geçen süre. Bunu ölçmek için hangi yanıtın hangi mesaja ait olduğunu bilmek gerekir; sunucu mesajlara sırayla yanıt vermiyorsa bu eşleştirme yanıltır.
- Mesajlar arası süre: server streaming'de art arda gelen iki yanıt arasındaki süre. Yük altında büyüyorsa sunucu akışı besleyemiyordur.
- Gönderilen ve alınan mesaj sayısı, ve aynı anda açık stream sayısı. Uzun ömürlü stream'ler, açık kaldıkları sürece bağlantıdaki stream sınırından bir yer tutar; aşağıdaki bağlantı sayısı konusu stream'lerde daha da önemlidir.
Streaming metotları unary çağrılarla aynı tabloda değerlendirmeyin; eşiklerini de kendi ölçülerine yazın.
Bağlantı sayısı: en sık yapılan hata
HTTP/2 tek bir bağlantı üzerinde birçok isteği aynı anda taşır (multiplexing). Bu yüzden bir yük testi aracı bütün sanal kullanıcıların (VU) çağrılarını tek bir bağlantıdan gönderebilir; ama bu iki yanıltıcı sonuç doğurur:
- Sunucunun stream sınırı. Sunucular bir bağlantıda aynı anda açık stream sayısını sınırlar (çoğu zaman 100–128). Sınır dolunca yeni çağrılar istemcide sıra bekler ve bu bekleme gecikme olarak ölçülür; sunucu yavaş değildir, istemci kuyruktadır.
- L4 load balancer. Bağlantı seviyesinde dağıtım yapan bir load balancer (örneğin Kubernetes Service) bir bağlantının bütün çağrılarını tek bir pod'a gönderir. Tek bağlantıyla yaptığınız test, 10 pod'luk bir servisin yalnız birini yükler.
Gerçekte servisinizi çağıran istemciler kaç bağlantı açıyorsa testte de ona yakın bir sayı kullanın. Az sayıda istemci instance'ı birçok çağrıyı paylaşıyorsa birkaç paylaşılan bağlantı, çok sayıda bağımsız istemci (mobil uygulamalar, edge cihazlar) varsa her VU için ayrı bağlantı gerçeğe yakındır.
Gerçekçi mesajlar ve metot karışımı
Bir gRPC servisinin yükünü belirleyen yalnız çağrı sayısı değildir; mesajın içeriği de önemlidir. Her çağrıda aynı id'yi istemek veritabanı önbelleğinden döner ve gerçekte olduğundan hızlı görünür. Id'leri, kullanıcıları ve sorgu parametrelerini bir veri dosyasından ya da rastgele üreteçlerden alın; liste dönen metotlarda sayfa boyutunu canlıdakine yakın tutun. Servisin metotlarını da canlıdaki oranlarla karıştırın: okuma metotları yazma metotlarından çok daha sık çağrılıyorsa testte de öyle olmalı. Kimlik doğrulama metadata'sını (token, tenant) gerçek akıştaki gibi gönderin; bir interceptor'ın her çağrıda yaptığı iş de yükün bir parçasıdır.
Status kodları ve deadline
gRPC çağrısının sonucu bir status kodudur: OK dışındaki her kod bir hatadır, ama hepsi aynı anlama gelmez. UNAVAILABLE genellikle bağlantının kurulamadığını ya da sunucunun yükü reddettiğini, DEADLINE_EXCEEDED çağrının süresinin dolduğunu, RESOURCE_EXHAUSTED bir kotanın ya da sınırın aşıldığını, UNAUTHENTICATED kimlik bilgisinin eksik olduğunu gösterir. Yük arttıkça hangi kodun arttığına bakmak, sorunun ağda mı, kapasitede mi, yoksa bir sınır ayarında mı olduğunu ayırmanın en kısa yoludur.
Deadline'ı canlıdaki istemcilerin kullandığı değere yakın tutun. Çok uzun bir deadline yavaşlamayı hataya çevirmez, yalnız gecikmeyi büyütür; çok kısa bir deadline ise sunucu sağlıklıyken bile hata üretir. Eşikleri yüzdeliklerle yazın: unary çağrılarda gecikme düşük olduğu için p99'daki birkaç milisaniyelik kayma bile anlamlıdır (p95 ve p99 rehberi).
Spitfire ile
Spitfire'ın gRPC adımı unary, server streaming, client streaming ve bidi metotları çağırır 0.17.0+. Metot listesi streaming metotların türünü yazar (server stream, client stream, bidi stream) ve editör yalnız o türe uyan seçenekleri gösterir.
- Bağlantı. Bağlantılar'da bir gRPC bağlantısı ekleyin: adres (
host:port), gerekirse TLS (CA sertifikası, istemci sertifikası ve anahtarı, SNI), authority ve bir bearer token (her çağrıyaauthorization: Bearer …olarak eklenir; adımın kendi authorization metadata'sı varsa o kullanılır). Gizli alanlar şifreli saklanır. - Şema: reflection ya da proto. Sunucuda server reflection açıksa şema oradan okunur. Açık değilse
.protodosyalarını import ettikleriyle birlikte bağlantıya yükleyin; controller onları derler, runner'lar derlenmiş şemayı alır. Bağlantıdaki Test düğmesi reflection'da servislerin listelenebildiğini, proto ile ise bağlantının kurulabildiğini denetler. - Adım. Metodu listeden seçin (
paket.Servis/Metot); Şablonu doldur mesajı bütün alanlarıyla JSON olarak hazırlar, siz değerleri yazarsınız. Mesajda ve metadata'da değişkenler, CSV verisi ve{{$uuid}}gibi üreteçler kullanılabilir. Yanıt JSON olarak kontrollere ve değişken çıkarmaya (JSONPath) gider; yanıt metadata'sı ve trailer'lar header olarak okunur. - Bağlantı sayısı. Varsayılan olarak bir runner'daki VU'lar 8 paylaşılan HTTP/2 bağlantısına VU numarasına göre dağılır; sayıyı bağlantı ayarında değiştirebilir ya da Her VU ayrı bağlantı seçeneğini açabilirsiniz. Böylece yukarıdaki stream sınırı ve load balancer tuzaklarından kaçınırsınız. Çok sayıda VU uzun stream'ler açıyorsa bu ayarı özellikle gözden geçirin.
Bir sipariş servisinin unary metodunu çağıran bir adım; orderId bir değişkenden ya da CSV dosyasından gelir, yanıttaki durum alanı kontrol edilir.
{
"id": "get_order", "name": "Siparişi getir",
"protocol": "grpc", "connection": "orders-grpc",
"grpc": {
"method": "shop.v1.OrderService/GetOrder",
"message": "{\"orderId\": \"{{orderId}}\"}",
"metadata": [ { "key": "x-tenant", "value": "acme" } ]
},
"checks": [ { "type": "jsonPath", "path": "$.status", "op": "eq", "value": "PAID" } ]
}Koşu sırasında her adımın istek/sn, p95, p99 ve hata oranı canlı izlenir; hata türleri status koduna göre ayrılır (DEADLINE_EXCEEDED zaman aşımı, UNAVAILABLE bağlantı reddi, UNAUTHENTICATED yetki hatası, diğerleri ayrı). Testin istek zaman aşımı (varsayılan 30 sn) unary çağrının deadline'ı olur. Eşikler adım bazında yazılır (p(95)<50), sabit istek hızıyla (arrival rate) sunucunun hangi hızda yetişemediğini kırılma noktası testi bulur. gRPC adımları aynı testte HTTP, Kafka ya da SQL adımlarıyla birlikte bir kullanıcı yolculuğu da oluşturabilir.
Stream adımları
Bir adım bir stream'dir: açılır, mesajlar gönderilir, yanıtlar okunur ve stream bitince adım biter.
- Gönderim. Server streaming istek mesajını bir kez gönderir. Client streaming ve bidi ya bir mesaj listesi (şablonlar, sırayla) gönderir ya da aynı mesajı belirli bir adet kadar tekrarlar;
{{__MSG}}mesajın sırasıdır (0, 1, 2…). Mesajlar arasında istediğiniz kadar beklenir, sonra client kendi tarafını kapatır. Client kapatınca stream'i bitiren sunucular için bidi'de gönderim tarafı açık tutulabilir. - Bitiş. Stream sunucu kapatınca, beklenen sayıda yanıt gelince ya da verdiğiniz süre dolunca biter; süre dolduğunda client stream'i kapatır ve bu hata sayılmaz. Bunların hiçbiri olmazsa deadline'da biter: varsayılanı testin istek zaman aşımı artı adımın kendi gönderim süresidir ve aşılırsa
DEADLINE_EXCEEDEDhatasıdır. Sunucu beklenen sayıda yanıt vermeden stream'i bitirirse adımincompleteolarak başarısız olur. - Kontroller ve değişken çıkarma son yanıtı ya da bütün yanıtları bir JSON dizisi olarak görür (
$[0].id; "herhangi bir mesajda geçsin" için body contains). Durum stream'in gRPC status'üdür; gönderilen ve alınan mesaj sayılarıgrpc-messages-sent/grpc-messages-receivedheader'larında okunur. - Metrikler. Adımın süresi (
req_duration) bütün stream'dir.grpc_time_to_first_messageaçılıştan ilk yanıta kadar geçen süre; bidi'de her yanıt henüz yanıtlanmamış en eski mesajla eşleştirilir ve aradaki süregrpc_message_latencyolur; eşleşecek mesajı olmayan yanıtlar (server streaming, fazladan push'lar) bir öncekinden bu yana geçen süreyigrpc_message_gapolarak verir.grpc_messages_sentvegrpc_messages_receivedsayaçtır.
Bir sohbet servisinin bidi metoduna 200 ms arayla 20 mesaj gönderen ve 20 yanıt bekleyen bir adım; kontrol bütün yanıtları görür ve son yanıtın son mesaja ait olduğunu doğrular. Altındaki eşikler ilk mesajın ve mesaj başına gecikmenin p95'ini sınırlar. Adım, bu adla bir bağlantısı olan bir testte spitfire validate ile doğrulandı.
{
"id": "chat", "name": "Sohbet",
"protocol": "grpc", "connection": "chat-grpc",
"grpc": {
"method": "chat.v1.ChatService/Talk",
"message": "{\"roomId\": \"{{roomId}}\", \"text\": \"mesaj {{__MSG}}\"}",
"stream": { "count": 20, "interval": "200ms", "receive": 20, "body": "all" }
},
"checks": [ { "type": "jsonPath", "path": "$[19].text", "op": "eq", "value": "mesaj 19" } ]
}"thresholds": [
{ "metric": "grpc_time_to_first_message", "filter": { "step": "chat" }, "expr": "p(95)<300" },
{ "metric": "grpc_message_latency", "filter": { "step": "chat" }, "expr": "p(95)<100" },
{ "metric": "req_failed", "expr": "rate<0.01" }
]Stream metriklerinin hepsine eşik verilebilir ve eşikler raporda görünür. Koşu sayfasının gRPC stream'leri tablosu adım başına gönderilen ve alınan mesajları, ilk mesajın, mesaj gecikmesinin ve mesajlar arası sürenin p95'ini gösterir; CLI özetinde de yer alırlar, karşılaştırmalar ilk mesajın ve mesaj gecikmesinin p95'ini yan yana koyar.
Bilmeniz gereken sınırlar:
- Tablo koşu bitince dolar. gRPC stream'leri tablosu koşu kaydedildikten sonra görünür; koşu sırasında canlı izlenen, stream adımının istek/sn, p95 ve hata oranıdır (süre bütün stream'dir).
- Bidi eşleştirmesi sıraya dayanır.
grpc_message_latencyher mesaja sırayla yanıt veren sunucular için doğrudur. Sunucu yanıtları başka sırayla veriyor, bazı mesajları yanıtsız bırakıyor ya da bir mesaja birkaç yanıt veriyorsa bu metrik yanıltır; o zaman ilk mesaja kadar geçen süreye, mesajlar arası süreye ve stream'in toplam süresine 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.