Back

Membangun Load Testing Tool dengan Golang

Membangun Load Testing Tool dengan Golang

Apa Itu Load Testing?

Load testing adalah salah satu jenis pengujian performa yang dilakukan untuk mengetahui bagaimana sebuah aplikasi atau sistem merespons ketika menerima beban tertentu. Pengujian ini bertujuan untuk mengukur stabilitas, kecepatan respons, dan kemampuan sistem dalam menangani sejumlah pengguna atau permintaan secara bersamaan.

Load testing diperlukan ketika sebuah perangkat lunak telah dirilis ke lingkungan production karena jumlah pengguna yang mengakses sistem tidak selalu dapat diprediksi. Seiring bertambahnya pengguna dan permintaan yang masuk secara bersamaan, aplikasi dapat mengalami penurunan performa apabila tidak dirancang untuk menangani beban tersebut. Melalui load testing, tim pengembang dapat memahami batas kemampuan sistem, mengidentifikasi potensi bottleneck, dan memastikan aplikasi tetap stabil saat menerima peningkatan trafik.

Selain itu, load testing juga dapat digunakan sebagai alat untuk menilai apakah spesifikasi sistem, baik secara vertikal maupun horizontal, sudah siap untuk digunakan di lingkungan production. Skala vertikal (vertical scaling) mengacu pada peningkatan kapasitas sumber daya pada satu server, seperti menambah CPU, RAM, atau storage. Sementara itu, skala horizontal (horizontal scaling) dilakukan dengan menambah jumlah server atau instance sehingga beban dapat didistribusikan ke beberapa mesin. Melalui pengujian ini, tim dapat memperoleh gambaran mengenai kapasitas infrastruktur dalam menangani beban yang diharapkan. Oleh karena itu, load testing tidak hanya berfungsi untuk mengukur kemampuan aplikasi, tetapi juga untuk mengevaluasi tingkat kesiapan infrastruktur sebelum sistem diluncurkan ke production.

Metrik yang Diukur dalam Load Testing

Load testing tidak hanya berfokus pada jumlah request yang berhasil diproses oleh aplikasi, tetapi juga mengukur berbagai metrik yang dapat digunakan untuk mengevaluasi performa dan stabilitas sistem.

Response Time

Response time merupakan waktu yang dibutuhkan aplikasi untuk memproses sebuah request hingga memberikan respons kepada pengguna. Semakin rendah nilai response time, semakin cepat aplikasi merespons permintaan.

Throughput

Throughput menunjukkan jumlah request yang dapat diproses oleh sistem dalam periode waktu tertentu. Metrik ini digunakan untuk mengetahui kapasitas aplikasi dalam menangani beban kerja.

Requests Per Second (RPS)

Requests Per Second atau RPS mengukur berapa banyak request yang dapat diproses aplikasi setiap detik. Nilai ini sering digunakan untuk membandingkan performa antar konfigurasi sistem.

Error Rate

Error rate menunjukkan persentase request yang gagal diproses oleh aplikasi. Tingginya nilai error rate dapat mengindikasikan adanya bottleneck, resource exhaustion, atau masalah pada konfigurasi sistem.

Concurrent Users

Concurrent users mengukur jumlah pengguna aktif yang mengakses aplikasi pada waktu yang bersamaan. Metrik ini membantu memahami bagaimana sistem berperilaku saat menerima banyak koneksi secara simultan.

Resource Utilization

Selain metrik pada aplikasi, load testing juga perlu memantau penggunaan sumber daya seperti CPU, memory, disk I/O, dan network throughput. Informasi ini penting untuk mengidentifikasi komponen yang menjadi bottleneck ketika sistem berada di bawah beban tinggi.

Membangun Load Testing Sederhana dengan Golang

 

Contoh Gambar Dashboard di Terminal saat load testing berjalan.

Setelah memahami konsep dasar dan metrik yang digunakan dalam load testing, langkah berikutnya adalah membuat load generator sederhana menggunakan Golang. Pada implementasi ini, kita akan memanfaatkan goroutine, channel, dan worker pool untuk menghasilkan request secara paralel ke beberapa endpoint serta menampilkan statistik pengujian secara real-time.

 

Mengapa Menggunakan Golang Dibandingkan JMeter?

JMeter merupakan salah satu tool load testing yang sudah matang dan banyak digunakan untuk menguji aplikasi web, API, database, maupun berbagai protokol lainnya. Dengan antarmuka yang lengkap dan fitur yang kaya, JMeter sering menjadi pilihan utama ketika kebutuhan pengujian dapat dipenuhi menggunakan konfigurasi yang sudah tersedia.

Namun, pada beberapa kasus, pengembang membutuhkan fleksibilitas yang lebih tinggi dibandingkan sekadar membuat skenario pengujian melalui konfigurasi. Di sinilah Golang menjadi menarik.

Dengan Golang, kita dapat membangun load testing tool yang sepenuhnya disesuaikan dengan kebutuhan sistem yang sedang diuji. Misalnya, menambahkan autentikasi khusus, membuat alur bisnis yang kompleks, mengintegrasikan pengujian dengan service internal, atau mengumpulkan metrik yang tidak tersedia secara bawaan pada tool load testing umum.

