Designning Data-Intensive Applications by Martin Kleppmann

Aplikasi Reliable, Scalable, dan Maintainable

Aldimhr
12-07-2025
12 min read

Aplikasi Reliable, Scalable, dan Maintainable

Seiring dengan meningkatnya kebutuhan akan pengolahan data dalam software, banyak tools baru dikembangkan untuk menangani berbagai kasus yang lebih kompleks. Semakin banyaknya tools baru, pengembangan software menjadi semakin rumit, misalnya, satu sistem bisa menggunakan Memcached untuk caching layer, Elasticsearch untuk full-text search, Redis untuk message queue, dan lain sebagainya.

Karena banyak tools digunakan dalam satu sistem yang sama, muncul banyak kekhawatiran juga, seperti: bagaimana memastikan data diproses dengan benar meskipun terjadi kesalahan? Bagaimana menyediakan performa yang konsisten kepada pengguna meskipun ada bagian sistem yang mengalami penurunan performa? Bagaimana sistem dapat menangani lonjakan pengguna secara tiba-tiba?

Banyak faktor menentukan kualitas suatu software, mulai dari keahlian dan pengalaman tim pengembang hingga ketergantungan antar sistem. Martin Kleppmann membagi kekhawatiran tersebut menjadi tiga: reliability (keandalan), scalability (skalabilitas), dan maintainability (kemudahan pemeliharaan).

Reliability

Secara umum, reliability berarti kemampuan sistem untuk tetap beroperasi meskipun terjadi kesalahan pada satu atau lebih komponennya.

Dalam praktiknya, kesalahan sering dilakukan secara sengaja—misalnya dengan mematikan salah satu service—untuk mengidentifikasi bug kritis yang mungkin belum tercover. Dengan begitu, jika kesalahan serupa terjadi secara alami di kemudian hari, sistem sudah siap menangani bug tersebut.

Menurut Kleppmann, reliability terdiri dari tiga bagian: hardware, software dan manusia.

Hardware

Hard disk crash, RAM tidak berfungsi, atau ada penurunan performa di satu komponen, atau mungkin listrik padam merupakan masalah yang umum terjadi dan menyebabkan sistem mati total.

Masalah pada hardware umumnya diatasi dengan menggunakan banyak hardware dengan fungsi yang sama dalam satu sistem sehingga mengurangi kemungkinan terjadinya kesalahan saat sistem berjalan. Misalnya menggunakan banyak hard disk, sehingga ketika salah satu hard disk mengalami masalah, otomatis sistem menggunakan hard disk yang lainnya.

Pendekatan lain yang umum digunakan adalah dengan memindahkan software yang berjalan ke sistem yang lain, jadi misalnya ketika satu server mengalami masalah, secara otomatis software dijalankan menggunakan server yang lain.

Software

Kesalahan yang terjadi dalam sistem tidak hanya disebabkan oleh hardware, bisa juga disebabkan oleh kesalahan sistemik di dalam sistem. Seperti kesalahan yang sulit untuk diantisipasi, karena mereka saling berhubungan antar nodes, sehingga menyebabkan terjadi kegagalan dimana mana yang tidak ada hubungannya dengan hardware, seperti

  • Service yang makan banyak resource sehingga mengganggu service yang lain, seperti yang berkaitan dengan konsumsi CPU, memory, disk space atau network bandwidth.
  • Service dependency yang melambat, menjadi unresponsive atau mulai mengirimkan response yang corrupt.
  • Kesalahan yang beruntun, adanya kesalahan dalam satu service yang berdampak ke service yang lain, sehingga kesalahan mulai muncul di banyak service.

Tidak ada solusi yang cepat untuk mengatasi kesalahan sistemik ini. Tapi, banyak hal kecil yang bisa dilakukan untuk menghindari hal ini, seperti melakukan testing, process isolation, atau secara sengaja mematikan salah satu service, selain itu juga melakukan measuring, monitoring, dan analyzing perilaku sistem di production.

Manusia

Manusia berada dibalik sebuah sistem, manusia yang melakukan design, membuat dan mempertahankan sistem agar tetap berjalan sesuai fungsinya. Lalu, bagaimana cara kita memastikan agar sistem tetap reliable dan mempersempit kesalahan yang dapat dibuat?

  • Membuat sistem dengan design sedemikian rupa agar terhindari dari error seminimal mungkin.
  • Membuat area yang aman terhadap error. Misalnya, menyediakan testing environment sehingga bebas dalam melakukan eksperimen dan dapat explore sistem menggunakan real data, tanpa berdampak kepada user.
  • Melakukan testing disemua level, dari unit test hingga integration test semua sistem.
  • Membuat sistem mudah dalam melakukan recovery dari kesalahan manusia, untuk memperkecil kesalahan yang dapat ditimbulkan. Seperti, membuat fitur fast roll back configuration changes, rollout kode baru secara bertahap (sehingga ketika terdapat bugs akan berdampak hanya ke sebagaian kecil dari sistem saja).
  • Set up monitoring yang detail dan jelas, seperti performance metrics dan error rates.

