Membuat Aplikasi Berkinerja Tinggi

Neneng Vela Nevita
ecomindo-dev
Published in
5 min readJan 25, 2021

Kinerja aplikasi (performance) sering menjadi masalah dalam pengembangan aplikasi. Penyebabnya adalah kompleksitas kalkulasi dan tingginya volume transaksi. Artikel ini akan menjelaskan metode untuk membuat aplikasi yang mampu memproses 1 juta transaksi dalam waktu 5 menit atau 3.333 transaksi per-detik.

Untuk mensimulasikan transaksi, kita akan membuat sebuah modul yang berperan sebagai Producer. Modul producer akan menghasilkan (generate) data untuk diproses. Lalu kita perlu membuat modul lain yang berperan sebagai Consumer. Modul consumer ini yang akan memproses dan merekam data transaksi ke database.

Alur kerja sistem adalah sebagai berikut:

  1. Modul Producer menghasilkan (generate) record transaksi.
  2. Modul Producer mem-publish record transaksi sebagai pesan (message) ke streaming system (Kafka)
  3. Modul Consumer subscribe ke Kafka. Setiap ada data baru, secara otomatis modul consumer akan memproses data transaksi dan merekamnya ke Database.
  4. Modul consumer harus dibuat bisa bekerja secara paralel dan terdistribusi, tujuannya agar proses dapat dilakukan dengan cepat. Oleh karenanya, data transaksi harus bisa diproses secara independen dan tidak harus diproses secara berurutan. Modul consumer juga tidak boleh menyimpan status (harus bersifat stateless).
  5. Di sisi lain, kita perlu mengendalikan jumlah instance paralel tersebut agar tidak melebihi batas tertentu. Ini karena infrastruktur lain seperti RDBMS (PostgreSQL) memiliki keterbatasan untuk menangani transaksi per-detiknya.

Platform dan Infrastruktur

Kafka, Aplikasi, dan PostgreSQL masing-masing berjalan pada Virtual Machine Azure dengan 2 Core dan memory 4 GB. Kami juga menggunakan kubernetes sebagai orchestrator untuk mengatur traffic dan jumlah instance modul consumer.

Langkah-Langkah

Untuk membangun aplikasi, ikuti langkah-langkah sebagai berikut:

Langkah 1: Setup Streaming System Topic dan Databasse Table

Buat kafka topic terlebih dahulu dengan menjalankan command dibawah ini pada server kafka. Nama topik yang digunakan adalah “poc”. Kemudian, set partisi di nilai 5 agar topik tersebut dapat diproses oleh 5 proses secara paralel.

bin/kafka-topics.sh -create -topic poc -bootstrap-server <url>:9092 -partition 5

Kemudian, buat tabel message di database dengan kolom message_id, generate_date dan receive_date. Kolom message_id dan generate_date akan dikirimkan dari aplikasi dan receive_date merupakan timestamp pada database dan akan otomatis terisi pada saat data masuk ke tabel tersebut.

CREATE TABLE public.message
(
message_id numeric NOT NULL,
generate_date numeric,
receive_date bigint DEFAULT
(date_part(‘epoch’::text, now()) * (1000)::double precision),
CONSTRAINT message_pkey PRIMARY KEY (message_id)
)

Langkah 2: Setup Project

Buka spring intializr di https://start.spring.io/, kemudian isi project metadata dan pilih dependency yang dibutuhkan, antara lain Spring Web, Spring for Apache Kafka, Spring Data JPA dan PostgreSql Driver. Lalu, klik GENERATE.

Setelah klik generate, hasil unduhannya diextract kemudian buka proyek tersebut dengan IDE. Pada file pom.xml nya sudah ada dependency yang dibutuhkan, tapi kita perlu menambahkan dependency untuk hikari dan json. HikariCP dibutuhkan agar koneksi ke database bisa dilakukan dengan metode connection pool. Tujuannya agar koneksi dapat reusable, sehingga koneksi ke database tidak harus dilakukan berkali-kali.

Kemudian buat package baru bernama model, config, dao, controller dan service di com.learning.poc.

Langkah 3: Konfigurasi

Atur konfigurasi PostgreSql, HikariCP dan Kafka pada file application.properties yang terletak di folder resources.

