Belajar Golang - State, Caching & Schema Migrations
Episode 9 of 19

Belajar Golang - State, Caching & Schema Migrations

Episode ini mengajarkan strategi state dan performa: caching in-memory dengan sync.Map dan golang-lru, integrasi Redis dengan go-redis, pengelolaan schema database dengan golang-migrate, serta best practice state management untuk service Go.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Setiap kali database diquery, biaya I/O, jaringan, dan beban CPU dikeluarkan. Untuk data yang jarang berubah, membaca berulang kali dari database adalah pemborosan. Di episode 9 kita menyelesaikan masalah ini dengan caching, sekaligus menjaga schema database tetap terkelola lewat migrasi.

Episode 9 mencakup tiga hal: caching in-memory dengan sync.Map dan library LRU, integrasi Redis dengan go-redis untuk cache terdistribusi, serta pengelolaan schema database dengan golang-migrate. Di akhir episode kalian paham kapan state sebaiknya hidup di proses, di Redis, atau tetap di database.

Caching In-Memory

sync.Map untuk Proses Tunggal

sync.Map adalah peta aman-concurrency dari standard library, cocok untuk kasus di mana kunci ditulis sekali dan dibaca berkali-kali. Untuk beban ringan pada satu proses, ini pilihan paling sederhana.

Cache dengan sync.Map
package main
 
import "sync"
 
type Cache struct {
	data sync.Map
}
 
func (c *Cache) Set(kunci string, nilai string) {
	c.data.Store(kunci, nilai)
}
 
func (c *Cache) Get(kunci string) (string, bool) {
	v, ok := c.data.Load(kunci)
	if !ok {
		return "", false
	}
	return v.(string), true
}

Namun sync.Map tidak punya mekanisme kadaluarsa atau batas ukuran. Untuk cache yang butuh LRU dan TTL, gunakan library seperti github.com/hashicorp/golang-lru/v2lru.New[string, string] memberikan type safety generik, mempertahankan item yang sering diakses, dan mengeluarkan item terlama saat kapasitas penuh.

Redis dengan go-redis

Koneksi dan Operasi Dasar

Cache in-memory hanya hidup selama proses berjalan. Ketika aplikasi dijalankan dalam banyak instance, setiap instance punya cache sendiri yang bisa saling tidak konsisten. Solusinya: Redis, cache terpusat yang dibagi semua instance.

Menambah go-redis
go get github.com/redis/go-redis/v9
Klien Redis
package main
 
import (
	"context"
	"fmt"
	"time"
 
	"github.com/redis/go-redis/v9"
)
 
func main() {
	ctx := context.Background()
	rdb := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "",
		DB:       0,
	})
 
	err := rdb.Set(ctx, "kunci", "nilai", 5*time.Minute).Err()
	if err != nil {
		panic(err)
	}
 
	hasil, err := rdb.Get(ctx, "kunci").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("dari redis:", hasil)
}

Set dengan TTL 5 menit memastikan data tidak basi selamanya. Seluruh operasi go-redis menerima context.Context sebagai argumen pertama.

Cache-Aside Pattern

Pola paling umum memakai Redis adalah cache-aside: baca dari cache dulu, jika kosong baca dari database lalu isi cache:

Cache-aside dengan TTL
func ambilPengguna(ctx context.Context, id int) (Pengguna, error) {
	key := fmt.Sprintf("pengguna:%d", id)
 
	if val, err := rdb.Get(ctx, key).Result(); err == nil {
		var u Pengguna
		json.Unmarshal([]byte(val), &u)
		return u, nil
	}
 
	var u Pengguna
	if err := db.QueryRowContext(ctx,
		"SELECT id, nama FROM pengguna WHERE id = $1", id,
	).Scan(&u.ID, &u.Nama); err != nil {
		return Pengguna{}, err
	}
 
	data, _ := json.Marshal(u)
	rdb.Set(ctx, key, data, 10*time.Minute)
	return u, nil
}

Serangan cache-miss (thundering herd) saat banyak request datang bersamaan bisa dimitigasi dengan singleflight atau lock. Invalidasi cache dilakukan dengan menghapus kunci saat data berubah, bukan memperbarui nilai secara langsung.

Schema Migrations dengan golang-migrate

Mengapa Perlu Migrasi

Schema database berkembang seiring fitur aplikasi: menambah tabel, kolom, atau indeks. Migrasi menjadikan perubahan ini versioned dan bisa direproduksi di setiap environment. golang-migrate adalah tool standar untuk ini.

Install golang-migrate
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest

Membuat dan Menjalankan Migrasi

Setiap migrasi terdiri dari dua file: up untuk menerapkan dan down untuk membatalkan.

Membuat migrasi baru
migrate create -ext sql -dir migrations -seq create_pengguna
migrations/000001_create_pengguna.up.sql
CREATE TABLE pengguna (
    id BIGSERIAL PRIMARY KEY,
    nama TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    dibuat_pada TIMESTAMPTZ DEFAULT now()
);

Jalankan migrasi dengan perintah dari terminal:

Menjalankan migrasi
migrate -path migrations -database "postgres://user:pass@localhost:5432/app" up

migrate down 1 membatalkan satu migrasi terakhir. Gunakan migrasi sebagai langkah pertama di pipeline deployment sebelum aplikasi baru dirilis, dan selalu uji migrasi di staging dulu. Perintah migrate -version menampilkan versi migrasi yang sedang aktif di database.

Strategi State Management

Beberapa prinsip untuk state di service Go:

  • Stateless bila memungkinkan: simpan state persisten di database, state sesaat di cache.
  • Satu sumber kebenaran: database adalah sumber utama; cache hanyalah turunan yang boleh dibuang.
  • TTL yang disengaja: set masa hidup cache sesuai seberapa cepat data berubah.
  • Graceful degradasi: ketika Redis mati, aplikasi tetap melayani request langsung ke database.
  • Instrumentasi: pantau hit ratio cache agar tahu apakah cache benar-benar membantu.

Dengan prinsip ini, kalian bisa menambah cache ke service tanpa menciptakan sumber inkonsistensi baru.

Penutup

Episode 9 melengkapi strategi state dan performa: cache in-memory dengan sync.Map dan golang-lru, cache terdistribusi dengan go-redis dan pola cache-aside, migrasi schema yang versioned dengan golang-migrate, serta prinsip state management untuk service Go.

Inti yang harus dibawa pulang:

  • sync.Map untuk cache sederhana dalam satu proses.
  • golang-lru menambah batas ukuran dan strategi eviction.
  • Redis menyatukan cache lintas instance aplikasi.
  • Cache-aside: baca cache, lalu database, lalu isi cache.
  • Migrasi up dan down menjaga schema konsisten antar environment.
  • Database adalah sumber kebenaran; cache boleh dibuang kapan saja.

Di episode 10 selanjutnya kita akan membahas networking dasar, HTTP server, dan REST API — membangun HTTP server dengan net/http dan handler, routing dengan http.ServeMux di Go 1.22, middleware, router modern seperti chi, gin, atau echo, serta best practice desain route. Inilah saat aplikasi Go kalian menjadi service yang nyata.