Seberapa penting reliability?

Bugs di aplikasi dapat menyebakan hilangnya produktifitas penggunanya dan matinya sistem juga bisa menyebakan hilangnya revenue yang seharusnya didapat dan mungkin juga dapat memperburuk reputasi dimata penggunanya.

Meskipun di aplikasi ‘noncritical’, kita punya responsibility ke pengguna. Tapi ada juga situasi dimana kita memilih untuk mengorbankan reliability, misalnya untuk mengurangi cost development dalam develop prototype product dari market yang belum terbukti, atau mengurangi operasional cost, misalnya untuk layanan dengan margin keuntungan yang sangat tipis — tapi kita harus benar benar sadar dampaknya ketika kita mengabaikan ini.

Scalability

Meskipun hari ini aplikasi bekerja secara reliable, belum tentu dengan seiring bertambahnya load pada aplikasi akan tetap reliable. Misalnya, aplikasi yang berjalan tanpa kendala ketika diakses oleh 10 user, bagaimana jika dalam satu waktu user bertambah hingga 100 kali lipat, atau mungkin aplikasi bekerja dengan lancar ketika mengolah 200 data, tapi bagaimana jika satu waktu data bertambah menjadi 1 juta data?

Scalability merupakan jawaban dari pertanyaan “Opsi apa yang bisa kita pilih ketika load pada aplikasi bertambah?” dan “Bagaimana kita bisa menambahkan computing resource untuk mengatasi load yang bertambah?”

Load

Pertama, kita perlu tau load saat ini di sistem yang berjalan seperti apa, setelah itu kita bisa cari tau bagaimana cara mengatasi ketika load bertambah signifikan. Banyak parameter yang bisa digunakan untuk mengukur load dalam sistem, ini tergantung dari design sistem itu sendiri, seperti request per second ke web server, rasio read dan write ke database, jumlah user yang sedang aktif, hit rate ke cache, atau yang lainnya.

Contoh kasus menggunakan data Twitter yang dipublish November 2012. Terdapat dua operasi utama dalam twitter, yaitu

  1. Post Tweet

    User dapat post tweet baru ke followernya (rata-rata 4.6k req/sec, paling banyak ada di 12k req/sec)

  2. Home Timeline

    User dapat melihat tweet yang diposting oleh orang yang difollow (300k req/sec)

Kesulitan Twitter dalam melakukan scalling sistemnya bukan berkaitan dengan banyaknya orang melakukan post tweet, tapi berkaitan dengan fan-out — setiap user mengikuti (following) banyak user lain, dan setiap user diikuti (followers) juga oleh banyak user lain.

Secara umum, terdapat dua cara dalam menangani kasus ini

Pertama, ketika user akses home timeline, sistem perlu lihat siapa yang difollow user tersebut, lalu ambil semua tweet dari setiap user, semua tweet tersebut akan dijadikan satu dan diurutkan berdasarkan waktu. Dalam query, kita bisa tulis seperti ini

SELECT tweets.*, users.* FROM tweets
INNER JOIN users   ON tweets.sender_id    = users.id
INNER JOIN follows ON follows.followee_id = users.id
WHERE follows.follower_id = current_user

Kedua, membuat cache untuk setiap home timeline user, seperti mailbox dari banyak tweet untuk setiap user penerimanya. Ketika user melakuan post tweet, sistem akan melihat user followers-nya, dan insert tweet baru tersebut ke setiap home timeline cache. Ketika follower user tersebut akses home timeline, user akan menarik data di cache, sehingga tidak banyak operasi berjalan, karena data sudah ada.

Awalnya, Twitter menggunakan pendekatan pertama, tapi sistem kesulitan untuk mengimbangi beban query home timeline, sehingga beralih menggunakan pendekatan kedua. Pendekatan tersebut berjalan dengan lancar, karena jumlah publish tweet lebih kecil dari akses ke home timeline, jadi dalam kasus ini lebih prefer untuk sistem bekerja lebih banyak ketika write dibandingkan ketika read.

Meski begitu, terdapat kekurangan dari pendekatan kedua, ketika user memiliki followers dengan angka yang sangat besar, katakanlah 30 juta followers, artinya sistem harus melakukan write 30 juta kali ke cache home timeline.

