Streams in Node.JS

Wai Lin Kyaw
3 min readJan 20, 2021

ကိုယ်က Non-Blocking I/O ကောင်းကောင်းရေးတတ်ချင်တယ်၊ နောက် data intensive လို့ခေါ်တဲ့ data ပမာဏ အများကြီးကို သေချာ ကိုင်တွယ် တတ်ချင်တယ် ဆိုရင် အနည်းဆုံး Event Driven Architecture တွေ၊ Asynchronous Programming စတာတွေ သိထားဖို့ လိုတယ်။

နောက် application တိုင်းလိုလို I/O နဲ့ကင်းလို့ ရတယ်လို့မရှိဘူး။ System ဖက်ကနေ I/O ကို efficient လည်းဖြစ်အောင် developer တွေသုံးရလည်းလွယ်အောင် API ထုတ်ပေးရတာ အတော်မလွယ်တဲ့ ကိစ္စပါပဲ။

Node.JS မှာ ကျတော့ data buffer တွေနဲ့ ‌asynchronous data streams တွေကို ကောင်းကောင်း handle လုပ်နိုင်ပြီဆိုရင် I/O နဲ့ပက်သက်ပြီး အားသာချက်တွေ အများကြီး ခံစားရမယ်။ သူ့ design ကလည်း အဲ့ဒီအတွက် ထုတ်ထားတာကိုး။

Node.JS မှာ stream ဆိုတာက bytes အတွဲလိုက်၊ အတန်းလိုက်ကို ခေါ်တာ။ ဥပမာ

“Node.JS” ဆိုတဲ့ စကားလုံးကို stream အနေနဲ့ဆိုရင်

4e 6f 64 65 2e 4a 53 ဒီလိုမြင်ကြည့်လို့ရတယ်။

digital data မှန်သမျှကို byte နဲ့ဖော်ပြလို့ရနိုင်တာ မလို့ stream ထဲမှာရှိတဲ့ character တစ်ခုစီတိုင်းက တိတိကျကျ သတ်မှတ်လို့ရတယ်။ နောက် stream ကို အပိုင်းလေးတွေ ပိုင်းလို့ရတယ်။ ဒီအပိုင်းတွေကို chunk လို့ခေါ်တယ်။ chunk တစ်ခုကို byte ဘယ်လောက်ထားမယ် ဆိုပြီးသတ်မှတ်လို့ရတယ်။

stream တစ်ခုကို chunk လေးတွေ အနေနဲ့ ပိုင်းလိုက်ရင် ဘာအားသာချက်တွေ ရနိုင်မလဲဆိုတော့ ပထမ တစ်ချက်အနေနဲ့ memory ကို တစ်ချိန်တည်းမှာ အများကြီး သုံးစရာမလိုတော့ဘူး။ ဥပမာ 1GB ရှိတဲ့ video file တစ်ခုကို client ကနေ လိုချင်တယ်ဆိုပါစို့။ ဒီတော့ disk ထဲကနေ 1GB ရှိတဲ့ file တစ်ခုလုံးကို memory ‌ပေါ်တင်၊ ပြီးရင် client ကို 1GB ရှိတဲ့ file လိုက်ကြီး ပြန်ပေးမယ်ဆိုရင် memory ပေါ်ကို 1GB လိုက်အရင် တင်ရမယ်ပေါ့။

ဒီလို မလုပ်ပဲနဲ့ 100KB လောက်ဖြစ်ဖြစ် 200KB လောက်ဖြစ်ဖြစ် chunk လေးတွေ ခွဲပြီး read လုပ်မယ် memory ပေါ်တင်မယ်၊ client ကိုလည်း chunk အလိုက်ပြန်ပေးမယ်ဆိုရင် ပိုအဆင်ပြေတယ်။ memory ကိုလည်း overuse လုပ်ရာမကျတော့ဘူး။ နောက်တစ်ခုက ဘာပိုကောင်းသွားမလဲဆိုတော့ ခုနက video example မှာပဲပြန်ကြည့်ရင် file တစ်ခုလုံး response ပြန်ရင် တစ်ခုလုံး ရောက်လာမှ client က video ကို ကြည့်လို့ရမယ်။ chunk လေးတွေခွဲပို့ရင် file တစ်ခုလုံး ရောက်မလာသေးရင်တောင် ရောက်လာတဲ့ chunk တွေနဲ့တင် video ကို play လုပ်လို့ရပြီ။ file တစ်ခုလုံး loading ပြီးအောင် စောင့်စရာမလိုတော့ဘူးပေါ့။

ဒါက ဥပမာတစ်ခုပေါ့။ တခြားဟာတွေလည်း ရှိဦးမယ်။ Node.JS မှာ stream module ဆိုပြီး builtin ပါတယ်။ သူ့မှာ Readable, Writable, Duplex, Transform နဲ့ PassThrough ဆိုပြီးတော့ stream 5 မျိုးပေးထားတယ်။

Readable stream

readable ဆိုတဲ့အတိုင်းပဲ တစ်ခြား stream တစ်ခုခုကနေလာပြီး read လုပ်လို့ရနိုင်တယ်။ တစ်နည်းပြောရရင် သူက data တွေကို produce လုပ်တဲ့ကောင်၊ ထုတ်တဲ့ကောင်။ ကျန်တာသူမသိဘူး။ ဥပမာ file တစ်ခုခုကို ဖတ်မယ် file ထဲက data တွေကို chunk လေးတွေ အနေနဲ့ ထုတ်ပေးမယ်။ သူ့ရဲ့ internal buffer size ကို ဘယ်လောက်ထားမလဲဆိုတာတွေ သတ်မှတ်ပေးလို့ရတယ်။

Writable stream

