Hepsiexpress — Teslimat Takip Projesi
Bu yazımızda sizinle bir Hepsiburada girişimi olan Hepsiexpress’in saha kurye takip uygulamasının yeniden yazılma aşamasını, bu yolda yaşanan zorlukları, alınan mimari kararları, neden bunların tercih edildiği, nelerin denendiği ve başarılı olduğu gibi Hepsiexpress’in sahayı yeni uygulamasında nasıl kontrol altına aldığı hakkında sizlere merak uyandıracak. Bu konu üzerine uygulama geliştirecek arkadaşlarımıza ilham ve örnek olacak bir yazı derledim.
Sorun Neydi?
Sürecin başından başlarsak eğer öncelikle eski takip uygulamamızdan başlamamız gerekmekte. Eski uygulamamız zamanında performans metriklerini karşılayabilen fakat Hepsiexpress’in büyümesi saha üzerinde veri akışının artması-hızlanması ile uygulama artık yetersiz hale gelmiş olup gecikmeler kasılmalar kullanım sırasında kilitlenmesi vs. sorunlar ile artık buranın sonunun geldiği anlaşılmış olup yeni bir projenin yazılması ve buranın kademeli olarak hayatımızıdan çıkarılması gerektiğine karar alındı.
Ne Kullanacağız Ki Biz?
İlk sorumuz şu oldu biz bu projeyi ne ile yazmalıyız? Çok basit gibi duran ama çok büyük bir soru, öncelikle tabii ki yazılım dünyasının 2 devini doğrudan listeye aldık React ve Vue fakat bunun yanında bunlarla ilgili sorularımız da fazlaydı acaba bu işin altından kalkabilirler mi? Saha üzerinde çok fazla kuryemiz var bunun yanında tüm kuryeler haritaya dökülecek ve her birinin anlık konumları sürekli olarak Web Socket ile dinlenecek ve bu saha üzerinde tüm bu verilerin anlık yenilenmesi demek.
Ufak bir matematik yaparsak 2.000 aktif kurye X kurye başına dakikada 5 güncelleme verisi = 10.000 veri güncellemesi yani bir bakıma render işlemi, inanılmaz bir yük değil mi? (sayılar örnek açısından minimal tutuldu gerçek sonuç ve sayıların bundan çok daha yüksek olduğunu düşünebilirsiniz)
Tüm bu yükü düşündüğümüz zaman aklımıza acaba daha hafif teknolojilere mi yönelsek diye bir düşünce oluştu ve Svelte üzerine bir araştırma yapmaya başladık. Doğruyu söylemek gerekirse Svelte bayağı bir aklımıza yattı fakat düşünmemiz gereken bir diğer sorun ise zaman 😊. Bilirsiniz ki E-Ticaret sektörü dünyadaki en hızlı ve dinamik sektörlerin başında gelir yani vaktimiz çok azdı. Çok az sürede harika işler çıkmalıydı. Svelte ile kodlama yapmak gerçekten rahat çok daha az kod ile çok iş yapılabiliyor fakat bu aynı zamanda bir sorun. Projemiz bayağı bir detay barındıracak ve acaba ileride Svelte bize yetersiz gelir mi? Topluluk desteği çok güçlü değil takıldığımız sorunlarda Ar-Ge için ne kadar zaman harcarız? Bu ve bunun gibi sorular ile bu işi yapabileceğimizden ve yetkinliğimizden emin olduğumuz asıl teknolojilerimize geri dönmeye karar verdik.
React,
Hooks,
Redux.
— Backend ve BFF tarafında ise zaten teknoloji seçimimizi belirlemiştik.
Go,
Node Js,
MongoDB.
—İmplementasyon detayları ile uğraşmamak adına ise Socket Server olarak.
Socket IO
— Projemizin mimari şeması ise;
Verilerimizi Nasıl Tutacağız?
Projedeki ilk soru işaretimiz verilerimizi nasıl tutacağız? Array mi Json Object mi? Bu konuda uzun bir araştırmaya girdik ne seçersek bize ne faydası olur, ne zararı olur. Bunları size kısaca aktaracağım ama öncelikle şunu söylemeliyim bizim asıl odağımız performans.
Verilerimiz bir Array’de tutmak bize çalışırken çokça konfor sağlar sonuçta harita üzerinde dökmemiz gereken binlerce veri ve bunları oluşturacak Markerler gerekli. Bir dizi üzerinden render işlemlerini çok rahat bir şekilde yapabilirdik fakat bu yolun performansı bizi zora sokabilirdi neden derseniz şöyle açıklayabilirim;
Bir diziyi dönüp tüm Marker’ları oluşturmak evet çok kolay fakat bizim bir entegre noktamız var. Web Socket, Socket IO üzerinden akan anlık veriler sürekli olarak state üzerinde güncelleme gerektirmekte. Verimiz Socket IO üzerinden Redux’a ulaşır ve Redux güncellenmesi gereken objeyi ve nesnesini günceller ama burada bir sorun var güncellenecek obje bize geldi fakat bunu array içerisinde güncellemek için Array’i dönmeli her bir index için koşul kontrolü yapılmalı, doğru index üzerine ulaşmalı sonra bunu güncellemeliyiz ve bunu dakikada 10.000 kez yapacağız. İçerisinde binlerce kuryeyi tuttuğumuz diziyi her bir kuryenin lokasyon güncellemesi geldiğinde dönüp güncelleyeceğiz ve dizi içerisinde arama maliyetimiz çok uçuk noktalara gelecekti. Bu noktada araştırmamız Json Object üzerine yöneldi.
Yaptığımız bir testten bahsetmem gerekirse 1.000.000 veri içeren bir dizide aranan veriyi bulmak ortalama 12.85 ms sürerken Json Object içerisinde bu veriyi aramak(key ile) sadece 0.06 ms sürmekte. En iyi senaryoda ise yani aranan eleman dizinin başında başında ise Array ortalama 2.97 ms, Json Obj 0.05 ms ile veriye ulaşmakta. Görüldüğü üzere Json Object kullanırsak veriye ulaşmada ve onu anlık olarak güncellemekte çok büyük bir hız ve stabilite kazanacağız. Bizim tercihimiz de tabiki verilerimizi Json Object olarak tutmaktan yana oldu ve böylece Redux State yapımıza karar vermiş olduk.
Socket IO Hakkında
Socket IO hakkında ilk sorunumuz paketin kendisi oldu. Npm kuruldu socket bağlantıları, ayarlar, test socketleri her şey hazırdı bağlantı açık ve veriler de düzenli geliyor gözüküyordu fakat kod üzerinde verileri bir türlü toplayamıyorduk veri yokmuş gibi davranıyordu. Farklı yöntemler denedik, yayında mı sorun var diye araştırdık fakat hiç bir sorun yoktu bu aşamadan sonra sorunun paketin kendisinden olabileceği aklımıza geldi ve son sürüm yerine daha alt ve kullanıcısı çok olan sürümlerden birine geçiş yaptık ve tüm akışımız doğru şekilde çalışır hale geldi, özetle her şeyin son sürümü de iyi değildir 😊.
Az öncede bahsettiğimiz gibi Socket IO yapımızdan bize kuryelerin lokasyonları, sipariş durumları, yeni siparişler, biten siparişin durumu vs gibi anlık takibi önemli olan veriler aktarılmakta. Socket üzerinden gelen her veri Redux’a ulaşacak ve burada State üzerinde durum güncellemesi olacak, State güncellendiği anda ise harita üzerinde render gerçekleşecek.
Bu senaryoda bir aksaklık mevcut gelen verilerin gelir gelmez bunu sağlaması performansı dibe çekmekte ve ekranda kilitlenmelere yol açmaktaydı. Peki ne yaptık? Socket verilerini toplayan ufak bir Cache mekanizması kurduk. Gelen verileri anlık olarak Redux’a atmak büyük yük fakat verileri bekletip belirli aralıklar ile atmak ise yükü büyük ölçüde azalatacak bir yöntem. Gelen yeni verileri sürekli olarak bir değişkende sakladık ve bunları belirli zaman aralıkları ile belirli miktarlar olarak gönderdik (100ms aralıklar ile 200 veri gibi) ve gönderdiğimiz veriyi Cache’den sildik. Bunun yanında harita görünümü uzaklaştırıldıkça da aktarım süresini uzattık (100ms, 500ms, 1sn, 5sn…). Ms bazında süreler insan gözü için önemli süreler değil fakat React’ın performansı açısından büyük bir değişim.
Bu yöntem ile dakikada 10.000 Redux isteği ve 10.000 Render almak yerine dakikada 600 istek atarak 10.000 render almaya başladık. Bu yöntem bizim için performans sorununu çok büyük oranda çözdü, aslında işimizi görecek haldeydi fakat biz tabii ki en iyisini istiyoruz 😊. Bu noktada ise son iyileştirmemiz Redux Batch kullanmak oldu.
Batch’i kısaca anlatmak gerekirse Redux üzerinde güncelleme yapılacak state’e 10 değişikliği 10 yenileme olarak değil, 10 değişikliği 1 yenileme olarak yaptıracak ve bu da performansı artık maksimize edecek. Batch’i Socket IO içerisinde aktif hale getirdik Cache olarak biriktirdiğimiz dataları Interval kullanmaya devam ederek Redux’a Batch aracılığı ile iletmeye başladık. Bu yöntem bize dakikada 600 istek 10.000 render yerine 600 istek 50 render (10.000/200) ile veri aktarabilir hale geldik. İlk duruma oranla çok büyük bir fark bu sayede artık performans sıkıntımız hiçbir şekilde kalmadı ne şimdi ne gelecekte.
Yaşanan bir diğer sorun ise Socket yapısının ara ara kapanıp tekrar açılmasıydı, bu durum bize veri kaybı yaşatabilecek bir sorundu ve buna hızlı bir çözüm getirmeliydik. Socket yapısını ve kodları incelediğimizde her iki tarafta da aslında bir sorun yoktu her şey olağandı fakat sonrasında fark ettik ki uzun süre bağlantı sağlayan socket, timeout gibi belirli aralıklarla bağlantıyı kesiyordu. Neden, neden diye araştırırken aslında sorun çok basitti Socket bize aslında hala orada mısın demek istiyormuş, timeout sürelerini düzenleyince sorun çözüldü.
API Gateway
Projenin ulaşması gereken tüm verileri Back-End servislerine istek atıp, toparlayıp gerekiyorsa veriyi manipüle edip uygulamaya buradan aktarıyoruz. Api Gateway, Node JS üzerinden çalışmakta ve bizi performans anlamında özellikle de büyük verilerin manipüle edilip sunulması gereken noktalarda hızı bizi çok mutlu ediyor.
Google Map NPM Seçimi
Npm seçiminde öncelikle bir kütüphane kullandık üzerinde geliştirmeler başladı fakat olağan dışı kasmalar yaşatıyordu. Performans iyileştirmeleri yaptık ama hala istediğimiz gibi değildi ve anlamsız performans kayıplarıydı fakat sonrasında bir aydınlanma ile sorunun npm’in kendisinden olabileceğini düşündük ve kütüphaneyi tekrar araştırdık aslında çok yüksek indirme sayılarına sahip(haftalık 200 bin gibi) güvenilir bir npm gibi duruyordu fakat son güncellemesini 4 yıl önce almış 😊. Bu noktada bu kütüphaneden vazgeçme kararı aldık ve yeni bir kütüphaneye geçtik bu kütüphane ise yakın zamanda sık sık güncelleme almış ve desteği devam eden bir kütüphaneydi geçişi tamamladıktan sonra tahmin ettiğimiz gibi bu anlamsız kasılmalar kalkmış ve hiçbir performans sorunumuz kalmamıştı.
Marker İkon Seçimi
Harita üzerindeki marker’ların anlamlı bir hal alması ve kullanıcıya doğruyu anlatması için özenle hazırlanmış ikonlar kullanılmalıydı, güzel tasarımlar konusunda UX ekibine buradan tekrar teşekkür ederim.
Burada benim bahsetmek istediğim konu ise mevcut ikonun formatı. Başta klasik bir seçim olan png kullandık fakat harita üzerinde göze ara ara takılan atlamalar yaşanmaya başladı. Rahatsız edici tabiki değildi ama biz en iyisini istiyoruz değil mi? Neden oluyor bu diye araştırırken büyüklüğünden, boyutuna kadar her şeyi dikkate alarak deneyerek devam ettik fakat doğrusu hiçbir fark yaratmıyordu, sonrasında ise format değişirsek acaba ne olur dedik ve ikonlarımızı SVG olarak değiştirdik. Bingo… az önce bahsettiğim ara ara göze batan atlamalar tamamen ortadan kalktı bunun yanında SVG’nin doğası gereği çok daha şık ve net görüntüler elde ettik.
Marker Tanımı
Marker’lar oluşturulurken binlerce marker oluşturacağımız için haliyle burayı döngü haline almamız gerekiyor. Bu konuda dikkat edilmesi gereken bir durum var Marker’lara key tanımı yapılmasının atlanmaması. Marker’lara diğerleri ile çakışmayacak eşsiz keyler tanımlanmalı biz burada marker’in id bilgilerini vererek key’lemeyi sağladık. Alternatif olarak buraya index’de verilebilir fakat yüksek boyuttaki ve bilgileri socket tarafından her an değişebilecek verilerde index kullanımı tehlikeli sonuçlara sebebiyet verebiliyor(verinin silinmesi vs.). Key tanımı bizim için React’ın o objeyi eşsiz olarak kontrol edebilmesini sağlıyor böylece eğer ki o marker üzerinde bir güncelleme yok ise React bunu tekrar render etmiyor bilgilerini kontrol edip bu işlemi atlıyor bu da bize stabilite olarak geri yansıyor.
Bir diğer husus ise ister istemez değişiklik olan tüm markerlar render alınıyor ve bu da üst üste binen veya markerların birbirine yakın olan durumlarında sürekli bir göz kırpma hali yaratıyor bu konuda markerların index bilgisine map üzerinden gelen index değerini verirseniz bu üst üste gelen bilgilerin seviyeleri sabitleneceği için gereksiz göz kırpmalardan kurtulmuş olursunuz.
🥳 Özet
Yaklaşık 2,5 ay gibi bir sürede tamamladığımız ve 5 aydır da canlı olarak çalışan yeni takip ekranımız ile ilgili proje süresince yaşadığımız, karar verirken değerlendirdiğimiz koşulları, hatalarımızı, iyi kararlarımızı sizlerle paylaşmak istedik. Umarım deneyimlerimiz sizin için faydalı olmuştur.
Yeni yazılarımızda görüşmek üzere 😊