Why not to Give It a Go(lang)!

The lessons that we learned when using Node.js for large code-base services

Dewi Oktaviani
Kata.ai Tech Blog
6 min readJul 10, 2019

--

Background

Setelah dua tahun bergabung dan menjadi bagian dari team engineer di Kata.ai, akhirnya saya merasakan bahwa sebuah teknologi memiliki lifecycle.

Saat ini service di Kata.ai berjalan di environment Node.js yang ditulis menggunakan Typescript — sebagai transcompiler Javascript yang menyediakan pola static-typing — sehingga lebih mudah untuk maintain code-base Javascript dalam pengembangan service di Kata.ai yang cukup besar. Secara personal, saya mengenal Javascript sebagai bahasa pemrograman yang digunakan untuk menghiasi dan memanipulasi tampilan website. Client-side. Sehingga saya merasa cukup amazed ketika menggunakan Javascript (lagi) sebagai server-scripting language, karena Node.js memiliki memroses thread secara asynchronous dan non-blocking I/O.

Kata.ai menyediakan platform untuk membantu client partner melakukan proses design, build dan deploy bot sendiri sesuai kebutuhan mereka, yakni melalui web browser dan command-line interface (cli). Kata.ai berusaha menjadi pelopor penyedia layanan chatbot berbasis SaaS (Software as a service) di Indonesia. Namun, ada beberapa perusahaan partner — terutama sektor perbankan dan telekomunikasi — yang ingin aset data disimpan di server mereka sendiri. On-premise. Untuk memenuhi kebutuhan tersebut, team engineer pun melakukan setting-up dari awal hingga bot bisa berjalan di server mereka.

When Node.js Problem Attacks!

On-premise service installation is too long and complicated

Team engineer harus melalui step-step yang lumayan panjang dalam proses set-up on-premise di server partner, dimulai dari install Node.js, clone source code-base masing-masing service yang saling berkaitan, install npm dependencies, memastikan masing-masing service dapat berkomunikasi di server mereka; hingga ke level aplikasi, yakni: bot bisa mengklasifikasikan intent dari kalimat dari user dan menjawab sesuai intent.

Pada proses instalasi on-premise di perusahaan partner, ada banyak proses yang seharusnya bisa disederhanakan, seperti proses instal dependensi modules (node_modules) untuk setiap service. Total size folder node_modules juga sangat besar, sehingga dibutuhkan koneksi internet yang kencang dan stabil saat instal dependensi melalui npm. Regulasi perusahaan partner pun berbeda-beda, ada beberapa perusahaan yang membatasi penggunaan koneksi internet dan akses url tertentu karena security concern yang bisa membuat proses instalasi menjadi terhambat. Tidak jarang proses ini membutuhkan waktu berhari-hari.

Nah, jika ada lebih banyak perusahaan partner yang menghendaki instalasi on-premise, team engineer sangat butuh solusi penyederhanaan instalasi service. The less-dependent the better.

We need to protect our source-code

Potensi masalah yang muncul adalah ancaman vulnerability pada source-code dan susahnya proses debugging jika terjadi error. Javascript adalah interpreted language — bukan compiled language — sehingga instalasi on-premise berarti membiarkan salinan source-code ‘beredar bebas’ dan running di server perusahaan partner. Team engineer tidak bisa menjamin source-code tersebut tidak dimodifikasi oleh pihak lain, sehingga proses tracing error dan debugging menjadi lebih susah.

Scaling-up service is undeniable, but …

Semakin bertambahnya perusahaan partner yang menggunakan platform Kata.ai, membuat semakin banyak data yang mengantri untuk diproses. Service yang dibangun dengan Node.js kurang robust untuk handle task pemrosesan data yang banyak dan kompleks. Demi meningkatkan reliabilitas dan scalabilitas service, metode clustering pun dilakukan untuk membagi workload.

Karena scalability issue ini, kami belajar bahwa Node.js tidak diperuntukkan bagi task yang memiliki komputasi berat dan yang memiliki I/O request yang intens dan besar ke/dari database, karena request tersebut — meskipun Node.js adalah asynchronous — akan membuat request lain terblokir selama beberapa waktu. Mengingat JavaScript adalah bahasa pemrograman single-threaded dimana hanya memroses ada satu set instruksi pada satu waktu.

Our service hit Node.js memory-cap limit

Team engineer pernah mengalami error berupa out of memory (OOM) di salah satu service di Kata.ai. Penyebab terjadinya memory allocation issue salah satunya karena service menyimpan terlalu banyak data ke variabel atau bug yang menyebabkan memory leaking, sehingga alokasi limit memori yang disediakan tidak cukup. Sementara limit memori bawaan dari Node.js — untuk prosesor 32-bit adalah 512 MB dan untuk 64-bit adalah 1 GB — terlalu kecil.