Dari contoh Twitter diatas, distribusi dari follower per user adalah kunci dari parameter load untuk selanjutnya mencari tahu scalability sistem. Setiap aplikasi mungkin memiliki karakteristiknya sendiri, tapi kita bisa menggunakan prinsip yang sama untuk mencari tahu load parameternya.

Pada akhirnya twitter menggunakan kedua pendekatan tersebut untuk use case yang berbeda. User dengan followers yang relative kecil akan menggunakan pendekatan kedua, sedangkan untuk user dengan followers yang banyak akan menggunakan pendekatan pertama, sehingga ketika follower akses home timeline mereka, post akan ditarik dan dimerge ke cache home timeline.

Performance

Ketika kita sudah tahu parameter load dari sistem kita, selanjutnya kita bisa mencari tahu apa yang terjadi ketika load bertambah signifikan. Kita bisa cari tahu dengan dua cara

  1. Ketika kita menambah load parameter dan sistem resource tidak berubah, apakah performa dari sistem berdampak?
  2. Ketika kita menambah load parameter, berapa banyak yang kita butuhkan untuk menambah resource jika kita ingin performa tidak berubah?

Kedua pertanyaan tersebut membutuhkan angka dari performa, jadi mari kita cari tahu performa dari sistem itu sendiri.

Di online sistem, kita biasanya melihat response time—waktu yang dibutuhkan ketika client melakukan request hingga mendapatkan response dari server—untuk mengukur performa dari sistem.

Meskipun kita melakukan request yang sama berkali kali, akan menghasilkan response time yang berbeda, maka dari itu biasanya kita tidak melihat response time tunggal, tapi distribusi nilai yang dapat diukur.

Umumnya, report dari performance menggunakan rata-rata (mean)—jumlah dari n value, dibagi total dari n. Tapi, mean bukan metric yang bagus jika kita ingin melihat response time, karena ini tidak menggambarkan dengan jelas berapa banyak user yang mengalami delay.

Biasanya, response time dibaca menggunakan percentil. Jika, kita punya data dari response time lalu mengurutkannya dari kecil hingga besar, maka median adalah titik tengahnya. Misalnya, jika kita punya median response time 200ms, artinya setengah dari user mendapat response time kurang dari 200ms dan sisanya lebih dari itu.

Median bisa disebut dengan percentil ke 50, atau bisa ditulis p50.

Dalam melihat outlier yang lebih buruk, kita bisa melihat dalam percentil yang lebih besar: p95, p99 dan p99.9 umum digunakan. Contoh ketika kita memiliki data response time dari user p95 adalah 1.5s, artinya 95 dari 100 user memiliki response time kurang dari 1.5s dan 5 dari 100 user mengalami response time lebih dari 1.5s.

Disisi lain jika kita menggunakan percentil yang ekstrim, misalnya p99.99, butuh usaha dan biaya besar, karena sering dipengaruhi oleh noise atau unpredictable spikes.

Queue delay biasanya menjadi penyebab utama dalam response time jika menggunakan percentil yang tinggi. Server hanya bisa mengolah process dalam jumlah yang sedikit secara paralel, hanya membutuhkan sebagian kecil process yang lambat untuk menahan process berikutnya — proses ini disebut juga dengan head-of-line blocking.

Meskipun process di server terjadi sangat cepat, user biasanya mengalami proses yang lambat karena menunggu process sebelumnya dikirimkan. Karena efek ini, direkomendasikan pengukuran response time dilakukan di client side.

Jadi, saat kita membuat uji beban pada sistem, baiknya menggunakan beberapa user untuk hit path yang sama dalam waktu berdekatan, karena jika kita melakukan request untuk user kedua setelah user pertama menyelesaikan requestnya, antrean dalam sistem akan terlihat lebih pendek dalam pengujian dibandingkan kondisi nyata. Akibatnya, hasil pengukuran menjadi tidak akurat atau bias.

Pendekatan untuk mengatasi masalah load

Pendekatan dalam mengatasi scalability, umumnya terdapat dua, scaling up (vertical scaling, pindah ke mesin yang lebih powerful) dan scaling out (horizontal scaling, membagi load ke banyak mesin).

sistem yang berjalan disatu mesin umumnya lebih simple dibanding dengan sistem yang berjalan dibanyak mesin. Tapi, masalah umumnya, mesin yang powerfull butuh biaya yang tidak sedikit, sehingga scaling out jadi opsi yang bisa dipilih.

Beberapa mesin memiliki sifat elastis, jadi ketika load bertambah maka mesin otomatis menambah resourcenya, beberapa mesin yang lain melakukan scaling up secara manual. Sistem yang elastis sangat dibutuhkan ketika load bertambah dengan cepat dan sulit diprediksi.

