📔
Microservices dengan Golang
  • Go Guidance
  • Golang Fundamental
    • Basic Golang
    • Pseudo OOP
    • Konkurensi
  • Design Pattern
    • Singleton
    • Dependency Injection
    • Concurrency Pattern
      • Worker Pool
      • Future / Promise
      • Rate Limit Pattern
      • Semaphore
      • Single Flight
  • Build Rest API Framework
    • Pengenalan Rest API
    • Start up
    • Shutdown
    • Json
    • Database
    • Clean architecture
    • Configuration
    • Fatal
    • Logging
    • Routing
    • CRUD
    • Request Response Helper
    • Error Handler
    • Unit Testing
    • API Testing
    • Context
    • Validation
    • Middleware
    • Token
    • Role Based Access Controller
  • GraphQL Framework
    • Pengenalan GraphQL
  • gRPC Framework
    • Pengenalan gRPC
    • Protocol Buffer
    • makefile
    • gRPC Server
    • Config
    • Database
    • Routing
    • Clean Architecture
    • gRPC Client
    • Tracing
    • Caching
    • gRPC Testing
Powered by GitBook
On this page
  • Kapan Teknik Ini Berguna?
  • Cara Menentukan Unique Key
  • Implementasi Single Flight Pattern
  • Output yang diharapkan
  • Implementasi Menggunakan Library "golang.org/x/sync/singleflight"
  • Kesimpulan

Was this helpful?

  1. Design Pattern
  2. Concurrency Pattern

Single Flight

Single Flight adalah pola yang digunakan untuk mencegah redundant request dengan memastikan bahwa hanya satu goroutine yang menjalankan proses tertentu dalam satu waktu. Goroutine lain yang meminta hasil yang sama akan menunggu hasil dari goroutine pertama, bukan memproses ulang permintaan yang sama.

Pola ini sangat berguna untuk:

  • Mengurangi load ke database atau API eksternal (misal: caching atau fetching data).

  • Menghindari spam request ke third-party API, sehingga lebih efisien.

  • Menghindari race condition saat banyak goroutine meminta data yang sama.

  • Meningkatkan efisiensi dalam sistem dengan banyak request paralel.

Kapan Teknik Ini Berguna?

  • Ketika banyak request ke API yang sama dalam waktu bersamaan.

  • Jika API third-party memiliki rate limit dan kita ingin menghindari throttling.

  • Untuk mengurangi latensi dengan menghindari redundant request.

  • Untuk menghemat biaya jika API third-party menggunakan sistem berbayar per request.

Cara Menentukan Unique Key

Seperti yang kita lihat dari contoh kode implmentasi single flight patter, ada satu unique-key yang digunakan, sehingga request-request yang memiliki unique key yang sama, hanya akan diproses 1x. Unique-key bisa digenerate dengan berbagai logic, untuk kasus pemanggilan api third party, unique-key bisa menggunakan path url termasuk dengan parameter/query yang digunakan. Jika url, path dan parameter dirasa terlalu panjang, bisa menggunakan hashing agar lebih ringkas.

key := fmt.Sprintf("%x", sha256.Sum256([]byte(urlWithParams)))

Implementasi Single Flight Pattern

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"
	"sync"
	"time"
)

// MySingleFlight adalah struktur untuk menangani request tunggal per key
type MySingleFlight struct {
	mu    sync.Mutex
	calls map[string]*call
}

// call menyimpan informasi tentang request yang sedang berlangsung
type call struct {
	wg  sync.WaitGroup
	res string
	err error
}

// NewMySingleFlight membuat instance MySingleFlight
func NewMySingleFlight() *MySingleFlight {
	return &MySingleFlight{
		calls: make(map[string]*call),
	}
}

// Do memastikan hanya satu request per key yang berjalan pada satu waktu
func (sf *MySingleFlight) Do(key string, fn func() (string, error)) (string, error) {
	sf.mu.Lock()
	if c, found := sf.calls[key]; found {
		sf.mu.Unlock()
		c.wg.Wait() // Tunggu hasil request yang sedang berjalan
		return c.res, c.err
	}

	// Jika belum ada request, buat yang baru
	c := &call{}
	c.wg.Add(1)
	sf.calls[key] = c
	sf.mu.Unlock()

	// Jalankan request
	c.res, c.err = fn()
	c.wg.Done()

	// Hapus dari map setelah selesai
	sf.mu.Lock()
	delete(sf.calls, key)
	sf.mu.Unlock()

	return c.res, c.err
}

// Hash URL dengan SHA-256 sebagai key
func hashURL(url string) string {
	hash := sha256.Sum256([]byte(url))
	return hex.EncodeToString(hash[:])
}

// Fetch API menggunakan MySingleFlight
func fetchAPI(sf *MySingleFlight, url string) (string, error) {
	key := hashURL(url)

	return sf.Do(key, func() (string, error) {
		fmt.Println("Fetching API:", url) // Indikasi request benar-benar terjadi
		resp, err := http.Get(url)
		if err != nil {
			return "", err
		}
		defer resp.Body.Close()

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			return "", err
		}

		return string(body), nil
	})
}

