Node.js ile Crawling Yaparken Bilinmesi Gerekenler

Yaklaşık 3 aydır node.js'i yalnızca scripting ve crawling için kullanıyorum. Herhangi production seviyesinde bir projede node kullanmak an itibariyle bana pek güven verici gelmiyor, fakat güzel bir makine ile nodejs üzerinde yapacağınız senkron olmayan network crawling işlemlerinde harika bir performans veriyor.

Line by line bir programlama dilinde karşılaştığınız X işlemini yap, Y işlemini yap, Z işlemini yap sonra tekrar X işleminden devam et mantığı burada işlemci gücü, network hızı, kullanılan veritabanı yapısı gibi değişkenlere göre değişmek üzere, X işleminden aynı anda 35 tane yap, tamamlanan X işlemlerinden sonra 25 tane Y işlemi yap, yapabildiğin kadar Z işlemi yap gibi bir ortam yaratıyor.

async vs sync

Bu tip bir aksiyonu, thread worker kütüphaneleri ile statik programlama dillerinde de sağlayabiliyor olmanıza rağmen, nodejs'de kullanım çok daha rahat ve basit.

Bu yazıda 3 aylık süreçte karşılaştığım sorunları ve ipuçlarını bulabilirsiniz.

Memory Leak, Out of Heap Memory Sorunları

Node uygulaması başlamadan önce yaklaşık 1.7GB boyutunda kullanmak için bir heap yaratıyor. Eğer crawling sırasında tuttuğunuz ve yok etmediğiniz değişkenler ile bu heap'i doldurduysanız, program kendisini durduracaktır.

<--- Last few GCs --->

   64004 ms: Mark-sweep 1368.9 (1434.7) -> 1368.8 (1434.7) MB, 1642.7 / 3 ms [al
location failure] [GC in old space requested].
   65610 ms: Mark-sweep 1368.8 (1434.7) -> 1368.8 (1434.7) MB, 1606.3 / 2 ms [al
location failure] [GC in old space requested].

<--- JS stacktrace --->

==== JS stack trace =========================================

Security context: 000001834C5C9E79 <JS Object>
    2: new constructor(aka Request) [C:\Users\open\Desktop\ascrawl\node_modules\
request\request.js:129] [pc=000003D7D86912A7] (this=000001E047030FC1 <a Request
with map 00000200253A62F1>,options=000001E0470328F9 <an Object with map 00000200
253A8E41>)

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

Bu örnekte oluşturulan map üzerinde bilinçli olarak kullandığım-yarattığım objeleri yok etmedim, konsolda 65 saniye sonra (65610ms) bu hatayı aldım. Burada dikkat etmemiz gereken, eğer çok fazla veri ile uğraşıyorsanız, özellikle HTML cevaplarını bir array'de tutmak veya tutulan değişkeni uzun süre diğer paralel programlarla birlikte tutmak, heap'in dolmasını sağlayabiliyor. Bunun yönetiminin iyi gerçekleştirilmesi gerekiyor.

Bu sorunun eğer limiti arttırarak sizin kodunuz için çözüleceğini düşünüyorsanız, crawling scriptinizi şu komut ile çalıştırarak heap memory limitini bu script için yükseltebilirsiniz;

node --max_old_space_size=4096 yourFile.js

Burada 4096 değeri, 1.7gb dolaylarındaki heap memory limitini 4GB'a yükseltiyor. Bahsettiğim gibi kullanırken dikkatli olmakta fayda var, bunun yerine kodunuzdaki heap üzerinde gereksiz yer tutan instanceları temizleyebilirsiniz.

Request Limiti ve Bottleneck

İnternette araştırdığımda fazla kullanıcının şikayet ettiğini görmediğim, fakat Türkiye'deki networking hızını düşündüğümüzde, localhost üzerinde çalışan scriptler için oluşacak bir sorun, aynı anda yapılan paralel http requestleri.

Örneğin, crawl scriptimizin 10k HTML adresine aynı anda http requesti yaptığını düşünelim; bu tip bir durumda, Node; bu isteklerin tamamını başlattığında, isteklerin tamamını bitirmek konusunda garanti verse bile, yavaş ve dalgalı bir internet bağlantısında, işlemlerin tamamlanması çok uzun sürebilir, veya bağlantı zaman aşımına uğrayabilir. Node mimarisinde tek seferde 6 paralel http işleme izin verilmesi durumu bile, yavaş internet bağlantısına sahip makinada, bağlantının zaman aşımına uğramasına sebep olabilir.

Bu sorunun çözümü olarak;

  • Network isteği zaman aşımı süresi limiti kaldırılabilir veya yüksek bir değere atanabilir. Bu çözümü, crawl edilen kaynakta yaşanabilecek muhtemel bağlantı dalgalanmalarından dolayı önermediğimi söyleyebilirim.

  • Diğer bir çözüm ise eşzamanlılığı boşverip (ki önerilmez) tek seferde tek http isteği yapılması sağlanabilir. (recursive olarak fonksiyon işlemini tamamladığında, fonksiyonu farklı parametre ile tekrar çağırmak) Bu yapı nodejs'i, çoğu line-by-line programlama dilinde yapılan işlem ile aynı yapıya çevirecektir. (X -> Y -> Z: ilk görseldeki ilk diagrama göz atın.)

  • Sunulacak en mantıklı çözüm, bottleneck gibi bir kütüphane ile ansenkron şekilde gerçekleştirilecek işlemi limitlemek olacaktır.

Bottleneck

Bottleneck ile async çalışan fonksiyonlara anlık çalışma limiti verebiliyorsunuz;

var Bottleneck = require("bottleneck");
var limiter = new Bottleneck(1, 2000);
someAsyncCall(arg1, arg2, argN, callback);
//Bunun yerine;
limiter.submit(someAsyncCall, arg1, arg2, argN, callback);
//Bunu kullan!

Burada bottleneck, bir limiter oluşturuyor ve bu limitler üzerine, async fonksiyonlarınızı submit metodu ile bu limiter'a iletiyorsunuz. Limiter, verilen limite göre, fonskiyonlarınızı sırasıyla çalıştırıyor.

bottleneck @NPM

Crawl Edilen Verinin Yönetimi ve Veritabanı

Crawling yaparken, elde edilen verinin temizlenmesi, seçilmesi veya yeniden düzenlenmesi ve bu verinin depolanması konusunda da bazı şeylerden bahsedelim;

Cheerio, blog'da kullanımından da bahsettiğim bir DOM işleyicisi. Sayesinde elde ettiğiniz HTML cevabını anlamlandırıp çok daha rahat parse edebiliyorsunuz. Ayrıntılar ve kullanımı için şu yazıyı inceleyin;

Cheerio – Sunucu Taraflı DOM İşleyicisi @buraktokak.com

Buna ek olarak crawl edilen verinin JSON dosyalarında tutulması, sonrasında herhangi bir uygulama üzerinde kullanılmasını kolaylaştırıyor. Eğer verinin searchable olduğu veya basit bir konsol uygulaması üzerinde kullanılabilir olması gerektiğini göz önüne alırsak, mongo benzeri bir veritabanına veri aktarılabilir.

Eğer crawl edilen veri, şematik ve relational bir yapıya sahipse (ki genellikle öyle oluyor) bu veriyi mysql veritabanında tutmak güzel bir tercih olabilir. MySQL veya mongo üzerine aktarılan veriyi kolaylıkla manipüle edip uygulamalarda da kullanabilirsiniz.

Yorumları Göster veya Yeni Yorum Yaz