Belajar Sistem Terdistribusi dengan Membuat Klona YouTube: Bagian 1 - Desain Sistem
Peringatan spoiler: jauh lebih kompleks dari yang pertama saya bayangkan
Mungkin sebagian pembaca akan mengernyitkan dahi ketika sekilas melihat diagram yang tercantum di gambar depan artikel karena mungkin terlalu kompleks. Tetapi saya ingin ingatkan kembali bahwa seri ini adalah seri belajar sistem terdistribusi, jadi ya memang tujuannya bukan penghematan anggaran ataupun mendesain sistem yang seefisien mungkin. Sebaliknya, salah satu tujuannya adalah melakukan overengineering dengan harapan lebih banyak terekspos ke pengalaman dan pengetahuan yang berharga dari penggunaan perkakas baru serta dari mengimplementasikan sistem yang lebih kompleks dari yang biasa saya hadapi, sehingga ketika diperlukan untuk membuat sistem yang mirip saya tidak berakhir kelabakan.
Mendefinisikan Kebutuhan
Baru bagian pertama dari seri ini tapi di sinilah saya, sudah membuat "kesalahan" pertama saya dengan melakukan hal yang bertentangan dengan kalimat saya sendiri di bagian sebelumnya di mana saya mengatakan bahwa sistem yang saya buat tidak didesain untuk menangani jutaan pengguna secara langsung.
Di bagian desain sistem ini saya memutuskan untuk mengadopsi pendekatan yang umumnya digunakan di sesi desain sistem, yakni dengan mendefinisikan kebutuhan fungsional dan kebutuhan non-fungsional lalu merumuskan arsitektur seperti apa yang sekiranya mampu memfasilitasi kebutuhan tersebut. Perlu dicatat bahwa pendefinisian ini dilakukan hanya dengan tujuan agar ada patokan yang bisa memberikan gambaran karakteristik penggunaan seperti apa yang akan dihadapi oleh sistem ini. Kemungkinan saya tidak akan melakukan pengetesan secara keseluruhan untuk benar-benar menguji apakah sistem yang saya buat benar-benar mampu menangani karakteristik beban kerja yang saya deskripsi di seksi berikut.
Kebutuhan Fungsional
Kebutuhan fungsional mudahnya adalah fitur-fitur yang hendak disediakan. Adapun untuk sistem ini, kebutuhan fungsionalnya adalah sebagai berikut:
Pengguna dapat mengunggah video
Pengguna dapat menjelajahi katalog video
Pengguna dapat menonton video dalam berbagai resolusi
Pengguna dapat meninggalkan komentar pada video
Pengguna dapat mencari video
Pengguna dapat melakukan live stream
Pengguna dapat meninggalkan komentar di sesi live stream
Kebutuhan Non-fungsional
Kebutuhan non-fungsional adalah definisi sifat-sifat seperti apa yang perlu ada pada sistem yang hendak dibuat, berikut adalah kebutuhan non-fungsional yang saya tentukan untuk sistem ini:
Sistem harus highly-available.
Sistem harus observable.
Sistem harus mampu menangani permintaan streaming dengan latensi yang dapat diterima.
Sistem harus mampu menangani 1 juta pengguna aktif per hari yang masing-masing pengguna melakukan rata-rata 10 pencarian dalam sehari.
Sistem harus mampu menangani 5000 unggahan video baru per hari dalam format mp4 dengan rata-rata durasi 8 menit dan resolusi 1080p.
Sistem mampu menangani 20 komentar per 1000 view per video.
Solusi
Gambar di atas menunjukkan solusi yang saya rumuskan berdasarkan kebutuhan fungsional dan non-fungsional di atas. Kurang lebih terdapat 11 service inti yang terdiri atas:
No. | Nama Service | Fungsi |
1 | Auth | Menangani hal-hal yang berkaitan dengan autentikasi |
2 | User | Menangani operasi yang berkaitan dengan data-data pengguna |
3 | Search | Menangani pencarian video |
4 | Uploader | Menangani pengunggahan video baru |
5 | Encoder | Menangani pemrosesan video yang diunggah pengguna |
6 | Storage | Menangani penyimpanan video yang diunggah pengguna |
7 | Catalog | Menangani penjelajahan katalog video oleh pengguna |
8 | Metadata | Menangani operasi yang berkaitan dengan metadata video, seperti deskripsi, judul, dsb. |
9 | Playback | Menangani permintaan pemutaran video oleh pengguna |
10 | Comment | Menangani operasi-operasi yang berkaitan dengan komentar kepada video |
11 | Streamer | Menerima dan memproses data live stream dari pengguna |
Keputusan, Trade-off, dan Konsiderasi Lainnya
Cassandra Untuk Basis Data
Barangkali satu hal yang sangat nampak pada diagram di atas adalah keputusan saya untuk menggunakan Cassandra untuk menyimpan data di service Catalog dan service Comment. Hal ini didasarkan pada kebutuhan non-fungsional sistem di atas yang menunjukkan bahwa jumlah video dan komentar pada sistem diharapkan akan berkembang dengan cepat, oleh karena itu dibutuhkan basis data yang secara bawaan mendukung fitur sharding dengan baik dan mudah untuk diimplementasikan sehingga data yang terus berkembang dengan cepat tersebut dapat didistribusikan ke beberapa instansi shard untuk mengurangi beban kerja tiap instansi. Dengan karakteristik perkembangan data yang demikian, saya melihat Cassandra yang menyediakan kemudahan melakukan partisi data, kemampuan menangani data dengan skala besar, serta kemudahan scaling secara horizontal membuatnya cocok untuk digunakan pada kasus ini.
Mungkin sebagian pembaca akan bertanya, "Lalu kenapa masih ada service yang menggunakan MySQL? Kenapa tidak mengganti Cassandra dengan MySQL saja atau sebaliknya? Bukankah ada Vitess yang bisa dimanfaatkan untuk membuat proses sharding MySQL menjadi lebih mudah?" Pertanyaan ini adalah pertanyaan yang valid. Dan jawabannya adalah saya masih menggunakan MySQL meskipun di service-service lain menggunakan Cassandra, selain karena kemampuan Cassandra itu sendiri yang cocok digunakan untuk service-service tersebut, alasan pemilihan Cassandra yang kedua adalah saya ingin mengadopsi teknologi baru. Dan alasan saya tetap mempertahankan MySQL meskipun saya ingin mengadopsi teknologi baru adalah saya juga tetap ingin mencoba menggunakan Vitess dengan MySQL, sehingga dengan demikian saya terekspos dengan lebih banyak teknologi baru yang harapannya dapat meluaskan perspektif saya.
Kafka Untuk Message Queue
Hal lain yang begitu nampak adalah keputusan saya untuk memilih Apache Kafka secara eksklusif untuk menangani semua kebutuhan antrean. Lebih tepatnya jika dilihat pada diagram di atas terdapat dua tempat di mana Kafka digunakan, yakni sebagai antrean untuk pemrosesan video-video unggahan pengguna serta satu instansi lagi lagi yang bertindak sebagai event bus untuk event-event yang berhubungan dengan video. Instansi Kafka yang bertindak sebagai event bus akan menampung event-event seperti penghapusan video, pembuatan video baru, pengeditan video, dan lain sebagainya. Service-service yang memiliki kepentingan terhadap event-event tersebut dapat terhubung sebagai konsumer dan melakukan apa yang hendak dilakukan setiap kali event yang diinginkan terjadi. Contoh konkritnya adalah service Search yang terhubung dengan event bus dapat mendengarkan event penghapus video, dan ketika event itu terjadi, service Search dapat menghapus index dari video yang dihapus tersebut.
Keputusan untuk menggunakan Kafka secara eksklusif di sini didasari oleh hasil riset singkat saya yang menghasilkan kesimpulan bahwa untuk skala besar Kafka lebih dapat diandalkan dibandingkan pilihan sejenisnya seperti RabbitMQ. Kafka dapat dengan mudah menangani data dalam ukuran petabyte dengan desain yang memang dirancang untuk dapat di-scale secara horizontal. Salah satu artikel yang menjadi acuan saya dalam pemilihan Kafka ini adalah artikel ini dari Quix.
Service Encoder
Service Encoder bekerja dengan cara mengambil data dari antrean video unggahan, lalu memprosesnya, dan hasil akhirnya adalah berupa video unggahan tersebut dalam bentuk dan resolusi yang berbeda-beda.
Yang menarik adalah dalam service ini proses encoding untuk masing-masing resolusi dilakukan secara paralel. Keputusan ini didasarkan pada kebutuhan untuk membuat waktu pengunggahan video lebih cepat, karena jika dilihat pada seksi perencanaan kapasitas di atas, rata-rata panjang video adalah 8 menit dan rata-rata resolusi yang diunggah adalah 1080p. Jika proses encoding dilakukan secara sekuensial tentu akan memakan waktu yang lebih lama dan waktu yang dibutuhkan untuk menyelesaikan prosesnya akan meningkat drastis seiring makin besarnya ukuran video yang perlu diproses. Selain itu, keputusan untuk melakukan proses encoding secara paralel adalah agar spesifikasi server masing-masing encoder dapat diatur dan disesuaikan dengan lebih bebas. Misalnya untuk encoding dengan resolusi tinggi dapat dilakukan menggunakan server dengan berspesifikasi tinggi, dan semakin rendah resolusinya, beban kerja dapat dikerjakan oleh server dengan spesifikasi yang lebih rendah pula.
Lapisan Caching
Pada beberapa service seperti Catalog dan Comment dapat dilihat bahwa terdapat pemanfaatan Redis sebagai lapisan caching. Mungkin melihat hal ini sebagian pembaca akan bertanya, "Kenapa tidak semua service memanfaatkan caching?" Jawabannya untuk sekarang service yang saya rasa paling banyak berinteraksi dengan pengguna adalah service Catalog karena pengguna akan sering menjelajahi katalog video, serta service Comment karena setiap kali pengguna menonton video maka komentar-komentar di video itu juga akan diambil dari service Comment.
Adapun untuk service lain, saya belum melihat manfaat dalam penggunaan caching. Bisa jadi kedepannya saya akan menambahkannya jika saya melihat manfaatnya.
Live Streaming
Jika dilihat pada referensi-referensi desain sistem untuk sistem live streaming seperti ini di YouTube, maka biasanya akan dijumpai penggunaan PoP server, atau point of presence server. Ini adalah server-server yang tersebar di penjuru dunia yang bertugas untuk sebagai poin kontak pertama dengan pengguna yang hendak melakukan live streaming sehingga data-data live stream dari pengguna dapat lebih cepat sampai ke jaringan internal sistem melalui PoP server ini yang kemudian bertugas mengirim data-data tersebut dengan memanfaatkan jaringan berkecepatan tinggi ke server lain yang bertugas memproses data live streaming menjadi beberapa resolusi dan format. Kurang lebih seperti berikut alurnya:
Tetapi di seri ini saya bleum ada rencana untuk menggunakan server ini, karena perlunya penyebaran server secara fisik, yang berarti perlu adanya penggunaan cloud computing. Hal ini saya hindari karena saya tidak ingin mengeluarkan banyak biaya selama menulis seri ini.
Adapun untuk protokol yang saya pilih untuk penanganan live streaming adalah protokol RTMP. Protokol ini adalah protokol paling populer untuk kebutuhan live streaming dan merupakan protokol live streaming yang digunakan oleh platform-platform besar seperti YouTube, Twitch, Restream, dan banyak platform lainnya.
Selain itu, hal lain yang barangkali cukup gamblang jika dilihat dari diagram di atas adalah keputusan untuk tidak menggunakan antrean ketika memproses live streaming. Keputusan ini dibuat agar latensi layar-ke-layar untuk live streaming bisa dipangkas dan lebih mudah diprediksi, karena jika data dari sesi live streaming pengguna harus mengantre di antrean yang sama dengan video unggahan pengguna lainnya maka yang terjadi adalah 2 hal berikut:
Latensi akan lebih lambat daripada jika proses encoding dilakukan secara langsung tanpa melalui antrean terlebih dahulu.
Latensi menjadi lebih tidak dapat diprediksi karena akan sangat bergantung dengan seberapa banyak entri di dalam antrean.
Platform Observability
Saya melihat proyek ini merupakan kesempatan emas untuk mempraktekkan materi yang saya dapat dari bootcamp Software Instrumentation-nya Mas Imre Nagi (@imrenagi). Saya penasaran bagaimana membangun sistem yang pengembangannya sudah memikirkan aspek observability sejak awal.
Adapun pemilihan teknologinya sendiri (LGTM Stack) murni didasarkan pada kefamiliaran saya saja. Di bootcamp yang saya sebutkan di atas, kami dikenalkan dengan beberapa alat dari LGTM Stack seperti Loki dan Grafana. Oleh karena itu, saya memutuskan untuk melanjutkannya saja dengan mengadopsi beberapa alat lain dari LGTM Stack, yakni Mimir dan Tempo.
Bagaimana Caranya Memakan Gajah?
Sejujurnya saya sedikit terkejut dengan kompleksitas hasil desain sistem awal ini. Tetapi saya rasa itu juga merupakan bagian dari proses belajar dan tantangan tersendiri, jika saya bisa mengimplementasikan secara keseluruhan barangkali saya akan mendapatkan lebih banyak pengalaman berharga (serta sekumpulan tulisan dan proyek sampingan baru untuk memenuhi resume :p). Selain itu, sangat mungkin desain arsitekturnya akan berubah seiring berjalannya seri, bisa jadi nantinya akan lebih sederhana, atau bisa jadi berlaku sebaliknya, yakni akan jadi lebih kompleks (lol).
Ada satu pertanyaan yang terus terngiang di benak saya selama proses pembuatan desain arsitektur ini, "Bagaimana caranya saya bisa 'melahap' semua ini?" begitu pikir saya, karena jujur saja kompleksitasnya terasa begitu overwhelming. Tetapi saya teringat akan satu pepatah yang kurang lebih berbunyi,
"How do you eat an elephant?"
"One bite at a time"
Jadi pendekatan yang akan saya lakukan adalah dengan memotong-motong kompleksitas dari desain sistem di atas menjadi masalah-masalah yang lebih kecil yang lebih mudah untuk diselesaikan, lalu fokus ke satu masalah saja di satu waktu. Bisa jadi seri ini akan berjalan dalam waktu lama, tetapi saya rasa tidak masalah selama saya terus mendapatkan pelajaran baru dalam perjalanannya.
Lalu seperti apa eksaknya rencana saya berikutnya?
Di bagian berikutnya saya akan mulai mengerjakan service Uploader terlebih dahulu, karena service ini merupakan salah satu service paling penting.