Why not to Give It a Go(lang)!

Dengan konteks problem yang dialami selama masa pengembangan dan pemeliharaan service di Kata.ai, team engineer memutuskan untuk mengubah stack API service dari Javascript ke Golang. Beberapa alasan mengapa team engineer memutuskan untuk migrasi, antara lain:

Golang compiles program into binaries that protect our source-code!

Sebagai compiled language, Golang mengompilasi source-code (high-level language) menjadi object module dan menghubungkan (linking) semua object module untuk membentuk executable file dalam bentuk binary (low-level language) yang dipahami oleh mesin. Metode linking yang digunakan Golang adalah static linking dimana semua dependensi object module dihubungkan dan digabung ke dalam sebuah binary file.

Team engineer hanya perlu compile source-code sesuai tipe sistem operasi dan arsitektur di server milik perusahaan partner, kemudian binary file tersebut bisa langsung di upload ke server dan berjalan tanpa harus instal dependensi dari package atau module eksternal. Dengan mengadaptasi Golang, proses instalasi on-premise dapat dilakukan menjadi lebih sederhana dan efisien.

Selain itu, Golang juga menjamin security source-code karena hanya binary file yang didistribusikan ke perusahaan partner.

Golang error handling is just too awesome!

Proses error handling di Golang mendukung nilai balikan multiple, yakni: hasil fungsi tersebut (contoh: fungsi perkalian akan mengembalikan nilai hasil perkalian) dan variabel error. Variabel error ini berguna untuk memberi sinyal bahwa ada problem atau tidak dalam proses eksekusi fungsi tersebut. Jika tidak ada problem, maka nilai balikan dari variabel error adalah nil.

Melalui cara ini, kita bisa melakukan pengecekan nilai balikan variabel error dulu sebelum memroses hasil dari fungsi tersebut. Jika terdapat problem, maka error tersebut seharusnya di-handle baik melalui mekanisme log, return, atau retry.

Karena error handling di Golang yang eksplisit, team engineer bisa melakukan debugging untuk setiap function di source-code yang besar secara mudah dan bisa menunjuk tepat di titik problem penyebab error ada dimana.

Golang ability to do multi-threading

Salah satu keunggulan dari Golang adalah memiliki goroutines. Goroutines mendukung Golang untuk melakukan multi-threading. Multi-threading di Golang berarti ada beberapa function/method yang di-eksekusi pada goroutine — disebut juga lightweight threads — secara concurrent dengan function/method lain dalam 1 waktu.

Berbeda dengan Node.js — yang merupakan single-threading — pada Golang, function/method yang menggunakan I/O tidak akan memblocking function/method lain yang mengakses resource yang sama di waktu yang sama dengan goroutines. Kemampuan Golang melakukan multi-threading sangat mendukung scalability dari sebuah aplikasi.

Golang has more and more memory allocation

Golang memiliki alokasi memori limit yang sangat besar, Golang versi 1.4 memiliki memori limit sebesar 128GB. Sementara pada Golang versi 1.8 sebesar 512 GB. Golang menggunakan sistem memory mapped files, sehingga alokasi virtual address space bisa sangat besar — bahkan tak terbatas — dengan menyematkan flag `“ulimit -v = unlimited”` untuk setting limit otomatis menjadi unlimited.

Conclusion

Saat ini team engineer Kata.ai sedang dalam tahap awal migrasi beberapa service dari Node.js ke Golang. Setiap perubahan memang membawa tantangan baru, selain harus maintain legacy code, team engineer juga harus menyediakan waktu untuk mempelajari Golang. Karena tidak mudah untuk mengubah legacy code dari Node.js ke Golang — kami menentukan priority migrasi soure-code berdasarkan urutan service yang memiliki less dependency terhadap service lain.

Golang memang bukan bahasa pemrograman yang paling sempurna dan paling cocok untuk semua kondisi. Namun, Golang lebih reliable untuk handle service di Kata.ai dan robust terhadap perubahan yang akan muncul beberapa tahun kedepan — jauh lebih baik daripada Node.js.

Source

  1. Benchmarking Go vs Node vs Elixir: https://stressgrid.com/blog/benchmarking_go_vs_node_vs_elixir/
  2. Achieving Concurrency in Go: https://medium.com/rungo/achieving-concurrency-in-go-3f84cbf870ca
  3. Error Handling in Go: https://medium.com/gett-engineering/error-handling-in-go-53b8a7112d04

--

--