Arsitektur dari sistem yang menjalankan operasi yang besar biasanya sangat spesifik terhadap aplikasinya, maksudnya tidak ada arsitektur yang dapat menyelesaikan masalah load di semua kasus. Masalah yang mungkin muncul seperti volume read data, volume write data, volume simpan data, kompleksitas data, dan banyak lainnya.

Contoh, sistem yang didesin untuk handle 100.000 req/sec, dan setiap request memiliki ukuran 1KB, berbeda dengan sistem yang di design untuk 3 req/min tapi setiap requestnya memiliki ukuran 2GB—meskipun kedua sistem memiliki throughput yang sama.

Sistem yang memiliki scalability yang baik harus diukur berdasarkan asumsi, operasi mana yang banyak digunakan dan operasi mana yang jarang digunakan. Jika asumsi ini salah, maka effort engineering untuk scaling akan sia-sia, dan lebih buruk lagi kontraproduktif. Di tahap awal startup atau product yang belum teruji di market, umumnya lebih memerlukan iterasi yang sering pada fitur produk dibanding melakukan analisa agar mesin bisa scaling dimasa depan.

Maintainability

Pengeluaran paling besar dalam sistem bukan pada saat pertama kali dibuat atau initial development, tapi saat mulai masuk ke tahap maintenance—seperti fixing bugs, menjaga agar sistem tetap berjalan, mencari tau kegagalan dalam sistem, beralih ke platform yang baru, mengubah sistem untuk use case baru, dan menambahkan fitur baru.

Sayangnya, banyak orang yang bekerja di software sistem tidak suka maintenance yang disebut dengan legacy sistem—seperti memperbaiki kesalahan orang lain, atau bekerja di platform yang sudah usang, atau memaksa sistem melakukan sesuatu yang sebenarnya tidak ditujukan untuk itu.

Meski begitu, kita bisa membuat design software sejak awal untuk mempersempit kesulitan saat maintenance dan dengan begitu kita menghindari legacy sistem.

Operability

Operability merupakan kemampuan sistem untuk dioperasikan secara efisien dan andal dalam lingkungan production, biasanya yang melakukan operasi (monitoring) di lingkungan production adalah operations team (Ops/DevOps/SRE).

Monitoring yang bagus seringkali dapat mengatasi keterbatasan software yang buruk, dan software yang baik tidak akan reliable dengan monitoring yang buruk.

Operability yang bagus artinya membuat task rutin menjadi lebih mudah, sehingga operations team bisa fokuskan effort mereka ke aktifitas yang high-value.

Simplicity

Project software yang kecil dapat memiliki kode yang sangat sederhana dan ekspresif, tapi ketika project menjadi lebih besar, biasanya akan mejadi sangat kompleks dan sulit untuk dipahami. Kompleksitas tersebut memperlambat orang lain yang akan bekerja untuk sistem tersebut, lebih parah lagi ini akan menambah cost untuk maintenance. Software yang terperangkap di kompleksitas biasa disebut juga dengan big ball of mud.

Membuat sistem yang simple bukan berarti menghilangkan fungsionalitasnya, simple disini bisa diartikan membuang kompleksitas yang tidak dibutuhkan.

Salah satu cara untuk menangani ini adalah dengan abstraction. Abstraction yang bagus bisa menyembunyikan detail implementasi dibalik antarmuka yang bersih dan mudah dipahami. Misalnya, high-level programming languages merupakan abstraction yang menyembunyikan machine code, CPU register, dan syscalls. SQL juga merupakan abstraction yang menyembunyikan kompleksitas struktur data di memori dan disk, query planning, indexing, dan execution.

Meski begitu, mencari abstraction yang bagus sangat susah. Di distributed sistem, meskipun ada banyak algoritma yang bagus, masih kurang jelas bagaimana kita harus mengemasnya ke dalam abstraksi yang membantu kita menjaga kompleksitas sistem pada tingkat yang mudah dikelola.

Evolvability

Sistem requirement bukan berarti tidak akan berubah selamanya. sistem cenderung mengalami perubahan yang konstan: prioritas bisnis berubah, user request fitur baru, platform baru menggantikan platform lama, dan lainnya.

Perubahan yang terjadi dalam sistem harus diupayakan semudah mungkin, sehingga dapat beradaptasi pada perubahan requirement.


Kleppmann, M. (2017). Designing data-intensive applications: The big ideas behind reliable, scalable, and maintainable sistems. O’Reilly Media.

Tags
  • Reliability
  • Scalability
  • Maintainability
  • System Design
  • Book
View All Posts