<

Kurye Atama Probleminin İçyüzü: 90 Saniyede Bir Kararlar

Yağmurlu bir cuma akşamıydı; yemek tarafında sipariş hacmi olağan cumanın 2,3 katına çıkmıştı. Operasyon merkezindeki ekranda p90 teslimat süresi 34 dakikadan 61 dakikaya tırmanıyordu ve dashboard'da tuhaf bir çelişki vardı: aktif kurye sayımız yeterliydi, hatta boşta görünen kurye bile vardı, ama siparişler birikiyor, müşteri iptalleri artıyordu. Harita görünümüne geçince asıl garipliği gördük: kuryelerin ciddi bir bölümü hareket etmiyordu. Restoran kapılarında bekliyorlardı. O gece, iki yıldır işlettiğimiz dispatch sisteminin en öğretici arızasını yaşadık ve bu yazıda o sistemin içini, o gece öğrendiklerimizle birlikte açacağım.

Doksan Saniyelik Nefes

Önce mimariden başlayayım. Kurye atamayı sipariş geldiği anda, anlık ve açgözlü yapmıyoruz; siparişleri 90 saniyelik pencerelerde biriktirip her pencerenin sonunda toplu çözüyoruz. Bu sürenin kendisi bir mühendislik kararı ve iki kuvvetin dengesi: pencere uzadıkça elinizde daha çok sipariş ve daha çok kurye seçeneği olur, eşleştirme kalitesi ölçülebilir biçimde artar; ama her saniye bekleyen sipariş yaşlanır. A/B testlerinde 30 saniyelik pencere anlık atamaya göre ortalama teslimatı 1,9 dakika, 90 saniyelik pencere 3,4 dakika iyileştirdi; 150 saniyede kazanç plato yaptı, gecikme maliyeti baskın çıktı. 90, bir yuvarlak sayı değil, deney sonucudur. Pencere içinde de istisnalar var: VIP restoranlar ve süresi kritik siparişler (mesela zaten geciken bir yeniden atama) pencereyi beklemeden anlık atanabiliyor; kural motoru bu istisnaları toplu çözücünün önünde koşuyor.

Her döngüde şehir ölçeğinde problem çözülmüyor; harita, sınırları sipariş yoğunluğuna göre haftalık güncellenen bölgelere ayrılmış durumda. Yoğun bir akşam döngüsünde tipik bir bölgede 40 civarı bekleyen sipariş ve 120 civarı aday kurye olur. Maliyet matrisi kurulur: her sipariş-kurye çifti için tahmini toplam teslimat süresi, kuryenin mevcut rotasına ekleme maliyeti, restoranın tahmini hazırlık bitişiyle kuryenin varışı arasındaki senkron farkı ve dengeli iş dağılımı için bir adalet terimi. Bu ölçekte atama problemini Hungarian algoritmasının varyantıyla çözmek milisaniyeler alıyor; bizim gerçek bütçemiz 800 milisaniye ve onu da çoğunlukla matrisin kendisini doldurmak, yani binlerce ETA tahmini üretmek yiyor. Optimizasyon literatürünün size söylemediği şey budur: assignment problem'in zor kısmı çözücü değil, maliyet fonksiyonunun içindeki tahminlerdir.

O tahminlerin belkemiği ETA modeli. Kuryenin siparişe varış süresi kuş uçuşu mesafeyle hesaplanamaz; araç tipi (motor, bisiklet, yaya), saat, tek yönlü sokaklar, hatta bina girişinin arka sokakta olması dakikalar oynatır. Bizim ETA modelimiz yol ağı üzerindeki rota süresinin üstüne, tarihsel gerçekleşmelerden öğrenilmiş bir düzeltme katmanı bindirir: bu bölgede, bu saatte, bu araçla, rota motorunun dediğinin tipik olarak kaç dakika üstünde gerçekleşiyor. Maliyet matrisindeki her hücre bu modelden geçer; döngü başına on binlerce tahmin, cache'lenmiş bölge-saat düzeltmeleriyle 800 milisaniyelik bütçeye sığar. Ve her tamamlanan teslimat, tahmin-gerçekleşme çifti olarak geri akar; dispatch sistemi kendi hatasını her gün yeniden öğrenen bir organizmadır, ya da öyle kalmalıdır.

Yanlış Teşhis: Kurye Azlığı

O cuma gecesi ilk teşhisimiz reflekstendi: "Arz yetmiyor." Yağmurda talep artar, kurye çıkma isteği düşer; klasik denklem. Anında dinamik prim açtık, çevre bölgelerden kurye kaydırdık. Yarım saat sonra tablo neredeyse hiç düzelmemişti. Rakamlar da hipotezi desteklemiyordu aslında: kurye başına saatlik tamamlanan sipariş 1,9'dan 1,1'e düşmüştü. Arz sorunu olsaydı kuryeler daha çok koşar, verim sabit kalırdı; bizde kurye başına verim çökmüştü. Kuryeler çalışıyordu ama zamanları bir yerde buharlaşıyordu.

Restoran Mutfağındaki Kör Nokta