writable ကျတော့ data တွေ (ပြောချင်တာက chunks တွေ) ကိုလက်ခံပြီး တစ်နေရာရာမှာ write လုပ်ပေးမယ်။ write လုပ်တယ်ဆိုတဲ့နေရာမှာ console လည်းဖြစ်ချင်ဖြစ်မယ်။ client ကို response ပြန်ပေးနေတာလည်း ဖြစ်ချင်ဖြစ်မယ်။ ဒါမှမဟုတ် file တစ်ခုထဲကို write လုပ်ကောင်းလုပ်မယ်။ destination တစ်ခုခုပေါ့။ ဒီမှာလည်း readable မှာလိုပဲ internal buffer size ကိုသတ်မှတ်လို့ရတယ်။ internal buffer size ဆိုတာက ဒီ writable မှာဆိုရင် file တစ်ခုကို write လုပ်မယ်ပဲထားတော့။ writable stream ကနေ write မလုပ်ခင် သူ့ဆီမှာ hold လုပ်ထားရတဲ့ ပမာဏကို ပြောတာ။

ဘာပြသနာ တစ်ခုရှိလဲဆိုတော့ stream တွေမှာ သူတို့ရဲ့ လက်ရှိ internal buffer size ပြည့်သွားပြီဆိုရင် ထပ်ရောက်လာတဲ့ data တွေဘယ်သွားထားမလဲဆိုတာပါ။ မြင်သာအောင် ဥပမာပေးရရင် ရေပိုက်လိုပဲ။ ပိုက်ထဲမှာ ရေပြည့်နေမယ် နောက်ထပ် ရေထပ်လွှတ်ဖို့ဆိုရင် လက်ရှိရေတွေကုန်သွားဖို့၊ မဟုတ်ရင် လျော့သွားဖို့လိုတယ်။ ဒီလိုမှ မဟုတ်ရင် အနောက်ကို pressure ပြန်သက်ရောက်မယ်။ ဒီပြသနာကို backpressure လို့ခေါ်တယ်။

Node.JS မှာ backpressure ကို ထိန်းလို့ရအောင်လည်း လုပ်ပေးထားတယ်။ ဘယ်လိုပေးလဲဆိုတော့ read write operation လုပ်တဲ့ကောင်တွေကို ခေါ်လိုက်ရင် Boolean တစ်ခုပြန်ပေးတယ်။ value က false ဆိုရင် internal buffer size ကိုကျော်သွားလို့ပဲ။ back pressure ရှိပြီဆိုတဲ့သဘော။ ဒီကောင်ကိုဆုံးဖြတ်တဲ့ တခြား condition တွေလည်း ပါတာပေါ့လေ။ safe ဖြစ်သွားပြီဆိုတဲ့အချိန်ကျရင် နောက်ထပ် chunks တွေထပ်ပေးလို့ရအောင် drain ဆိုတဲ့ event တစ်ခု emit လုပ်ပေးတယ်။ ဒီ event ကို စောင့်ပြီး stream ကို ပြန် resume လုပ်လို့ရမယ်ပေါ့။

Duplex Stream

Readable ရော writable ရောရဲ့ facility နှစ်ခုလုံးရတဲ့ ကောင်ကို duplex လို့ခေါ်တယ်။ သူ့မှာကျတော့ bi-directional ရတယ်။ သူ့ရဲ့ အသုံးဝင်ပုံနဲ့ အားသာချက်ကတွေကတော့ အများကြီးပါ။ Node.JS မှာ TCP server create လုပ်လို့ရလာတဲ့ socket object က duplex stream ပါပဲ။ ဒီတော့ network level မှာပါ သပ်သပ်စီနေနေတဲ့ process တွေ ဆက်သွယ်ရတာ လွယ်ကူသွားတယ်။

Transform stream

သူလည်း duplex stream လိုပဲ readable နဲ့ writable ရောနှစ်ခုလုံးရမယ်။ နောက်တစ်ခုက readable နဲ့ writable ကြားထဲမှာ နေပြီးတော့ stream ကို transform လုပ်တာပေါ့။ data transformation တွေလုပ်ချင်ရင် ဒီကောင်ကို သုံးလို့ရတယ်။

PassThrough stream

သူက Transform ကို extend လုပ်ထားတာ။ ဘာလုပ်လဲဆိုတော့ ဘာမှမလုပ်ဘူး၊ ဝင်လာတဲ့ input data ကို output data အနေနဲ့ pass လုပ်ပေးရုံပဲ။ တစ်ခြားဘာ transformation မှ မလုပ်ဘူး။ ဘယ်နေရာတွေမှာ သုံးလဲဆိုတော့ ဥပမာ stream a က file တစ် file ကိုဖတ်ပြီး stream b ကိုလှမ်းပေးမယ်။ a ကပေးလို့ပြီးသွားတဲ့ အချိန်မျိုးမှာ သိချင်တယ်ဆိုရင် ကြားထဲမှာ PassThrough ခံထား‌ပေးလိုက်ရုံပဲ။ သူ့ကို ဖြတ်သွားတဲ့ ကောင်တွေကို စောင့်ကြည့်လို့ရမယ်။

နောက်တစ်ချက်က ရေးရတာ သက်သာတယ်။ ဘာလို့လဲဆိုတော့ Readable, Writable, Transform စတာတွေမှာ ဘယ်လို read လုပ်မှာလဲ၊ write လုပ်မှာလဲ နောက် transformation စတာတွေ ကိုယ်တိုင် ရေးပေးရတယ်။ PassThorugh မှာရေးစရာမလိုဘူး။ ရေးပုံရေးနည်း ပိုပြီး smart ဖြစ်တယ်ပေါ့။ API တွေတော့ ထည့်မပြောတော့ဘူး ကိုယ်တိုင် docs ဖတ်ကြည့်ပေါ့။

--

--