Ortalaması sağlıklı görünürken zaman aşımı yüzünden açık artırma kaybeden bir Go bidder'ı, tek bir belirtinin altında iki sorun taşır: exchange'e ait olan ve gidiş-dönüşü kapsayan bir deadline ve mark assist'i teklifi puanlayan goroutine'e yazan bir collector. Deadline'ın nasıl bölündüğü, hangi Go ayarlarının işe yaradığı ve bir Rust hot path'inin neyi düzeltmediği aşağıda.
Ortalaması sağlıklı görünürken zaman aşımı yüzünden açık artırma kaybeden bir bidder, yanlış sayıyla tarif ediliyordur. Deadline exchange'e aittir ve ağı her iki yönde kapsar. Collector sürecin bir özelliğidir; bu yüzden duraklaması aynı anda her bağlantıya yazılır ve tek bir bağlantıdaki sıçrama başka bir açıklama gerektirir.
Rust'a yeniden yazmak mı, runtime'ı ayarlamak mı sorusu, kimsenin sormadığı bir sorunun iki yanıtı arasında bir seçimdir: deadline'ın hangi kısmı, neye harcanıyor. Aşağıdakiler bütçeyi collector'dan, collector'ı scheduler'dan ve yeniden yazımı bunu hak eden bileşenden ayırır.
Kısa yanıt yapısaldır. Deadline'ı exchange belirler ve ağı da kapsar; bu yüzden ilk onarım, tek bir bağlantının p99'unu handler'da geçen süreye, işlemci bekleme süresine ve hat üzerinde geçen süreye böler. amBrain'in kamuya kanıtlayabildikleri: sıfırdan teslim ettiğimiz bir demand-side platform olan RTBBidder'ı biz kurduk; orada her teklif kararı, gösterim başına onlarca hedefleme koşulunu değerlendirir. Ölçüme dayalı olarak yayımladığımız latency rakamları bir reklam bidder'ından değil trading yollarından gelir ve aşağıdaki hiçbir sayı bize ait bir bidder üzerinde ölçülmemiştir.
OpenRTB spesifikasyonu tmax'ı, exchange'in tekliflerin alınması için tanıdığı, İnternet gecikmesi dahil milisaniye cinsinden azami süre olarak tanımlar ve bu değerin önceki tüm yönlendirmelerin yerine geçtiğini söyler. Bütçe, her isteğin içinde gelen bir gidiş-dönüştür.
Karşısında yargılandığınız eşik, kendi panonuzdaki eşik değildir. Google'ın Authorized Buyers dokümantasyonu, yanıtların %85'inin işlem noktasında ölçüldüğü şekliyle deadline içinde ulaşmasını şart koşar ve bunu tutturamayan bidder'ları kısıtlar. Onun saatiyle sizinkinin arasında, hesaplama olmayan her şey yer alır:
Dolayısıyla dahili deadline, o bağlantıda bir yanıtın maliyeti olarak kendi histogramınızın söylediği kadar tmax'ın altında durur ve bu, filo geneli için bir kez belirlenmek yerine exchange başına yeniden türetilir. Deadline bir kapasite planı değildir: deadline'da iptal edilen iş CPU'sunu zaten harcamıştır.
Aşırı yük altında bu, kimsenin saymadığı yanıtlar için tam bedel ödemek demektir; dolayısıyla eksik olan onarım kabul denetimidir: tmax'ı okuyun, ölçtüğünüz kuyruk gecikmesiyle karşılaştırın ve aritmetik tutmadığında no-bid yanıtı verin. Hızlı bir no-bid %85'e sayılır; geç bir teklif sayılmaz.
Garbage collector sürecin bir özelliğidir; bu yüzden herhangi bir bağlantıda tetiklenen bir döngünün faturası her bağlantıya çıkar. İlk ıskalayan, tmax'ı en dar ve isteği en ağır olandır. Aynı tabloyu collector olmadan üreten nedenleri eleyin:
Dördünün hiçbiri bir collector ayarıyla onarılmaz ve bu ayrımın ölçüm araçları vardır. Bir CPU profili collector faturalarını sembole göre ayırır: handler'a yazılanlar için runtime.gcAssistAlloc, arka plan marking'i için runtime.gcBgMarkWorker. Stop-the-world süresi /sched/pauses/total/gc:seconds, çalışmaya hazır bekleme /sched/latencies:seconds ve accept kuyruğu süreç dışından ListenOverflows üzerinden okunur.
Hangi Go onarımına ihtiyacınız olduğuna tek bir ayrım karar verir. Stop-the-world duraklaması aynı anda her goroutine'e yazılır, dolayısıyla tüm bağlantılarda düz bir sıçrama olarak görünür. Mark assist ise ayırmayı yapan goroutine'e yazılır, dolayısıyla en çok ayırma yapan isteklere iner. Assist'i iki şey düşürür: bid request başına daha az bayt ya da arka plan işçilerinin marking'in daha büyük kısmını üstlendiği daha uzun bir döngü. Trafik karışımı değiştiğinde bunlardan yalnızca ilki ayakta kalır.
Go collector'ı eşzamanlıdır ve resmi rehber, duraklama süresinin heap boyutuyla ölçeklenmediğini açıkça söyler; dolayısıyla stop-the-world geçişleri kısadır. Asıl önemli kaynak assist'lerdir: ayırma hızlı olduğunda goroutine'ler collector'a yardım eder, çünkü arka plan marking'i işlemcilerin sabit bir çeyreğini alır ve eksik kalan kısım ayırmayı yapana yazılır.
Hız, bunu bir eğim değil bir eşik yapar. Ayırma hızı, sabit bir arka plan payına karşı QPS ile bid request başına baytın çarpımıdır; bu yüzden trafiğinizin beşte birinde hiç assist etmeyen bir kod, 100K QPS'te neredeyse her istekte assist edebilir.
Marking maliyeti çöple değil, canlı işaretçi grafiğiyle orantılıdır ve bir bidder tam da marking için yanlış olan şekli tutar: kampanya indeksleri, kitle segmentleri, frekans önbellekleri. Discord 2020'de aynı bulguyu yayımladı: belleğin boş olup olmadığına karar vermek için bir LRU önbelleğinin tamamını tarayan bir collector. Tek bir ifadenin altında beş mekanizma saklanır:
Bunu beş ayrı fatura olarak okuyun. Bunlardan tam olarak biri bir collector ayarıyla kapanır ve ayrım ölçülmeden dil değiştirilerek hiçbiri kapanmaz.
Gil Tene bu arızaya coordinated omission adını verdi: ölçen sistem, test edilen sistemle aykırı değerleri ölçmekten kaçınacak şekilde uyum kurar; çünkü kapalı döngü yanıtı bekler ve takılma sırasında göndermeyi durdurur. ScyllaDB 2021'de bir karşılaştırma yayımladı: aynı iş yükü kapalı döngüde 249 mikrosaniyelik bir p99 bildirdi, düzeltmeli açık yük altında ise 665 ms - yaklaşık 2,700 katlık bir sapma.
Yanıt bekleyen bir yük üreteci, tam da bulmak için kurulduğu takılma sırasında göndermeyi durdurur ve sonra bu sessizliği sonuca ortalar. Ardından yazdırdığı yüzdelik, sizin bidder'ınızı değil üretecin kendisini tarif eder.
Ayar yapmadan önce durma sayısını adlandırın; çünkü yorgunlukla verilen bir yeniden yazma kararı, karar değildir. Bir değil iki rakam: assist'i besleyen istek başına heap ayırmaları ve marking'i besleyen canlı heap.
Sıra, en büyük kazanç değil, önce kaynak sonra tavan şeklindedir. Assist'in üretildiği yerden başlayın: teklif yolunda escape analysis, ayrılmak yerine yeniden kullanılan buffer'lar ve yepyeni bir nesne grafiği oluşturmak yerine yalnızca ihtiyacınız olan alanları okuyan bir codec. Slice'ları kısa ömürlü tutun: istek buffer'ına açılan bir slice, teklif yaşadığı sürece o buffer'ın tamamını tutar.
Bu yaklaşımın bir tavanı var: Uber 2021'de, GOGC'yi konteyner bellek limitine karşı ayarlamanın kritik önemdeki servisleri genelinde yaklaşık 70,000 çekirdek kazandırdığını bildirdi. Bu bir maliyet sonucudur, bir yüzdelik sonucu değil.
RTB House, Haziran 2025'te bir JVM teklif servisini anlattı: mikroservislere bölünme çok sayıda küçük istek üretti; eklenen gecikmenin, ortalama 2.5 ms'lik bir isteğe karşı 7 ms içinde kalması gerekiyordu ve 98. ile 99. yüzdelikler sık G1 duraklamaları altında bozuldu. Generational ZGC'ye geçtiler ve bedelini bellekle ödediler.
Bu onarımın neyi gerektirdiğine dikkat edin: geçilecek ikinci bir collector. Go tek bir collector ile gelir ve bu collector takılıp çıkarılabilir değildir; dolayısıyla Go'daki kollar ayırma hızı, canlı kümenin şekli ve GOMEMLIMIT'e karşı GOGC'dir. Bir Rust hot path'inin kaldırdığı şeyler ise nettir: assist yok, arka plan marking'i yok, zorunlu döngü yok. Ortadan kalkmayanların listesi ise çoğu ekibin beklediğinden uzundur:
Kuyruk; isteğin ayrıştırılmasında, exchange'e giden bağlantıda ya da scheduler kuyruğunda yaşıyorsa Rust bu milisaniyelerin hiçbirini geri getirmez ve yanlış bileşeni yeniden yazmak, aynı zaman aşımı oranını korumak için bir çeyrek dönemi harcamaktır.
Bu yüzden servisi değil, ayırmaların sahibi olan en küçük parçayı taşıyın: gösterim değerlendirme döngüsü ve indeksleri, aday seçimi, hedefleme, frekans ve bütçe sorguları, puanlama. Sınırın bedelini geçiş başına hesaplayın: bid request başına bir kez, düz bir buffer ile; asla hedefleme kuralı başına bir kez değil. Doğrulamayı, aynalanmış bir akışla beslenen ayrı örneklerde yapın; asla test edilen sürecin içinde değil - orada bir gölge yol, ölçtüğünüz iki büyüklüğü de ikiye katlar.
Her iki yolda da işe yarayan bir kabul testi: kısa bir pencerede, kendi belirlediğiniz bir bellek tavanı altında collector kapalı koşun ve p99.9'u handler'ın içinde kaydedin. Bunu, trafiğin bir kısmını alan tek bir örnekte otomatik geri alma ile çalıştırın; çünkü GOGC kapalıyken tavana çarpan bir heap sıçraması runtime'ı arka arkaya döngülere sokar ve rehber bu takılmanın süresiz olabileceğini söyler. Sonucu yalnızca GC CPU sınırlayıcısı hiç devreye girmediyse okuyun: devreye girdiği anda yüzdelik, sınırlayıcıyı tarif eder.
Sorunun ikinci yarısının - bu işi kim yapar - bir satıcı listesine ihtiyaç duymayan bir testi var. GC kaynaklı latency'yi onaran bir firma, yeniden yazım satan bir firmadan farklı davranır:
Hiçbiri güven gerektirmez: her biri ilk görüşmede isteyebileceğiniz bir belgedir ve muğlak dönen bir yanıt, teşhisin atlandığını söyler.
Yani ilk soru hangi dil değildir. Soru, zaman aşımına uğrayan bağlantıda eksik milisaniyelerin üç toplamdan - handler, scheduler ya da hat - hangisine ait olduğu ve siz üzerine gittiğinizde bid request başına ayrılan baytın kıpırdayıp kıpırdamadığıdır.
amBrain'in kamuya kanıtlayabildikleri: amBrain; trading platformları, matching engine'ler, real-time bidding sistemleri ve casino platformu mühendisliği alanlarında uzmanlaşmış bir yazılım geliştirme şirketidir, 2019'dan beri yazılım geliştiriyoruz ve AdTech'te kurduğumuz şey DSP geliştirme, real-time bidding platformları ve ad exchange mühendisliğidir. Üç formatta çalışıyoruz: tam teslim, özel ekip ya da sizin ekibinize yerleşen mühendisler. Bir yeniden yazımı bir ayar turuna karşı tartıyorsanız, konuşmaya değer görüşme, bir dil seçmeden önce ayrımı yapan görüşmedir.
Mevcut mimarinizi ve sizi endişelendiren hata senaryosunu getirin; yarım saatte birlikte üzerinden geçelim.