Selain itu, Golang memiliki dukungan konkurensi yang sangat baik melalui goroutine sehingga proses pembuatan request dalam jumlah besar dapat dilakukan dengan implementasi yang relatif sederhana. Hasil akhirnya juga berupa satu binary yang mudah dijalankan di berbagai lingkungan tanpa memerlukan instalasi tambahan.

Meskipun demikian, bukan berarti Golang selalu menjadi pilihan yang lebih baik dibandingkan JMeter. Jika tujuan utama adalah melakukan load testing dengan cepat menggunakan tool yang sudah siap pakai, JMeter sering kali lebih praktis. Sebaliknya, jika diperlukan kontrol penuh terhadap proses pengujian dan perilaku load generator, membangun tool sendiri menggunakan Golang dapat menjadi pilihan yang lebih tepat.

 

Implementasi Load Testing dengan Golang

Berikut implementasi Load Testing Sederhana dengan Golang:

Menentukan Endpoint Pengujian

Langkah pertama adalah menentukan endpoint yang akan menjadi target pengujian. Pada contoh ini, pengujian dilakukan terhadap beberapa halaman pada website, yaitu halaman utama, halaman blog, halaman proyek, dan halaman tentang.

Dengan menguji lebih dari satu endpoint, kita dapat memperoleh gambaran yang lebih baik mengenai performa aplikasi pada berbagai jenis halaman. Setiap endpoint dapat memiliki karakteristik yang berbeda, baik dari sisi jumlah data yang diproses, query database yang dijalankan, maupun kompleksitas rendering yang dilakukan oleh server.

Berikut daftar endpoint yang akan digunakan dalam pengujian:

var endpoints = []string{
	"http://fajrulaulia.com/",
	"http://fajrulaulia.com/blogs",
	"http://fajrulaulia.com/projects",
	"http://fajrulaulia.com/about",
}

Seluruh endpoint tersebut akan menerima request secara paralel selama proses load testing berlangsung sehingga kita dapat membandingkan tingkat keberhasilan request dan throughput yang dihasilkan pada masing-masing endpoint.

Menyiapkan Konfigurasi Load Test

Setelah menentukan endpoint yang akan diuji, langkah berikutnya adalah menyiapkan konfigurasi load test. Konfigurasi ini digunakan untuk mengatur jumlah request yang akan dikirim, tingkat concurrency yang digunakan, serta interval pembaruan statistik selama pengujian berlangsung.

Pada contoh ini, setiap endpoint akan menerima satu juta request dengan seratus worker yang berjalan secara paralel. Selain itu, dashboard akan diperbarui setiap 100 milidetik agar perkembangan pengujian dapat dipantau secara real-time.

const (
	RequestsPerEndpoint = 1000000
	Concurrency         = 100
	RefreshRate         = 100 * time.Millisecond
)

Nilai RequestsPerEndpoint menentukan jumlah request yang akan dikirim ke setiap endpoint. Semakin besar nilainya, semakin lama pengujian berlangsung dan semakin banyak beban yang diberikan kepada sistem.

Nilai Concurrency menentukan jumlah worker yang akan berjalan secara bersamaan. Pada implementasi ini, setiap worker akan mengirim request secara paralel sehingga dapat mensimulasikan banyak pengguna yang mengakses aplikasi dalam waktu yang sama.

Sementara itu, RefreshRate digunakan untuk mengatur seberapa sering dashboard memperbarui statistik pengujian. Pengaturan ini tidak memengaruhi jumlah request yang dikirim, tetapi membantu memantau progres load testing secara real-time selama program berjalan.

Membuat Worker Pool dengan Goroutine

Untuk menghasilkan beban secara paralel, kita dapat memanfaatkan goroutine dan worker pool. Pendekatan ini memungkinkan sejumlah worker berjalan secara bersamaan untuk memproses request tanpa harus membuat jutaan goroutine dalam satu waktu.

Pada implementasi ini, sebuah channel digunakan sebagai antrean pekerjaan yang berisi request yang akan diproses. Setiap worker akan mengambil pekerjaan dari channel tersebut dan mengirimkan request ke endpoint yang sedang diuji.

jobs := make(chan struct{}, RequestsPerEndpoint)

for i := 0; i < RequestsPerEndpoint; i++ {
	jobs <- struct{}{}
}
close(jobs)

Setelah antrean pekerjaan dibuat, program menjalankan sejumlah worker sesuai nilai Concurrency. Setiap worker dijalankan dalam goroutine terpisah sehingga request dapat dikirim secara paralel.

var workers sync.WaitGroup

for i := 0; i < Concurrency; i++ {
	workers.Add(1)

	go func() {
		defer workers.Done()

		for range jobs {
			// proses request
		}
	}()
}

Dengan pendekatan ini, jumlah goroutine tetap terkendali karena hanya worker yang aktif yang akan memproses seluruh antrean pekerjaan. Strategi ini lebih efisien dibandingkan membuat satu goroutine untuk setiap request, terutama ketika jumlah request yang dikirim mencapai ratusan ribu hingga jutaan request selama proses load testing.

