Bir salı sabahı 09:20'de growth ekibinden mesaj geldi: ana sayfadaki "sana özel" öneri bloğunun tıklanma oranı bir gecede %18 düşmüştü. İlk soru her zamanki soruydu: "Dün akşam deploy yapan var mı?" Yoktu. Model aynı modeldi, servisler ayaktaydı, hata oranları sıfırdı; bütün sağlık kontrolleri yeşildi. Ama Kafka tarafındaki bir panelde kimsenin alarm bağlamadığı bir çizgi sessizce tırmanıyordu: kullanıcı davranış olaylarını işleyen consumer grubunun lag'i 40 milyon mesajı geçmişti. Öneri modelimiz çalışıyordu; sadece kullanıcıların dokuz saat önceki halini görüyordu. Gece telefon kılıfına bakan kullanıcıya sabah hâlâ üç gün önceki merakını öneriyorduk. Vitrindeki yapay zekânın görünmez kısmı tam olarak budur: model değil, modeli besleyen kan dolaşımı.
İki Kule, On Dört Milyon Ürün
Öneri sistemimizin çekirdeği two-tower mimarisi: bir kule kullanıcıyı, diğer kule ürünü aynı embedding uzayında birer vektöre dönüştürüyor ve yakınlık, ilgiyi temsil ediyor. Ürün kulesinin çıktıları her gece 14 milyon ürün için hesaplanıp HNSW tabanlı bir yaklaşık komşu index'ine yazılıyor; kullanıcı vektörü ise istek anında, son gezinme olaylarıyla güncellenmiş özelliklerden üretiliyor. Bir ana sayfa isteğinde akış şöyle: kullanıcı vektörü hesaplanır, ANN index'ten 500 aday çekilir, adaylar iş kurallarından geçer (stok, satıcı skoru, çeşitlilik) ve daha ağır bir ranking modeli ilk 40'ı sıralar. Bütün bunlar için bütçe 80 milisaniye. Bu bütçenin en öğretici sonucu şu oldu: model seçimlerimizi doğruluk sıralaması değil, milisaniye başına doğruluk sıralaması belirliyor. Offline metriği %2 daha iyi olan bir ranking modelini, 30 milisaniye pahalı olduğu için canlıya hiç almadık; o 30 milisaniyeyi aday sayısını artırmaya harcamak A/B testinde daha çok GMV getirdi.
Serving tarafı da kendi başına bir mühendislik alanı. Ranking modeli ONNX'e derlenip Kubernetes üzerinde ayrı bir inference servisinde koşuyor; pod'lar CPU'da çalışıyor çünkü bizim model boyutumuzda GPU'nun maliyeti, kazandırdığı milisaniyeyi haklı çıkarmıyor. Akşam tepesinde bu servis saniyede 6 bin skorlama isteği alıyor ve HPA'nın ölçek sinyali CPU değil, p99 gecikme. Soğuk başlayan pod'un ilk isteklerde yavaş kalması gibi sıkıcı detaylar bile A/B metriklerini kirletebiliyor; model dosyasını önceden ısıtan bir readiness kontrolü eklemek, tek başına p99'daki deploy sonrası dikenleri sildi.
Bayat Özelliklerle Skorlamak
Salı sabahki vakaya dönersek: sorun feature tazeliğiydi. Online feature store'umuz Redis üzerinde yaşıyor; kullanıcının son baktığı kategoriler, son aramaları, fiyat hassasiyeti gibi yüzlerce özellik, clickstream olaylarını işleyen stream job'ları tarafından saniyeler içinde güncelleniyor. O gece bir şema değişikliği yüzünden deserializasyon hatası alan consumer, mesajları zehirli kuyruğa atmak yerine sonsuz retry'a girmiş ve lag birikmişti. Modelin kendisi tek satır değişmeden, sistemin kalitesi çökmüştü. Bu olaydan sonra iki şey kurduk: her kritik özellik grubuna "son güncellenme yaşı" metriği ve bu yaş üzerinden SLO tabanlı alarm; bir de skorlama servisine tazelik farkındalığı. Özellikler belli bir yaşın üstündeyse servis kişiselleştirmeden vazgeçip popülerlik tabanlı fallback listesine dönüyor. Bayat kişiselleştirme, kişiselleştirmemekten kötüdür; bunu CTR kaybıyla ölçerek öğrendik.
Tazelikle akraba bir başka dert cold start. Yeni üye olmuş kullanıcının davranış geçmişi yok, dün eklenen ürünün tıklama sinyali yok; two-tower bu ikisine karşı doğal olarak kör. Kullanıcı tarafında ilk oturumdaki birkaç tıklamayı anında vektöre çeviren bir oturum-içi model devreye giriyor; üç-dört tıklamadan sonra öneriler gözle görülür kişiselleşiyor ve bunun ilk oturum dönüşümüne etkisi tek başına ölçülebilir çıktı. Ürün tarafında ise içerik tabanlı köprü kuruyoruz: başlık, kategori ve görselden üretilen embedding, davranış sinyali birikene kadar ürünün vekil vektörü olarak iş görüyor. Pazaryeri tarafında her gün on binlerce yeni ürün listelendiği için bu vekil mekanizması süs değil, zorunluluk.
Yüz Yirmi Milisaniyede Dolandırıcıyı Tanımak
Aynı görünmez altyapının öbür ucunda fraud var. Checkout anında her ödeme isteği, karar için 120 milisaniyelik bir pencereye sahip: gradient boosting tabanlı bir skor modeli artı deterministik bir kural motoru. Modelin gördüğü özelliklerin en değerlileri tekil değil ilişkiseldir: bu kart son bir saatte kaç farklı hesapta denendi, bu cihaz parmak izi daha önce hangi adreslerle eşleşti, bu adrese giden siparişlerin iade oranı ne? Bu ilişki özellikleri bir graph üzerinden hesaplanıp yine online store'a yazılıyor. Bir gece kural motoru tek başına şunu yakaladı: 62 farklı "yeni" hesap, üç cihaz parmak izinde toplanıyor ve hepsi yüksek fiyatlı elektroniği aynı mahalledeki farklı adreslere sipariş ediyordu. Model her hesabı tek tek şüpheli bulmuyordu; hesaplar tek tek gerçekten masum görünüyordu. Dolandırıcılığı bireyler değil, bağlantılar ele verdi. O operasyonun engellediği tutar, fraud ekibinin o çeyrekteki maliyetinin tamamını tek gecede amorti etti.
Fraud tarafında eşik seçimi saf bir ML kararı da değil: false positive, gerçek bir müşterinin ödemesini reddetmek demek ve bunun da bir GMV ve güven maliyeti var. Eşiği, engellenen fraud tutarı ile reddedilen meşru sipariş tutarını aynı grafiğe koyarak, iş tarafıyla birlikte ayarlıyoruz; ayda bir bu eğri yeniden çizilir çünkü dolandırıcılar da bizi A/B test ediyor. Modeli üç ayda bir değil, drift metrikleri tetikledikçe yeniden eğitiyoruz. Drift'i iki katmanda izliyoruz: girdi dağılımındaki kayma (yeni bir cihaz tipi, yeni bir ödeme yöntemi dalgası) ve skor dağılımındaki kayma. İkisi de sessizdir; hata fırlatmaz, sadece kararlarınızı yavaş yavaş anlamsızlaştırır. Bir keresinde skor dağılımındaki kaymayı, yeni açılan bir kampanya mekaniğinin meşru ama alışılmadık sipariş desenleri üretmesine kadar iz sürdük; model kampanya müşterilerini toplu halde şüpheli bulmaya başlamıştı. Kampanya takvimini feature olarak modele vermek gibi cilasız bir çözüm, o çeyreğin en yüksek getirili ML işi oldu.
Modelden Daha Zor Olan Şey
Dışarıdan bakınca platformdaki yapay zekâ, vitrindeki "sana özel" başlığından ibaret görünüyor. İçeriden bakınca tablo şu: model eğitimi bu işin belki beşte biri; kalanı feature pipeline'ların tazeliği, milisaniye bütçeleri, fallback zincirleri, drift izleme ve offline metrikle online gerçeği bağlayan deney altyapısı. O deney altyapısının kendisi de görünmez kahramanlardan: öneri tarafında aynı anda tipik olarak beş-altı deney koşuyor ve deneyler kullanıcı bazında tutarlı bucket'lara dağıtılmazsa, iki deneyin etkileşimi metrikleri okunamaz hale getiriyor. Salı sabahı vakasında bize dokuz saati kaybettiren şey neydi diye sorarsanız: alarmın olmaması. Bugün aynı arıza dört dakikada page üretiyor, çünkü artık modeli değil, modelin gördüğü dünyanın yaşını izliyoruz. Bugün öneri sistemleri ana sayfa kaynaklı GMV'nin dörtte birinden fazlasını taşıyor, fraud katmanı her gün binlerce işlemi sessizce eliyor ve bu iki sistemin de en kritik bileşeni bir sinir ağı değil, Kafka consumer lag panelindeki o mütevazı çizgi. Ekibinize bir ML mühendisi almadan önce feature store'unuzun ve olay akışınızın sağlığını izlemeye başlayın; sıralamayı ters kuranların hikâyeleri hep aynı salı sabahına çıkıyor. Bu görünmez altyapı meselelerini tartışmayı severim; iletişim sayfası açık.