Usecase

Untuk melakukan pemeriksaan terhadap sebuah website atau endpoint, bisa dilakukan secara manual dengan mengunjungi alamat web atau endpoint tersebut melalui web browser atau http client yang biasanya digunakan.

Hanya saja, semakin banyak alamat yang harus dikunjungi maka semakin lama juga waktu yang dibutuhkan. Misal, untuk memeriksa satu alamat membutuhkan 1 menit, maka untuk memeriksa alamat sebanyak n maka waktu yang dibutuhkan adalah v = n * 1.

Dan tentu saja saya sendiri juga cukup malas untuk melakukan hal diatas secara manual dan berulang-ulang. Maka dari itu membuat sebuah tools kecil untuk melakukan pekerjaan diatas sangatlah berguna.

Pemeriksaan Tanpa Goroutine

Misalkan, berikut adalah alamat yang perlu dilakukan pemeriksaan:

Bahasa Pemrograman Go menyediakan library yang dapat digunakan untuk melakukan hal tersebut yaitu net/http.

package main

import (
	"log"
	"net/http"
)

func main() {
	urls := []string{
		"https://github.com",
		"https://api.github.com",
		"https://google.com",
		"https://ngurajeka.com",
	}
	for _, url := range urls {
		resp, err := http.Get(url)
		if err != nil {
			log.Printf("%s got err: %s", url, err.Error())
			continue
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK || resp.StatusCode != http.StatusCreated {
			log.Printf("%s get error code: %d", url, resp.StatusCode)
			continue
		}
		log.Printf("%s looks fine", url)
	}
}

Ketika kode tersebut dijalankan, maka hasilnya sebagai berikut:

Pada dasarnya, kode diatas sudah dapat digunakan untuk melakukan pemeriksaan terhadap lebih dari satu alamat sekaligus. Hanya saja akan ada beberapa masalah yang akan muncul ketika alamat yang diperiksa jumlahnya lebih banyak.

Perhatikan gambar di bawah, sebelum program dijalankan. Proses compile perlu dilakukan untuk membuat program di atas menjadi file binary, dan program dijalankan dari hasil binary hasil compile.

$ go build -o pinger

Saat menggunakan program yang sudah dicompile menjadi binary, waktu yang dibutuhkan menjadi lebih sedikit karena proses compile sudah tidak dilakukan.

Berikutnya, melakukan uji coba dengan jumlah alamat lebih banyak

urls := []string{
	"https://github.com",
	"https://api.github.com",
	"https://google.com",
	"https://ngurajeka.com",
	"https://meraxes.id",
	"https://valutac.com",
	"https://teams.microsoft.com",
	"https://facebook.com",
	"https://twitter.com",
}

Sebelum dijalankan, lakukan compile terlebih dahulu untuk memperbarui file binary.

Pada proses di atas, setiap alamat diperiksa satu persatu secara sinkronos. Dengan semakin banyaknya alamat yang dibutuhkan untuk periksa, tentunya pemeriksaan secara sinkronos bukanlah metode yang baik digunakan.

Alasannya, sebenarnya sama seperti melakukan pemeriksaan secara manual. Misalkan untuk memeriksa satu alamat dibutuhkan waktu 5 detik, maka untuk memeriksa alamat sejumlah n dibutuhkan waktu v = n * 5.

Solusinya adalah menjalankan proses pemeriksaan setiap alamat secara bersamaan. Untuk beberapa bahasa pemrograman yang tidak mendukung proses async, maka tentu saja dibutuhkan tools terpisah untuk melakukan proses tersebut.

Pemeriksaan Dengan Goroutine

Pattern yang cukup sering digunakan untuk kasus similiar adalah dengan membuat sebuah penampungan data yang akan dikonsumsi oleh program lainnya yang bertugas untuk menjalankan proses utama yang ingin dilaksanakan secara async.

Dengan menjalankan program secara async, maka waktu yang dibutuhkan menjadi lebih sedikit.

Di Go, terdapat sebuah fitur yaitu Goroutine. Dimana dengan goroutine program dapat menjalankan sebuah proses secara async.

Berikut kode program diatas setelah diubah untuk menggunakan goroutine

package main

import (
        "log"
        "net/http"
        "sync"
)

func main() {
        urls := []string{
                "https://github.com",
                "https://api.github.com",
                "https://google.com",
                "https://ngurajeka.com",
                "https://meraxes.id",
                "https://valutac.com",
                "https://teams.microsoft.com",
                "https://facebook.com",
                "https://twitter.com",
        }
        var wg sync.WaitGroup
        for _, url := range urls {
                wg.Add(1)

                go func(v string) {
                        defer wg.Done()
                        resp, err := http.Get(v)
                        if err != nil {
                                log.Printf("%s got err: %s", v, err.Error())
                                return
                        }
                        defer resp.Body.Close()

                        if resp.StatusCode != http.StatusOK || resp.StatusCode != http.StatusCreated {
                                log.Printf("%s get error code: %d", v, resp.StatusCode)
                                return
                        }
                        log.Printf("%s looks fine", v)
                }(url)
        }
        wg.Wait()
}

Perbedaan saat menggunakan goroutine dan tidak adalah proses utama yang ingin dijalankan dibungkus (wrap) menjadi sebuah anonymous function dan ditambahkan keyword go saat dijalankan. Bagian tersebut bisa dibuat menjadi fungsi terpisah agar lebih mudah dibaca dan dimodifikasi.

Kode diatas juga menggunakan sync.WaitGroup untuk menunggu semua proses selesai sebelum mengakhiri jalannya program utama (main).

Untuk mencoba melihat hasilnya, compile program terlebih dahulu lalu jalankan kembali.

Setelah menggunakan goroutine, proses pemeriksaan berjalan kurang dari 1 detik untuk total alamat yang sama dengan sebelumnya. Pada gambar diatas juga terlihat bahwa proses pemeriksaan sudah tidak dilakukan secara berurut lagi (sync).

Di goroutine juga terdapat channel yang bisa digunakan untuk berkomunikasi dari setiap program yang dieksekusi secara async kepada program utama yang menjalankannya. Mungkin bagian ini bisa dibahas dengan usecase yang berbeda.

Metode diatas digunakan pada tools berikut https://github.com/Valutac/meraxes dan juga pada meraxes.id untuk versi web.

Editor: Nur Yaumi