Kıtasal bir sevkiyat motorunu çevrimdışı bırakmadan yeniden inşa etmek.
Sekiz bin sürücü, 220 sevk memuru, on dört yıllık birikmiş mantık. Yük taşınmaya devam ederken operasyonun beynini değiştirdik.
01Sorun
Northwind’in sevkiyat sistemi 2011’de yazılmıştı; üç kez yerinde göç ettirilmiş bir Microsoft SQL Server şemasına dayanıyordu. 2024’e geldiğimizde 1,3 milyar dolarlık bir cironun tek arıza noktasıydı. Bir sevk memurunun işlemini çözmesi ortalama 1,4 saniye sürüyordu; sabah yoğunluğunda, ET 05:30–07:00 arası, bu süre dokuz saniyenin üstüne çıkıyor ve sistem sürücü tabletlerine değişiklikleri sessizce iletmeyi bırakıyordu.
On sekiz ay önceki yeniden yazma denemesi, bir tedarikçinin Northwind tarafında kimsenin işletmek konusunda kendine güvenmediği Kubernetes biçiminde bir ürün teslim etmesinin ardından öldürülmüştü. Yönetimin içgüdüsü, bizce haklı olarak, “sıfırdan yeniden inşa” gibi görünen her şeye son derece şüpheyle yaklaşmaktı. Bir kısıt üzerinde anlaştık: proje boyunca sevkiyat sistemi otuz saniyeden fazla kullanılamaz hâlde kalamazdı.
02Mimari ve kararlar
Sevkiyat alanını, operasyon ekibinin dilinde zaten var olan dikişler boyunca üç servise böldük: yük planlama (yükü bir çekiciye atama), rotalama (durakların gerçek sırasını hesaplama) ve yürütme (tabletlerden ve ELD’lerden gelen olay akışı). Her biri kendi Postgres örneğini ve tek bir Rust süreci aldı. Her şey Kafka üzerinden, küçük ve elle yazılmış bir şema kayıt defteri ile konuşuyor — Protobuf ve Avro denedik, sonunda olaylar sırasında grep ile kolayca aranabilmesi için sürümlenmiş bir JSON sözleşmesini tercih ettik.
Göç dokuz ay boyunca gölge modda çalıştı. Yazma işlemleri hem eski hem yeni sisteme gidiyor, okumalar eskiden geliyor, yeni sistemin çıktısı eskinin çıktısıyla sürekli karşılaştırılıyordu. Eski kodun on dört yılda biriktirdiği yaklaşık kırk davranışsal tuhaflığı yakaladık — kimsenin açıklayamadığı kurallar — ve sevk memurlarının bağımlı olduklarını yeniden ürettik, kimsenin gerekçelendiremediklerini ise kapattık. Geçişin kendisi bir pazar günü ET 04:00’te yayımlanan üç satırlık bir yapılandırma değişikliğiydi.
Savunacağımız ödün: Servis başına Postgres’i koruduk ve dağıtık işlemlerden tamamen kaçındık. Rotalamadaki her durum değişikliği, yük planlamanın Kafka’dan tüketip değişmez biçimde uyguladığı bir olay yayar — bir rota sonradan iptal edilirse telafi edici düzeltme dahil. Bu, sevk memurunun ekranında yaklaşık kırk milisaniyelik nihai tutarlılık bedeline mal oluyor. Operasyon ekibiyle bunun kabul edilebilir olduğunu test ettik; öyle.
Daha erken yapmış olmayı istediğimiz şey: Eskiyle karşılaştırma kablajını altıncı haftada değil, birinci haftada yazmak. Geçiş sırasında herkesi sakin tutan tek araçtı.
// load_planning::apply_route_event pub async fn apply(evt: RouteEvent, db: &Pg) -> Result<()> { // natural key: (load_id, sequence_no) is unique in route_assignments let existing = db.fetch_one( /* sql */ r#"SELECT applied_at FROM route_assignments WHERE load_id = $1 AND sequence_no = $2"#, &[&evt.load_id, &evt.sequence_no], ).await.optional()?; if existing.is_some() { return Ok(()); } // already applied -- safe replay db.execute(/* sql */ r#" INSERT INTO route_assignments (load_id, sequence_no, route, applied_at) VALUES ($1, $2, $3, now()) "#, &[&evt.load_id, &evt.sequence_no, &evt.route]).await?; audit::record(&evt).await?; Ok(()) }
03Ürün
04Sonuç
On iki ay sonra sistem, Northwind tarafında dört mühendis tarafından işletiliyor — önceki monolit on bir kişi tutuyordu. Çeyrek başına sayfalama olayıyla ölçülen nöbet yükü üçte iki azaldı. Hâlâ mimari incelemesi için küçük bir aylık ödemedeyiz; kalan her şey onların işletmesi.