Mengirim Request Secara Paralel

Setelah worker pool berhasil dibuat, setiap worker akan mengambil pekerjaan dari channel dan mengirimkan HTTP request ke endpoint yang sedang diuji. Pada tahap ini, request dikirim menggunakan HTTP client yang telah dikonfigurasi sebelumnya.

resp, err := client.Get(url)

if err != nil {
	atomic.AddInt64(&stats[url].Failed, 1)
	atomic.AddInt64(&stats[url].Completed, 1)
	continue
}

resp.Body.Close()

if resp.StatusCode >= 200 && resp.StatusCode < 400 {
	atomic.AddInt64(&stats[url].Success, 1)
} else {
	atomic.AddInt64(&stats[url].Failed, 1)
}

atomic.AddInt64(&stats[url].Completed, 1)

Setiap request yang berhasil maupun gagal akan dicatat ke dalam statistik pengujian. Selain itu, response body perlu ditutup setelah digunakan untuk mencegah kebocoran resource dan memastikan koneksi dapat digunakan kembali oleh HTTP client.

Mengumpulkan Statistik Pengujian

Selama proses load testing berlangsung, aplikasi perlu mengumpulkan informasi mengenai jumlah request yang berhasil, jumlah request yang gagal, serta total request yang telah diproses. Untuk menjaga konsistensi data ketika diakses oleh banyak goroutine secara bersamaan, implementasi ini menggunakan operasi atomic.

type Stats struct {
	Completed int64
	Success   int64
	Failed    int64
}

Dengan pendekatan ini, setiap worker dapat memperbarui statistik tanpa perlu menggunakan mutex. Hasilnya, proses pencatatan metrik menjadi lebih ringan dan tetap aman meskipun dijalankan secara paralel oleh banyak goroutine.

Menampilkan Dashboard Real-Time

Agar proses pengujian lebih mudah dipantau, program menampilkan dashboard sederhana yang diperbarui secara berkala menggunakan ticker.

ticker := time.NewTicker(RefreshRate)
defer ticker.Stop()

Dashboard akan menampilkan progres pengujian untuk setiap endpoint, jumlah request yang berhasil, jumlah request yang gagal, serta throughput dalam satuan request per second (RPS). Informasi tersebut diperbarui secara real-time sehingga perkembangan pengujian dapat diamati tanpa harus menunggu seluruh proses selesai.

Pendekatan ini cukup berguna ketika jumlah request yang dikirim sangat besar karena memberikan visibilitas terhadap kondisi sistem selama pengujian berlangsung.

Hasil Pengujian dan Analisis

Setelah seluruh request selesai diproses, dashboard akan menampilkan ringkasan hasil pengujian yang berisi total request, jumlah request berhasil, jumlah request gagal, waktu eksekusi, serta throughput keseluruhan.

Pada implementasi ini, metrik utama yang diamati adalah tingkat keberhasilan request dan throughput sistem. Nilai throughput yang tinggi menunjukkan kemampuan aplikasi dalam memproses request dalam jumlah besar, sedangkan meningkatnya jumlah request gagal dapat menjadi indikasi adanya bottleneck pada aplikasi atau infrastruktur yang digunakan.

Meskipun implementasi ini belum mengukur latency seperti average response time, P95, atau P99, hasil pengujian tetap dapat digunakan untuk memperoleh gambaran awal mengenai kemampuan sistem dalam menangani beban yang diberikan. Pendekatan ini juga dapat menjadi fondasi untuk mengembangkan tool load testing yang lebih kompleks sesuai kebutuhan pengujian di lingkungan production.

Kesimpulan

Golang menawarkan fleksibilitas yang tinggi dalam membangun tool load testing yang dapat disesuaikan dengan kebutuhan aplikasi dan infrastruktur yang sedang diuji. Dukungan goroutine dan channel memungkinkan pembuatan concurrent workload dalam jumlah besar dengan penggunaan resource yang relatif efisien.

Meskipun demikian, pendekatan ini tidak dimaksudkan untuk menggantikan tool yang sudah matang seperti JMeter. Tool seperti JMeter tetap menjadi pilihan yang tepat untuk berbagai kebutuhan pengujian karena menyediakan fitur, metrik, dan ekosistem yang lebih lengkap.

Namun, bagi software engineer yang ingin memahami cara kerja load testing secara lebih mendalam, membangun load generator menggunakan Golang merupakan pengalaman yang sangat berharga. Selain dapat mempelajari konsep concurrency secara langsung, pendekatan ini juga memberikan pemahaman yang lebih baik mengenai bagaimana request diproses, bagaimana metrik dikumpulkan, dan bagaimana sebuah sistem merespons beban dalam jumlah besar. Oleh karena itu, tidak ada salahnya mencoba membangun tool load testing sendiri menggunakan Golang sebagai sarana belajar sekaligus eksplorasi kemampuan bahasa tersebut dalam menangani workload yang tinggi.