Buharlaşmanın yeri, kurye uygulamasındaki durum geçişi loglarındaydı: "restorana vardı" ile "siparişi aldı" arasındaki süre normal akşamlarda ortalama 3,5 dakikayken o gece 12 dakikaya çıkmıştı. Dispatch sistemimiz kuryeyi, hazırlık süresi tahmin modelinin verdiği bitiş zamanına göre yola çıkarıyor. O model geçmiş verilerle eğitilmişti ve restoranın o anki yoğunluğunu zayıf bir sinyalle görüyordu. Yağmurlu gecede restoranlar da bizimle aynı talep şokunu yaşıyordu: mutfaklar dolmuş, gerçek hazırlık süreleri tahminin neredeyse iki katına çıkmıştı. Model "18 dakikada hazır" diyor, kurye 18. dakikada kapıda bitiyor, yemek 29. dakikada çıkıyordu. Aradaki 11 dakika boyunca kurye, sistemin gözünde meşgul, gerçekte ise bir kapı önünde kilitliydi. Yani kapasiteyi eriten şey kurye sayısı değil, kötü bir tahminin kurye-saatleri rehin almasıydı. Dispatch sistemlerinde en pahalı hata yanlış eşleştirme değildir; doğru eşleştirmeyi yanlış zamanda tetiklemektir.

Tahmini Tamir Etmek, Sonra Ona Güvenmemek

Çözüm iki koldan ilerledi. Birincisi modelin kendisi: hazırlık süresi tahminine restoranın son 30 dakikadaki kabul ettiği sipariş adedi, mutfaktaki bekleyen sipariş kuyruğu ve hava durumu gibi anlık sinyaller eklendi; model artık günlük değil, özellikleri gerçek zamanlı beslenerek çalışıyor. Tek başına bu, yoğun saat tahmin hatasını (MAE) 6,8 dakikadan 2,9 dakikaya indirdi. İkinci kol daha az parlak ama bence daha önemli: sisteme tahmine güvenmeme yeteneği kazandırdık. Tahmin belirsizliği yüksekse dispatch kuryeyi erken göndermek yerine atamayı bir-iki döngü erteleyebiliyor; bir restoranda gerçekleşen bekleme süreleri tahmini sistematik aşmaya başlarsa o restorana giden atamalar otomatik gecikiyor ve restoran paneline "mutfak yoğun" sinyali düşüyor. Ayrıca aynı restorandan yakın adreslere giden siparişleri tek kuryede birleştiren bundling mantığının eşiği, yoğunlukta gevşiyor: bekleme zaten olacaksa, o beklemeye ikinci bir siparişi bindirmek neredeyse bedava kapasitedir.

Bu değişikliklerin hiçbirini doğrudan canlıda denemedik; dispatch tarafındaki en değerli yatırımımız simülatör. Geçmiş günlerin bütün sipariş, kurye konumu ve restoran olaylarını kaydediyoruz ve herhangi bir algoritma değişikliğini, mesela o yağmurlu cumanın verisi üzerinde, saatler süren günü dakikalara sıkıştırarak yeniden oynatabiliyoruz. Yeni hazırlık modeli ve erteleme mantığı önce simülatörde o geceyi "yeniden yaşadı": p90 teslimat simülasyonda 61 dakikadan 44'e indi, canlıdaki 41 ile arasındaki fark da simülatörün kalibrasyonuna dair ayrıca ders oldu. Simülatörsüz dispatch geliştirmek, körlemesine ilaç dozu değiştirmeye benziyor; hastanız her akşam yüz binlerce sipariş.

Bir de denklemin insan tarafı var. Maliyet fonksiyonundaki adalet terimini süs olsun diye eklemedik: saf verimlilik optimizasyonu, en hızlı ve en merkezi kuryelere sipariş yığar; kenar mahalledeki kurye akşamı iki paketle kapatır ve ertesi hafta platformdan çıkar. Kurye başına saatlik kazanç dağılımının varyansını da izliyoruz, çünkü bugün feda edilen adalet, üç ay sonra arz krizi olarak faturalanıyor. Optimizasyon fonksiyonunuza kimin gelirini yazdığınız, bir mühendislik kararı olduğu kadar bir işletme kararıdır.

Bir sonraki büyük yağmurda p90 teslimat 61 değil 41 dakikada tepe yaptı; kapı önü beklemesi ortalaması 4 dakikanın altında kaldı, kurye başına saatlik teslimat yoğun saatte 1,7'ye tutundu. Mükemmel değil; yağmur hâlâ vergisini alıyor, ama artık verginin kalemlerini biliyoruz. Bu işten payıma düşen ders şu oldu: optimizasyon sistemleri, en zayıf tahminleri kadar iyidir ve iyi bir sistem kendi tahminlerinin ne zaman yalan söylediğini fark edebilmelidir. Benzer bir dispatch, saha operasyonu ya da atama problemi üzerinde çalışıyorsanız iletişim sayfasından ulaşın; kapı önünde bekleyen kuryelerin hikâyelerini karşılaştırmak bile başlı başına öğreticidir.

📅 Yayınlanma:  ·  Yakup Zengin