Daftar konfigurasi dan penjelasan selengkapnya ada pada halaman berikut.

Untuk mengaktifkan async di aplikasi, kita perlu membuat configuration file yang terdapat anotasi @EnableAsync. Buat file baru bernama AsyncConfig.java di package config.

Langkah 4: Model Message.java dan MessageDao.java

Buat Message.java di package model.

Buat repository untuk objek message di package dao.

Langkah 5 : Kafka Producer

Pada package service, buat kelas ProducerService.java. Didalamnya terdapat objek KafkaTemplate yang dapat mempublish pesan ke Kafka. Penggunaan variable n agar mudah menyesuaikan jumlah message yg akan dikirim ke kafka topik.

Pesan yang dikirim ke kafka berupa json berisi sequenceNumber dan generatedTimestamp tujuannya agar kita tahu berapa waktu yang dibutuhkan untuk menyimpan data ke database dalam jumlah n.

Langkah 6: Kafka Consumer

Pada package service, buat ConsumerService.java yang didalamnya terdapat kafka consumer dan sebuah function untuk mengubah pesan yang di-consume menjadi objek message dan menginsert objek tersebut ke database.

Langkah 7: Controller

Pada package controller, buat KafkaController dengan endpoint “/kafka/producemessage/{n}” dimana n merupakan jumlah message yang ingin digenerate.

Pengujian

Pengujian dilakukan dengan menjalankan query berikut untuk memastikan apakah aplikasi ini dapat memproses 1 juta pesan dalam waktu kurang dari 5 menit.

select count(message_id), max(receive_date)-min(generate_date) as gen_to_ins from public.message;

message_id = id pesan
receive_date = waktu pesan masuk ke database
generate_date = waktu pesan digenerate oleh modul Producer

Query tersebut menampilkan jumlah pesan yang masuk / count(message_id) dan selisih antara waktu pesan terakhir masuk kedalam database/ max(receive_date) dengan waktu pesan pertama digenerate oleh aplikasi/ min(generate_date).

Berikut hasil pengujian aplikasi

Waktu yang dibutuhkan untuk mengenerate dan mempublish pesan ke kafka adalah 1 menit 15 detik.

Waktu yang dibutuhkan untuk mengenerate 1 juta data hingga masuk seluruhnya ke database adalah 274,420 detik atau setara 4,5 menit.

Takeaway

Contoh aplikasi di atas terbukti dapat melakukan pemrosesan 1 juta data hanya dalam waktu 4,5 menit. Dari contoh di atas, ada beberapa hal yang dapat dijadikan catatan:

  1. Algoritma aplikasi harus dibuat sedemikian rupa agar data transaksi dapat diproses secara paralel, independen, dan stateless. Data transaksi tidak harus diproses secara terurut. Pemrosesan suatu record tidak tergantung hasil proses record lainnya. Tidak ada data status yang disimpan di suatu instance tertentu saja.
  2. Partisi Kafka harus disesuaikan dengan kapasitas RDBMS. Jika instance terlalu banyak, ada kemungkinan RDBMS tidak mampu menangani “banjir” request tersebut.
  3. Koneksi ke Database harus menggunakan connection pool, agar koneksi reusable sehingga tidak menghabiskan waktu untuk rekoneksi.
  4. Tentukan jumlah record-per-batch yang optimal agar proses fetch ke Kafka dapat diminimalkan.
  5. Pastikan proses I/O dilakukan secara asynchronous, sehingga tidak terjadi blocking. Kita perlu memahami konsep single-thread application dan asynchronous secara tepat, sehingga tidak salah dalam menerapkannya.
  6. Cek performance dengan melakukan stress-test, jangan berasumsi semua sudah berjalan baik. Gunakan Application Performance Monitoring tools untuk mendiagnosa masalah secara detil.
  7. Lebih jauh, pastikan setiap step dilakukan secara efektif. Pastikan orde algortima sudah optimal dan durasi request ke I/O seperti Database di dalam batas wajar.
  8. Terakhir, hindari penggunaan library yang kurang tepat, seperti penggunaan regex yang tak perlu, exception handling yang tidak efisien, atau lookup ke List berukuran besar (karena List tidak ter-index, sebaiknyta gunakan HashMap).

Editor: AF

--

--