Read-Write Pattern #

Dalam pengembangan aplikasi modern, khususnya backend dan sistem terdistribusi, concurrency bukan lagi pilihan, melainkan kebutuhan. Banyak aplikasi harus melayani ratusan hingga jutaan request secara paralel. Masalahnya, tidak semua operasi terhadap data memiliki karakteristik yang sama: sebagian besar hanya membaca data, sementara sebagian kecil melakukan perubahan.

Jika semua operasi diperlakukan sama dan dilindungi dengan mekanisme lock eksklusif, performa aplikasi bisa turun drastis. Di sinilah Read-Write Pattern berperan. Pattern ini memungkinkan sistem untuk tetap aman dari race condition, sekaligus memberikan performa optimal ketika beban baca jauh lebih besar daripada beban tulis.

Apa itu Read-Write Pattern? #

Read-Write Pattern adalah pola concurrency yang membedakan antara operasi read (baca) dan write (tulis) terhadap shared resource.

Prinsip utamanya:

  • Banyak operasi read boleh berjalan secara paralel.
  • Operasi write harus bersifat eksklusif (tidak boleh ada read atau write lain saat berlangsung).

Dengan kata lain:

  • Read ↔ Read: boleh bersamaan
  • Read ↔ Write: tidak boleh
  • Write ↔ Write: tidak boleh

Di Go, pola ini diimplementasikan secara langsung melalui sync.RWMutex.


Tujuan Pattern #

Read-Write Pattern memiliki beberapa tujuan utama:

  1. Meningkatkan Performa Dengan mengizinkan banyak goroutine membaca data secara bersamaan, throughput aplikasi meningkat signifikan.

  2. Menjaga Konsistensi Data Operasi tulis tetap aman dan terisolasi sehingga tidak terjadi race condition.

  3. Optimasi untuk Read-Heavy Workload Sangat cocok untuk sistem di mana jumlah read jauh lebih banyak dibanding write.

  4. Mengurangi Lock Contention Lock eksklusif yang berlebihan dapat menjadi bottleneck. RW lock meminimalkan hal ini.


Kapan Cocok Digunakan? #

Read-Write Pattern cocok digunakan pada kondisi berikut:

  • Data bersifat shared dan diakses oleh banyak goroutine
  • Frekuensi read jauh lebih tinggi daripada write
  • Data relatif stabil dan jarang berubah
  • Aplikasi membutuhkan performa tinggi dengan latensi rendah

Contoh kasus nyata:

  • Cache in-memory
  • Configuration service
  • Metadata service
  • Feature flag system
  • Rate limiter berbasis memory

Sebaliknya, pattern ini tidak cocok jika:

  • Operasi write sama seringnya dengan read
  • Critical section sangat kecil
  • Data sering berubah secara agresif

Contoh Implementasi (Golang) #

Contoh Sederhana dengan sync.RWMutex #

package main

import (
	"fmt"
	"sync"
)

type SafeCounter struct {
	mu    sync.RWMutex
	count int
}

func (c *SafeCounter) Increment() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.count++
}

func (c *SafeCounter) Value() int {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.count
}

func main() {
	counter := &SafeCounter{}
	wg := sync.WaitGroup{}

	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			fmt.Println(counter.Value())
		}()
	}

	wg.Add(1)
	go func() {
		defer wg.Done()
		counter.Increment()
	}()

	wg.Wait()
}

Pada contoh di atas:

  • RLock() digunakan untuk operasi baca
  • Lock() digunakan untuk operasi tulis
  • Banyak goroutine dapat membaca count secara paralel

Contoh Kasus Nyata: In-Memory Cache #

type Cache struct {
	mu   sync.RWMutex
	data map[string]string
}

func NewCache() *Cache {
	return &Cache{
		data: make(map[string]string),
	}
}

func (c *Cache) Get(key string) (string, bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	value, ok := c.data[key]
	return value, ok
}

func (c *Cache) Set(key, value string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.data[key] = value
}

Ini adalah contoh klasik penggunaan Read-Write Pattern pada cache.


Informasi Tambahan yang Perlu Diketahui #

  • sync.RWMutex di Go tidak re-entrant
  • Reader yang aktif akan memblok writer baru
  • Writer akan memblok reader dan writer lainnya
  • Go runtime mengatur fairness secara internal, tetapi starvation tetap bisa terjadi

Memahami detail ini penting untuk menghindari bug concurrency yang sulit dilacak.


Best Practice #

Berikut beberapa best practice saat menggunakan Read-Write Pattern:

  1. Gunakan RWMutex Hanya Jika Read Lebih Dominan Jika write sering terjadi, RWMutex justru bisa lebih lambat dibanding Mutex biasa.

  2. Jaga Critical Section Sekecil Mungkin Jangan melakukan operasi berat di dalam lock.

  3. Hindari Lock Upgrade Jangan mencoba mengubah RLock() menjadi Lock() dalam satu flow. Lepas read lock terlebih dahulu.

  4. Konsisten dalam Penggunaan Lock Semua akses ke shared resource harus melalui mekanisme lock yang sama.

  5. Perhatikan Starvation Terlalu banyak reader dapat membuat writer kelaparan. Pastikan desain sistem tetap seimbang.

  6. Pertimbangkan Alternatif Untuk kasus tertentu, sync.Map, immutable data, atau channel-based concurrency bisa lebih tepat.


Penutup #

Read-Write Pattern adalah salah satu pola concurrency paling penting untuk membangun aplikasi yang aman dan performa tinggi. Dengan memisahkan operasi read dan write, kita dapat memaksimalkan parallelism tanpa mengorbankan konsistensi data.

Dalam ekosistem Go, sync.RWMutex membuat implementasi pattern ini menjadi sederhana dan ekspresif. Namun, seperti semua concurrency pattern, Read-Write Pattern harus digunakan dengan bijak dan berdasarkan karakteristik workload.

Jika digunakan pada tempat yang tepat, pattern ini bisa menjadi senjata utama untuk membangun sistem yang scalable, efisien, dan robust.

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact