Tentang Distributed System

Sebuah curhatan tentang distributed system

Disini kita tidak membicarakan tentang Microservices, spesifiknya kita akan membahas tentang sistem yang ter-distribusi. Sebelum menyelam lebih dalam, mari kita bicarakan terlebih dahulu "kondisi" saat ini.

Gambar diatas sangat sederhana:

  • Client membuat request (mengakses blog.evilfactory.id)
  • Request tersebut dihandle oleh server Express (Node.js)
  • Express mengambil data dari database (Postgresql) dan memberikannya ke server
  • Server memberikan response berbentuk view beserta dengan data yang dibutuhkan

Rata-rata aplikasi yang dibuat adalah seperti ini. Kita tidak akan membahas juga tentang monolithic, tapi beginilah kondisinya.

Lalu dimana masalahnya? Kondisi yang ingin gue capai adalah Zero Downtime Deployment, ketika gue membuat perubahan ke aplikasi tersebut (dan men-deploy nya) aplikasi seharusnya tidak down ketika proses deployment tersebut berlangsung.

Gue menggunakan Docker untuk menjalankan aplikasi-aplikasi gue, di Docker, kita bisa membuat replika untuk service yang kita miliki. Dipikir-pikir ini bisa untuk melakukan pendekatan Zero Downtime Deployment ini, gambarannya mungkin akan seperti ini:

Karena gue pakai Traefik untuk "mengatur" koneksi masuk, gambaran lebih tepatnya adalah seperti ini:

Kita gak bahas tentang Database diatas terlebih dahulu, tapi begitulah kira-kira gambarannya untuk mencapai "zero downtime deployment".

Blue-green deployment

Pola yang paling banyak digunakan untuk mencapai Zero Downtime Deployment adalah Blue-green deployment, yang singkatnya adalah lingkungan production kita memiliki 2 aplikasi yang identik (replika) yang satu dilabeli biru (yang sedang berjalan misalnya) dan yang satu dilabeli hijau (yang sedang idle, karena sedang "update" misalnya).

Karena menggunakan Docker (dan Traefik), mereka bisa mengetahui yang mana yang biru ataupun yang hijau dari proses "healthcheck". Yang sehingga, Traefik bisa "me-route" koneksi yang ada ke container yang biru sambil menunggu container yang hijau selesai di update. Sehingga pengguna tidak mendapatkan response dari container yang sedang di update.

Zero downtime deployment mungkin bisa diselesaikan dengan pendekatan diatas, tapi ada masalah lain yang gue miliki dalam konteks ini: Reliability.

Load Balancing

Saat lagi rame-ramenya aplikasi gue oleh trafik yang masuk, terkadang container gue menyerah dan down. Karena singkatnya 1 layanan dibanjiri dengan request secara bertubi-tubi dalam 1 waktu yang sama.

Ini bisa diselesaikan dengan replika (dan load balancing), request masuk dibagi-bagi menggunakan algoritma tertentu (Round robin/whatever) sehingga beban tidak hanya diemban oleh 1 layanan/backend.

Beruntungnya pakai Traefik, gue bisa mendapatkan fitur "load balancing" ini secara out of the box.

Apakah masih ada masalah? Ada, sayangnya. Layanan & Traefik berada di 1 mesin, yang singkatnya, resources berada di mesin yang sama meskipun ter-isolasi™.

Mesin gue memiliki RAM 2GB dengan 1 vCPU core, diasumsikan bisa mengatur koneksi masuk sebanyak 4000/rps. Meskipun request tersebut bisa "ditanggulangi" dengan cara load balancing (misal jadinya 2000/rps per-service/container), tapi Traefik tetap mengatur 4000/rps tersebut.

Yang artinya, bila Traefik kewalahan, keselurahan layanan akan tidak bisa diandalkan. Gue bisa melakukan Vertical Scaling misal menambahkan RAM tambahan 1GB dan vCPU menjadi 2, mungkin sekarang bisa mengatur 8000/rps. Tapi ada masalah lain yang akan gue bahas nanti.

Distributed System

Sebenarnya kita sudah terbiasa menggunakan Distributed System, salah satunya adalah internet yang kita gunakan untuk mengakses informasi. Kita sedang tidak kuliah sejarah internet, tapi singkatnya adalah tentang komputer yang saling terhubung.

Kembali ke permasalahan, jika sebelumnya kita melakukan "replika" di level aplikasi, bagaimana bila replika tersebut berada di level mesin juga? Dan bila sebelumnya replika berada di mesin yang sama, bagaimana bila replika tersebut berada di mesin yang berbeda?

Misal, seperti ini:

Gambar diatas, Traefik dan 1 layanan kita berada di 1 mesin yang sama. Namun replikanya berada di mesin yang berbeda. Traefik bisa berkomunikasi dengan mesin yang berbeda dengan mode Swarm yang sudah dihandle out of the box oleh Traefik.

Tapi ada masalah disini, sialnya. Yakni di persistensi untuk database dan file statis.

Kita menggunakan Postgres untuk database kita, dan untuk membuatnya persisten (data tetap ada meskipun container diubah/dihapus) kita bisa melakukan "mounting" direktori dari mesin kita ke container. Yang singkatnya, bila container melakukan write ke /opt/postgres di container tersebut, berarti proses write sebenarnya mengarah ke /home/data/postgres yang berada di mesin asli kita.

Begitupula ketika melakukan "mounting" untuk menyimpan berkas statis.

Misal kita ambil contoh untuk menyimpan berkas statis. Gambar diatas berada di /home/data/ghost/content/images/2020/02/Untitled-Diagram-4.png yang berada di mesin bernama sg-1. Kita melakukan replika di mesin lain, dan sedangkan mesin lain tidak memiliki direktori /home/data/ghost/content.

Kita bisa saja membuatnya, namun bagaimana agar membuatnya tetap konsisten? Atau setidaknya "tau" berkas tersebut tersimpan dimana. Ini salah satu masalah yang gue miliki.

Network File System

Salah satu solusinya adalah menggunakan Network File System atau NFS. Yang singkatnya berkas bisa diakses oleh mesin yang terhubung. Misal, di mesin satu kita mounting /home/data ke /mnt/data, di mesin satu kita mounting /home/data ke /mnt/data juga.

Bila terjadi perubahan, 2 mesin tersebut akan memiliki berkas yang sama, konsisten. Misal bila gambar tersebut dihapus, maka gambar tersebut akan dihapus di 2 mesin, begitupula ketika melakukan perubahan nama.

Gambaran diatas menggunakan pendekatan replika, yang singkatnya 10GB di mesin 1 akan berukuran 10GB juga di mesin yang lain.

Selain itu, bisa melakukan "chunking", singkatnya berkas dipecah-pecah. Jadi, gambarannya mungkin menjadi 6GB di mesin 1 dan 4 GB di mesin 2. Ini menarik, tapi terdapat latensi yang tidak sebentar. Gue pernah menggunakan GlusterFS, tapi malah menambah masalah baru (meskipun masalah terkait FS terselesaikan).

Masalah tersebut adalah di latensi.

Traefik swarm

Kita sebelumnya sudah menyinggung bahwa Traefik berada di mesin yang sama. Yang singkatnya ya tadi, 4000/rps berada di mesin yang sama, dan bila down, maka down semua. Ini kita bisa melakukan "load balancing" di Traefik dengan bantuan Consul, sebuah service mesh.

Mengapa menggunakan Consul? Pertama, kita ingin menyimpan konfigurasi dari Traefik dan berikut dengan sertifikat SSL nya. Consul sama seperti Traefik, yakni distributed. Dan juga menyediakan KV Storage, kita bisa menyimpan hal-hal terkait konfigurasi di Traefik disitu agar konsisten.

Yang artinya, setiap mesin kita akan memiliki Traefik nya sendiri dan biarkan Consul yang mengatur ke Traefik dan Traefik akan mengatur kemana request tersebut harus dikirimkan.

Jika gambarannya diatas, berarti kita menggunakan 3 mesin:

  1. Manager, khusus consul.
  2. Node, traefik + services
  3. Node, traefik + services

4000/rps diasumsikan tidak menjadi masalah karena tidak ada "proses khusus" selain hanya untuk "me-route" request. Tidak ada proses lain seperti, database, backend, dsb. Hanya consul.

File System???

Masalah yang paling sulit diselesaikan oleh gue adalah file system. Ada rencana untuk "men-split" mesin yang 1 khusus mengatur aplikasi, yang 1 khusus untuk database. Tapi enggak semudah itu, karena kita tidak tau container tersebut akan dijalankan dimana. Dan ya, kecuali gue tau.

Dan latensi yang paling parah adalah ada di Disk I/O berikut dengan membuatnya menjadi konsisten.

Ada rencana untuk menyimpan berkas statis di 3rd party ataupun di jenis database seperti GridFS. Tapi belum berani mencoba.

Distributed is hard af

Hal yang paling sulit di distrbuted system adalah konsistensi. Juga kompleksitas. Bila gue menyerah, gue "bisa aja" menggunakan layanan load balancing seperti ELB™ ataupun NodeBalancers™ kalau cuma ingin mengambil HA nya aja. Tapi disini gue ingin mengambil proses pembelajarannya, bukan murni solusinya.

Dan juga tidak sesederhana itu, bukan?

Dan juga, "distributed" disini berada di level Load Balancer, bukan aplikasi. Yang singkatnya, gue "stick-in" ke LB yang gue pakai untuk bisa mencapai pendekatan "distributed" ini.

Penutup

Gue masih meng-eksplorasi subjek ini, dan tujuan diterbitkannya tulisan ini untuk berbagi apa yang gue alami & pahami, in case there is something wrong about it.

Akhir-akhir ini (dari 2019 akhir sih sebenernya) lebih sering berurusan dengan infra daripada hanya sebatas layanan, dan bangsatnya gue malah enjoy dengan subjek ini. Meskipun LB juga termasuk aplikasi, mungkin next time akan coba eksplorasi seputar distribusi di level aplikasi langsung juga.

Untuk sekarang gue stick dengan vertical scaling/scale up, jika masalah FS sudah ter-solve, mungkin gue akan coba eksplorasi lagi ke horizontal scaling/scale out. Thank you!

Menikmati tulisan ini?

Blog ini tidak menampilkan iklan, yang berarti blog ini didanai oleh pembaca seperti kamu. Gabung bersama yang telah membantu blog ini agar terus bisa mencakup tulisan yang lebih berkualitas dan bermanfaat!

Pendukung

Kamu?