RabbitMQ yük testi: publisher confirm, consumer hızı ve kuyruk derinliği
RabbitMQ'da sorun genellikle sessiz başlar: consumer'lar gelen mesaja yetişemez, kuyruk büyür, broker bellek ya da disk sınırına yaklaşınca publisher'ları yavaşlatır ve yavaşlık bu kez kuyruğa mesaj gönderen servislerde görünür. RabbitMQ yük testi mesaj yolunu gerçekçi bir hızla zorlar ve şunu sorar: broker bu hızı hangi onay gecikmesiyle kabul ediyor, consumer'lar aynı hızda tüketebiliyor mu, ve kuyruk derinliği sabit mi kalıyor yoksa büyüyor mu.
Neyi ölçüyoruz?
- Publish gecikmesi: publisher confirm açıkken mesajın gönderilmesinden broker'ın onayına kadar geçen süre. Kalıcı (persistent) mesajlar ve quorum kuyruklar onaydan önce diske ve replica'lara yazıldığı için daha yavaştır; testte canlıdaki ayarları kullanın.
- Consumer hızı: saniyede tüketilip onaylanan (ack) mesaj sayısı. Prefetch değeri ve ack biçimi bu hızı doğrudan belirler.
- Kuyruk derinliği: kuyrukta bekleyen mesaj sayısı. Sabit kalan küçük bir derinlik normaldir; yük altında sürekli büyüyen derinlik consumer'ların yetişemediğini gösterir ve sonunda broker'ın bellek ya da disk alarmına götürür.
- Akış kontrolü: broker zorlandığında bağlantıları yavaşlatır, alarm durumunda publisher'ları tamamen durdurur. Bu, publish gecikmesinin aniden sıçraması olarak görünür.
- Hatalar: yönlendirilemeyen (hiçbir kuyruğa düşmeyen) mesajlar, broker'ın reddettikleri (nack), yetki hataları ve zaman aşımları.
Yük modelini kurmak
Publisher tarafında hedef bir hızdır ("saniyede 300 sipariş"), bu yüzden sabit bir varış hızı (arrival rate) kullanın: broker yavaşlasa da gönderim hızı düşmez ve gecikme olduğu gibi görünür. Sabit sayıda VU'lu kapalı bir modelde broker yavaşladıkça publisher'lar da yavaşlar ve sorun throughput'un sessizce düşmesi olarak gizlenir (yük testi türleri).
Consumer tarafında sayıyı canlıdaki consumer instance sayısına eşitleyin. Testin amacı kuyruğun dengede kalıp kalmadığını görmekse publisher hızını consumer'ların gerçek kapasitesinin biraz altında ve biraz üstünde iki ayrı koşuda deneyin; consumer'ların hangi hızda yetişemediğini bulmak için hızı basamaklarla artırın (kırılma noktası testi). Derinliğin büyüyüp büyümediğini görmek için yükün en az 10–15 dakika sabit kalması gerekir.
Sık yapılan hatalar
- Onaysız gönderim. Publisher confirm kapalıyken gönderim neredeyse anlıktır ama broker'ın mesajı alıp almadığı bilinmez; test olduğundan hızlı görünür. Canlıda confirm kullanıyorsanız testte de kullanın.
- Otomatik ack ve sınırsız prefetch. Bu ikisi consumer'ı olduğundan hızlı gösterir ve bir consumer çöktüğünde mesaj kaybına yol açar. Testte servisinizin kullandığı prefetch ve ack biçimini taklit edin.
- Consumer'sız kuyruk. Yalnız publish eden bir test kuyruğu sürekli büyütür ve broker'ı bellek alarmına sokabilir. Bir consumer senaryosu ekleyin ya da kuyruğa bir uzunluk sınırı ve TTL koyun.
- Paylaşılan kuyruk. Canlı consumer'ların okuduğu bir kuyruğa test mesajı göndermeyin. Test için ayrı bir exchange, kuyruk ya da vhost kullanın.
- Ortalama gecikme. Disk yazmaları ve replica senkronizasyonu seyrek ama büyük sıçramalar yapar; p95 ve p99'a bakın (p95 ve p99 rehberi).
Spitfire ile
Önce Bağlantılar'da bir RabbitMQ (AMQP) bağlantısı ekleyin: adres (amqp://host:5672/vhost; vhost adresin yoludur), kullanıcı adı, parola ve gerekiyorsa TLS (açıldığında adres amqps olur). Parola şifreli saklanır, testte yalnız bağlantının adı geçer; adresin içine parola yazılırsa kayıt reddedilir. Varsayılan olarak her runner'daki VU'lar tek bir AMQP bağlantısını paylaşır ve her VU kendi kanallarını açar, tıpkı bir servisin instance'ları gibi; Her VU ayrı bağlantı seçeneği her VU'ya ayrı bir bağlantı verir. Bağlanma ve kanal açma süresi adımın süresine katılmaz. Spitfire exchange ya da kuyruk oluşturmaz: testin kullandığı exchange'ler, kuyruklar ve binding'ler önceden tanımlı olmalıdır. AMQP adımının üç işlemi vardır:
- publish: exchange'e routing key ile bir mesaj gönderir; body, content type, header'lar ve kalıcılık (
persistent) ayarlanabilir. Kanal confirm modundadır: adımın süresi broker'ın onayına kadar geçen süredir. Mesajmandatorygönderilir, yani hiçbir kuyruğa düşmeyen bir mesaj başarısız sayılır; broker'ın reddettiği mesaj da öyle. Değerlerde{{$uuid}},{{$randInt 10 5000}}ya da CSV veri dosyasından değişkenler kullanılabilir. Boş exchange varsayılan exchange'dir: routing key doğrudan kuyruğun adıdır. - consume: VU kuyruğa prefetch 1 ile bir consumer olarak bağlanır ve her adımda bir mesaj alır; mesaj ölçümden sonra onaylanır (ack). Adımın süresi sıradaki mesajı bekleme süresidir; bekleme süresi (varsayılan 10 sn) dolarsa adım zaman aşımıyla başarısız olur. Body kontrollere ve değişken çıkarmaya gider (JSON ise JSONPath ile);
exchange,routing-key,redelivered,content-type,correlation-id,message-idve mesajın kendi header'ları header olarak okunabilir. - rpc: istek-cevap için: mesajı RabbitMQ'nun direct reply-to özelliğiyle ve her istek için ayrı bir correlation id ile gönderir, aynı correlation id'li cevabı bekler. Süre gidiş-dönüştür; bu işlemde publisher confirm beklenmez.
Saniyede 300 sipariş olayını kalıcı olarak orders exchange'ine gönderen ve 10 consumer'la orders.loadtest kuyruğunu tüketen bir test; gönderim onayının p95'i 50 ms'nin, hata oranı %0,1'in altında kalmalı. Exchange, kuyruk ve aralarındaki binding önceden oluşturulmalıdır. Test spitfire validate ile doğrulandı.
{
"name": "RabbitMQ: sipariş kuyruğu",
"scenarios": [
{ "name": "yayinci",
"executor": { "type": "constant-arrival-rate", "rate": 300, "timeUnit": "1s",
"duration": "10m", "preAllocatedVUs": 20, "maxVUs": 100 },
"steps": [ { "id": "publish", "name": "Sipariş yayınla", "protocol": "amqp", "connection": "rabbit",
"amqp": { "action": "publish", "exchange": "orders", "routingKey": "order.created",
"persistent": true, "contentType": "application/json",
"body": "{\"orderId\":\"{{$uuid}}\",\"amount\":{{$randInt 10 5000}}}" } } ] },
{ "name": "tuketici",
"executor": { "type": "constant-vus", "vus": 10, "duration": "10m" },
"steps": [ { "id": "consume", "name": "Sipariş tüket", "protocol": "amqp", "connection": "rabbit",
"amqp": { "action": "consume", "queue": "orders.loadtest", "wait": "5s" },
"checks": [ { "type": "jsonPath", "path": "$.orderId", "op": "exists" } ] } ] }
],
"thresholds": [
{ "metric": "req_duration", "filter": { "step": "publish" }, "expr": "p(95)<50" },
{ "metric": "req_failed", "expr": "rate<0.001" }
]
}Koşu sırasında adım bazında gönderim ve tüketim hızı, p95, p99, gönderilen ve alınan veri ve hata türleri (yetki, bağlantı reddi, yönlendirilemeyen ya da reddedilen mesaj, zaman aşımı) canlı izlenir. Consume adımının süresi uçtan uca gecikme değildir: kuyrukta mesaj birikmişse sıradaki mesaj zaten bekliyordur ve süre kısalır. Bu yüzden consume sürelerinin aniden kısalması kuyruğun büyümeye başladığının işareti olabilir.
Spitfire kuyruk derinliğini kendisi ölçmez. RabbitMQ'nun Prometheus eklentisiyle kuyruk ve broker metriklerini zaten topluyorsanız Observability'te bir Prometheus bağlantısına o sorguları ekleyin: koşu sayfasının Backend sekmesi onları yük basamaklarıyla hizalı gösterir (OpenTelemetry ile kök neden). Spitfire'ın client'ı AMQP 0-9-1 konuşur; AMQP 1.0 ve stream protokolü 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.