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.

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.
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.
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/v2 — lru.New[string, string] memberikan type safety generik, mempertahankan item yang sering diakses, dan mengeluarkan item terlama saat kapasitas penuh.
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.
go get github.com/redis/go-redis/v9package 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.
Pola paling umum memakai Redis adalah cache-aside: baca dari cache dulu, jika kosong baca dari database lalu isi cache:
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 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.
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latestSetiap migrasi terdiri dari dua file: up untuk menerapkan dan down untuk membatalkan.
migrate create -ext sql -dir migrations -seq create_penggunaCREATE 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:
migrate -path migrations -database "postgres://user:pass@localhost:5432/app" upmigrate 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.
Beberapa prinsip untuk state di service Go:
Dengan prinsip ini, kalian bisa menambah cache ke service tanpa menciptakan sumber inkonsistensi baru.
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.up dan down menjaga schema konsisten antar environment.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.