func main() {
	url := "https://example.com/todos/1" // API contoh
	sf := NewMySingleFlight()

	var wg sync.WaitGroup
	numRequests := 3

	wg.Add(numRequests)
	for i := 0; i < numRequests; i++ {
		go func(id int) {
			defer wg.Done()
			data, err := fetchAPI(sf, url)
			if err != nil {
				fmt.Printf("Goroutine %d error: %v\n", id, err)
			} else {
				fmt.Printf("Goroutine %d result: %s\n", id, data)
			}
		}(i)
	}

	wg.Wait()
}

Penjelasan Kode :

  • Membuat MySingleFlight

    • Menggunakan map[string]*call untuk menyimpan request yang sedang berjalan.

    • Menggunakan sync.Mutex agar hanya satu goroutine yang bisa memodifikasi map pada satu waktu.

    • Request kedua dan seterusnya akan menunggu hasil request pertama.

  • Struktur call

    • wg sync.WaitGroup: Menunggu hasil request yang sedang berlangsung.

    • res string: Menyimpan hasil response.

    • err error: Menyimpan error jika terjadi.

  • Mekanisme Do()

    • Jika request dengan key tertentu sudah berjalan, goroutine menunggu hasilnya (c.wg.Wait()).

    • belum ada request, membuat request baru dan menyimpannya di map.

    • Setelah request selesai, hapus entri dari map agar request baru bisa dilakukan.

  • Memanggil API dengan Hashing

    • Menggunakan SHA-256 hash dari URL sebagai key untuk menghindari duplikasi request.

  • Menjalankan fetchAPI() dengan Beberapa Goroutine

    • Tiga goroutine menjalankan request bersamaan.

    • Hanya satu request yang benar-benar dikirim, sisanya menunggu hasilnya.

Output yang diharapkan

Fetching API: https://example.com/todos/1
Goroutine 0 result: {"userId":1,"id":1,"title":"lorem ipsum delectus aut autem","completed":false}
Goroutine 1 result: {"userId":1,"id":1,"title":"lorem ipsum delectus aut autem","completed":false}
Goroutine 2 result: {"userId":1,"id":1,"title":"lorem ipsum delectus aut autem","completed":false}
  • Fetching API: hanya muncul sekali, menandakan hanya satu request yang benar-benar dikirim.

  • Semua goroutine mendapatkan hasil yang sama tanpa harus request ulang.

Implementasi Menggunakan Library "golang.org/x/sync/singleflight"

Saat ini, sudah ada library single-flight pattern yang cukup populer di golang. Pertimbangkan untuk menggunakan library ini agar kita tidak perlu membuatnya from scratch.

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"
	"sync"
	"time"

	"golang.org/x/sync/singleflight"
)

var sf singleflight.Group

// Hash URL dengan SHA-256 untuk digunakan sebagai key
func hashURL(url string) string {
	hash := sha256.Sum256([]byte(url))
	return hex.EncodeToString(hash[:])
}

// Fetch data dari API third-party menggunakan SingleFlight
func fetchAPI(url string) (string, error) {
	key := hashURL(url) // Gunakan hash URL sebagai key

	// Gunakan SingleFlight untuk mencegah duplikasi request
	result, err, _ := sf.Do(key, func() (interface{}, error) {
		fmt.Println("Fetching API:", url) // Indikasi request benar-benar terjadi
		resp, err := http.Get(url)
		if err != nil {
			return "", err
		}
		defer resp.Body.Close()

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			return "", err
		}

		return string(body), nil
	})

	if err != nil {
		return "", err
	}
	return result.(string), nil
}

func main() {
	url := "https://example.com/todos/1" // API contoh

	var wg sync.WaitGroup
	numRequests := 3

	wg.Add(numRequests)
	for i := 0; i < numRequests; i++ {
		go func(id int) {
			defer wg.Done()
			data, err := fetchAPI(url)
			if err != nil {
				fmt.Printf("Goroutine %d error: %v\n", id, err)
			} else {
				fmt.Printf("Goroutine %d result: %s\n", id, data)
			}
		}(i)
	}

	wg.Wait()
}

Kesimpulan

SingleFlight adalah solusi yang efisien dan sederhana untuk menghindari eksekusi berulang dari tugas yang sama dalam lingkungan konkuren. Jika ingin kontrol penuh, kita bisa membuat SingleFlight buatan sendiri. Jika ingin implementasi cepat dan stabil, cukup gunakan sync/singleflight.

🚀 Dengan menggunakan SingleFlight, kita bisa meningkatkan performa aplikasi secara signifikan dan menghindari pemborosan resource!

PreviousSemaphoreNextPengenalan Rest API

Last updated 2 months ago

Was this helpful?