Ketika Go Terasa Lambat
Go dikenal cepat, tapi "cepat" bukan berarti tidak bisa dioptimasi. Saya pernah handle sistem yang memproses 50,000 request per detik, dan setiap millisecond latency yang bisa dikurangi berarti ribuan user yang lebih puas. Optimasi performance itu tentang data, bukan intuisi. Jangan menebak — profil dulu.
1. Profiling: Kenali Bottleneck-nya
Go punya built-in profiling tools yang excellent. Ini langkah pertama yang selalu saya lakukan:
import (
"net/http"
_ "net/http/pprof"
"runtime"
)
func init() {
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
}
// Generate CPU profile:
// go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
// Generate memory profile:
// go tool pprof http://localhost:6060/debug/pprof/heap
// Visualize:
// go tool pprof -http=:8080 profile.pb.gz
2. Memory Optimization
Dari profiling, saya menemukan bahwa sebagian besar memory issue di Go berkaitan dengan allocation yang tidak perlu:
// Bad: Creates new slice on every iteration
func processBad(items []string) []string {
var result []string
for _, item := range items {
result = append(result, strings.ToUpper(item))
}
return result
}
// Good: Pre-allocate with known capacity
func processGood(items []string) []string {
result := make([]string, 0, len(items))
for _, item := range items {
result = append(result, strings.ToUpper(item))
}
return result
}
// Better: Reuse buffer with sync.Pool
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func processBest(items []string) []string {
buf := bufferPool.Get().(*bytes.Buffer)
defer bufferPool.Put(buf)
result := make([]string, 0, len(items))
for _, item := range items {
buf.Reset()
buf.WriteString(strings.ToUpper(item))
result = append(result, buf.String())
}
return result
}
3. Benchmarking
Go punya testing framework yang mendukung benchmarking secara native:
func BenchmarkProcess(b *testing.B) {
items := generateTestData(1000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
process(items)
}
}
func BenchmarkProcessParallel(b *testing.B) {
items := generateTestData(1000)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
process(items)
}
})
}
// go test -bench=. -benchmem -count=5
4. Database Query Optimization
Dari pengalaman saya, 80% performance issue di Go applications itu sebenarnya di database layer. Beberapa tips:
- Connection pooling: Jangan buka-tutup koneksi setiap query. Pakai
sql.DByang sudah handle pooling - Batch operations: Jangan insert 1 row per query. Gunakan batch insert untuk data volumetric
- Selective columns: Jangan
SELECT *. Pilih hanya kolom yang dibutuhkan - Pagination: Jangan load semua data sekaligus. Gunakan cursor-based pagination untuk data besar
5. Goroutine Management
Goroutine yang tidak dikontrol bisa menyebabkan memory leak. Saya selalu pakai pattern berikut:
func boundedWorkerPool(ctx context.Context, jobs <-chan Job, maxWorkers int) {
sem := make(chan struct{}, maxWorkers)
for job := range jobs {
select {
case <-ctx.Done():
return
case sem <- struct{}{}:
go func(j Job) {
defer func() { <-sem }()
process(j)
}(job)
}
}
}
Kesimpulan
Performance optimization di Go itu sistematis: profil, identifikasi bottleneck, optimasi, benchmark untuk verifikasi. Jangan pernah optimasi tanpa data. Dan ingat: premature optimization is the root of all evil — tapi juga, delayed optimization di production juga bisa jadi masalah besar.