Postfix Kuyruğunu Ölçekleme: Process Limiti Değil, Concurrency ve Rate Akordu
master.cf'deki process limitini yükselttiniz ama kuyruk hâlâ şişiyor. Çünkü asıl darboğaz smtp process sayısı değil; hedef başına concurrency, rate_delay ve Postfix'in feedback döngüsü. evilmail.pro'nun outbound yükünden çıkan ölçüm temelli bir akort reçetesi.
EvilMail Team31 Temmuz 202613 dk okuma
Kuyruk şişince refleks bellidir: master.cf'e gidip smtp satırındaki process limitini 100'den 400'e çıkarmak, postfix reload demek ve hiçbir şeyin değişmediğini görmek. Deferred kuyruk aynı hızla büyümeye devam eder, Gmail'e teslim aynı yavaşlıktadır. Çünkü yükselttiğiniz sayı, çözmeye çalıştığınız problemin parçası değildi.
Postfix'te giden mesaj hızı üç ayrı katmanda sınırlanır: kaç smtp process'i çalışabilir (process limiti), tek bir hedef alan adına aynı anda kaç bağlantı açılır (destination concurrency) ve o bağlantılara ne ritimde mesaj verilir (rate delay). Process limitini artırmak sadece ilk kapıyı genişletir. Ama Postfix, gmail.com'a varsayılan olarak yalnızca 20 eşzamanlı bağlantı açar; 400 process'iniz olsa bile geri kalanı boş bekler. Gerçek şişe boynu her zaman per-destination katmandadır, ve orayı körlemesine açmak deliverability'yi bozar.
Bu yazı, evilmail.pro'nun kendi outbound ve temp-mail relay yükünden çıkardığı sayılarla, tahmin yerine ölçüme dayalı bir akort reçetesi.
Kuyruk neden şişer: process limiti mi, concurrency mi?
Postfix Concurrency ve Kuyruk Ölçekleme Akordu | evilmail.pro — EvilMail Blog
Önce iki kavramı ayıralım. master.cf'teki smtp servisinin sondan bir önceki alanı (maxproc) o servisin çalıştırabileceği maksimum process sayısıdır. Boşsa default_process_limit (varsayılan 100) geçerlidir. Bu, teorik toplam paralellik tavanınızdır — tüm hedeflere dağılan.
# master.cf — soldan sağa: service type private unpriv chroot wakeup maxproc command
smtp unix - - n - - smtp
Buradaki boş maxproc (-) default_process_limit = 100 demek. Ama Postfix bu 100 process'i tek bir hedefe yığmaz. qmgr (queue manager) her alan adı için ayrı bir per-destination concurrency window tutar ve varsayılan tavan default_destination_concurrency_limit = 20'dir. Yani 10.000 mesajınız gmail.com'a gidecekse, kaç process'iniz olursa olsun aynı anda en fazla 20 bağlantı üzerinden akar.
Bu tek satır active ile deferred'ı ayırmaz, ve ayrım kritiktir. active queue qmgr'ın şu an teslim etmeye çalıştığı, bellekte tuttuğu sınırlı sayıda mesajdır. deferred queue ise alıcının reddettiği (4xx) ya da bağlanılamayan mesajların, giderek uzayan aralıklarla yeniden denenmek üzere park ettiği yerdir. Per-destination concurrency zaten doluysa, process limitini artırmak active queue akışını hızlandırmaz; sadece boşta process yaratır.
deferred sayısı sürekli büyüyorsa problem hız değil, alıcı sizi throttle ediyor demektir — ve o durumda concurrency'yi artırmak yangına benzin döker.
Concurrency ve rate: dört ayarın anatomisi
Per-destination davranışı dört parametre yönetir. Hepsi postconf -d | grep concurrency ile efektif değeriyle görülebilir.
`default_destination_concurrency_limit = 20` — Bir hedefe aynı anda açılabilecek maksimum bağlantı. Tavan.
`initial_destination_concurrency = 5` — Yeni bir hedefe teslime başlarken açılan ilk bağlantı sayısı. Postfix buradan yukarı ramp'ler.
`default_destination_rate_delay = 0s` — İki teslim arası zorunlu bekleme. Sıfırsa ritim yok, olabildiğince hızlı.
`default_destination_recipient_limit = 50` — Tek SMTP oturumunda kaç alıcıya (RCPT TO) mesaj verilebileceği.
Burada Postfix'in en çok kafa karıştıran davranışı devreye girer. `rate_delay` sıfırdan farklı yapıldığı an, o hedef için concurrency otomatik olarak 1'e zorlanır. Rate ve concurrency birlikte kullanılamaz — mantıklıdır, çünkü "her 2 saniyede bir mesaj" demek zaten "aynı anda bir bağlantı" demektir. Ama bunu bilmeden default_destination_rate_delay = 1s yazan operatör, tüm giden trafiğini 20 kat yavaşlatmış olur ve nedenini haftalarca bulamaz.
Sayısal fark net: 20 concurrency ile ortalama 300ms süren bir SMTP teslimi teorik olarak saniyede ~66 mesaj demek. rate_delay = 1s (dolayısıyla concurrency 1) ise saniyede en fazla 1 mesaj. Aradaki fark ~66 kat. Bu yüzden rate_delay'i asla global koymayın — sadece sizi açıkça throttle eden tek tük hedefe, transport bazında verin.
Hedef başına transport: gerçek ölçekleme buradan başlar
Doğru mimari, "tüm dünyaya tek smtp ayarı" değil, hedefin davranışına göre ayrı transport hatlarıdır. Gmail sizi seviyor mu, agresif gidin; yeni bir bulk hedefe mi çıkıyorsunuz, kısın. Bunu master.cf'te ayrı servisler tanımlayıp transport_maps ile yönlendirerek yaparsınız.
master.cf — iki ek transport tanımlayalım:
# Isınmış, itibarlı IP'den agresif — Gmail/Outlook gibi kooperatif hedefler
smtp-gmail unix - - n - - smtp
-o smtp_bind_address=203.0.113.10
-o syslog_name=postfix-gmail
# Yeni/temkinli hat — bulk ve throttle'lı hedefler
smtp-bulk unix - - n - - smtp
-o smtp_bind_address=203.0.113.10
-o syslog_name=postfix-bulk
main.cf — her transport için ayrı concurrency/rate override. Parametre adı servis adı + `_destination_...` kalıbındadır:
# Isınmış hat: yüksek concurrency, rate yok
smtp-gmail_destination_concurrency_limit = 20
smtp-gmail_destination_recipient_limit = 50
# Bulk hat: düşük concurrency + rate_delay (concurrency zaten 1'e düşer)
smtp-bulk_destination_concurrency_limit = 3
smtp-bulk_destination_rate_delay = 2s
smtp-bulk_destination_recipient_limit = 10
transport_maps = hash:/etc/postfix/transport
/etc/postfix/transport — hangi domain hangi hattan çıkacak:
gmail.com smtp-gmail:
googlemail.com smtp-gmail:
outlook.com smtp-gmail:
hotmail.com smtp-gmail:
# geri kalan her şey main.cf'teki default_transport'a düşer
Sonra derleyip yeniden yükleyin — restart değil, reload yeter:
bash
postmap /etc/postfix/transport
postfix reload
Gönderen alan adına göre farklı hat isterseniz sender_dependent_default_transport_maps de aynı şekilde çalışır. Birden fazla çıkış IP'niz varsa her transport'a ayrı smtp_bind_address verip IP itibarını hatlara bölebilirsiniz — evilmail'de temp-mail relay trafiği ile işlemsel outbound ayrı IP'lerden çıkar, tam da bu yüzden.
Postfix initial_destination_concurrency'den başlayıp sabit kalmaz; her hedef için concurrency window'unu teslim sonuçlarına göre canlı ayarlar. Mekanizmayı üç parametre yönetir:
`..._concurrency_positive_feedback = 1` — Her başarılı teslimde window'a eklenen pay (window += feedback/window).
`..._concurrency_negative_feedback = 1` — Her deferral'da window'dan düşülen pay.
`..._concurrency_failed_cohort_limit = 1` — Kaç ardışık başarısız kohorttan sonra hedefin ölü sayılıp concurrency'nin 1'e çekileceği.
Pratikte: teslimler 250 ile dönüyorsa Postfix window'u 5 → 6 → 7 ... → 20 (limit) diye yukarı ramp'ler. Alıcı 421/450 dönmeye başlarsa window aşağı iner ve ardışık başarısızlıklar failed_cohort_limit'i aşınca hedef geçici "dead" işaretlenir. Yani Postfix zaten adaptiftir — sizin işiniz tavanı (concurrency_limit) ve tabanı (initial) makul koymak, gerisini feedback döngüsüne bırakmak.
Feedback döngüsünü loglardan doğrulayabilirsiniz. conn_use= teslim başına bağlantı yeniden kullanımını gösterir; yüksekse Postfix aynı bağlantıda birden çok mesaj göndermiş, verimli. qmgr'ın delivered= ve deferred= sayaçları da her run sonunda özet basar:
Gmail throttle'a girdiğinizde tipik yanıt 421-4.7.0 [203.0.113.10] Our system has detected an unusual rate ya da 450-4.2.1 The user you are trying to contact is receiving mail at a rate that... olur. Doğru refleks concurrency'yi artırmak değil, o hedefi smtp-bulk'a taşıyıp IP'yi ısıtmaktır. Kalıcı 4xx'i geçici bounce gibi ele almak isterseniz soft_bounce = yes test sırasında işe yarar ama production'da açık bırakmayın.
Kuyruk tarama ritmi: queue_run_delay ve retry stratejisi
deferred mesajların "unutulmuş" görünmesinin sebebi backoff'tur. Postfix bir mesaj deferred olunca onu hemen değil, artan aralıklarla yeniden dener:
`queue_run_delay = 300s` — qmgr'ın deferred kuyruğu ne sıklıkta taradığı.
`minimal_backoff_time = 300s` — İlk retry'a kadar minimum bekleme (Postfix 3.x).
`maximal_backoff_time = 4000s` — İki retry arası maksimum aralık (~66 dk).
`maximal_queue_lifetime = 5d` — Mesaj bu süre teslim edilemezse bounce olur.
Yani bir mesaj 40 dakikadır deferred'daysa, Postfix onu her 40-60 dakikada bir deniyor olabilir — "takılmış" değil, backoff'un uzağında. Bu davranışı zorla kırıp tüm kuyruğu hemen denetmek istediğinizde:
bash
postqueue -f # tüm deferred kuyruğunu flush et (bir queue run tetikle)
postsuper -r ALL # tüm mesajları yeniden zamanla (backoff sayacını sıfırla)
postsuper -r 3F8A2B1C4D # tek bir mesajı queue ID ile yeniden zamanla
Dikkat: agresif postsuper -r ALL + kısa queue_run_delay kombinasyonu, greylisting yapan hedeflerde ters teper — aynı mesajı sürekli erken deneyip greylist penceresini sürekli sıfırlarsınız. Backoff'a saygı gösterin.
Ölçüm: akort etmeden önce ve sonra ne bakılır
Akort körlemesine yapılmaz. Değişiklikten önce ve sonra aynı metrikleri alın. En değerli araç qshape — domain × zaman matrisi verir:
bash
qshape deferred
Matrisi okumak teşhisin yarısıdır. Sağ kolonlar (160m+) doluysa eski deferral birikmiş, backoff uzamış demektir — alıcı throttle ediyor. Tek bir domain satırı diğerlerine göre şişmişse o hedef izole bir darboğazdır; ona ayrı transport açıp concurrency düşürmek doğru hamle. Toplam (T) kolonu her yerde büyükse problem hedef değil, sizin IP itibarınız ya da global bir yanlış ayardır.
Sürekli görünürlük istiyorsanız kumina/postfix_exporter ile Prometheus'a postfix_smtpd_connects, kuyruk boyutu ve teslim gecikmesi metriklerini akıtın; evilmail'de deferred kuyruk boyutu bir alarm eşiğidir, göz kararı değil.
Değişikliği canlıya sürmeden önce kontrollü yük testi yapın. Postfix'in kendi smtp-source/smtp-sink ikilisi tam bunun için var:
-c teslim başına istatistik basar; concurrency değişiminin gerçek throughput'a etkisini burada, production'ı riske atmadan ölçersiniz.
Pratik akort checklist
Baseline ölç. Akort öncesi qshape deferred, qshape -s active ve postqueue -p | tail -1 çıktısını kaydet. Kıyas noktan olmadan "iyileşti" diyemezsin.
Hedef başına transport ayır. Kooperatif hedefler (smtp-gmail) ve temkinli/bulk hat (smtp-bulk) için master.cf'te ayrı servis + transport_maps.
Concurrency'yi düşükten ramp'le. Yeni/ısınmamış IP'de hedef başına 2-3, ısınmış itibarlı IP'de 20. initial_destination_concurrency'yi düşük tut, feedback döngüsüne çalışma alanı bırak.
`rate_delay`'i sadece throttle'lı hedefte kullan. Global asla. Unutma: set ettiğin an o hedefin concurrency'si 1'e kilitlenir.
`queue_run_delay`'i yükten değil deferral pattern'inden ayarla. Kuyruk büyük diye taramayı sıklaştırmak greylisting'i tetikler.
`postfix reload` — restart değil. concurrency/rate değişiklikleri ve yeni master.cf servisleri reload ile yeter.
`qshape` ile doğrula. Değişiklikten 30-60 dk sonra matrisi tekrar oku; sağ kolonlar boşalıyor, şişik satır düzeliyorsa akort tutmuş demektir.
Kuyruk performansı bir "daha güçlü sunucu" problemi değildir. Postfix zaten adaptif; sizin işiniz ona doğru tavanları vermek, hedefleri davranışlarına göre ayırmak ve her hamleyi ölçümle doğrulamak. evilmail.pro'nun relay'inde saniyede binlerce mesajı taşıyan da bu — büyük sayılar değil, doğru akort.