🧠 Programming✍️ Khoa📅 19/04/2026☕ 5 phút đọc

Go: Chuyên sâu cho Production Systems

File này bỏ qua syntax cơ bản. Nếu bạn đã viết Go đủ để làm CRUD, giờ là lúc lên level runtime-aware engineer.


1) Runtime Mental Model

Khi request vào service Go, 3 hệ thống sẽ cùng lúc tác động:

  • Scheduler (G-M-P): quyết định goroutine nào được chạy.
  • Garbage Collector: quyết định memory nào còn sống.
  • Network Poller: đánh thức goroutine đang chờ I/O.

Nếu bạn chỉ nhìn source code mà không hiểu 3 thằng này, tối ưu theo "cảm giác học" rất dễ sai.

G-M-P trong 30 giây

  • G (Goroutine): task cần chạy.
  • M (Machine): OS thread thực thi.
  • P (Processor): token để M được chạy Go code.

GOMAXPROCS ≈ số P. Nếu CPU-bound mà để quá thấp, throughput mất oan. Nếu để quá cao, context switch và contention tăng.


2) Concurrency Patterns: Dùng đúng thuốc, đúng bệnh

Worker Pool có backpressure

Dùng khi có stream jobs dài và cần giới hạn tài nguyên.

package workerpool

import (
    "context"
    "sync"
)

type Job func(context.Context) error

func Run(ctx context.Context, workers int, jobs <-chan Job) error {
    var wg sync.WaitGroup
    errCh := make(chan error, workers)

    worker := func() {
        defer wg.Done()
        for {
            select {
            case <-ctx.Done():
                return
            case job, ok := <-jobs:
                if !ok {
                    return
                }
                if err := job(ctx); err != nil {
                    select {
                    case errCh <- err:
                    default:
                    }
                    return
                }
            }
        }
    }

    for i := 0; i < workers; i++ {
        wg.Add(1)
        go worker()
    }

    done := make(chan struct{})
    go func() {
        wg.Wait()
        close(done)
    }()

    select {
    case <-done:
        return nil
    case err := <-errCh:
        return err
    case <-ctx.Done():
        return ctx.Err()
    }
}

Checklist production:

  • Có giới hạn queue size để tránh RAM phình không?
  • Có cancel propagation qua context.Context không?
  • Có metric queue depth + worker saturation không?

Pipeline có fan-out/fan-in

Dùng khi dữ liệu qua nhiều stage biến đổi. Mỗi stage cần:

  • đóng channel đúng lúc,
  • xử lý cancel,
  • tránh goroutine leak nếu consumer dừng sớm.

Anti-pattern kinh điển: producer vẫn gửi vào channel trong khi consumer đã timeout.


3) Memory và Escape Analysis

Một biến "nhìn như local" nhưng vẫn có thể bị đẩy lên heap nếu compiler thấy nó cần sống sau stack frame.

Kiểm tra bằng:

go build -gcflags='-m=2' ./...

Tín hiệu cần chú ý:

  • escapes to heap
  • moved to heap

Không phải escape là xấu. Vấn đề là escape quá nhiều trong hot path gây:

  • tăng pressure lên GC,
  • tăng p99 latency,
  • tăng CPU cho mark/sweep.

Rule thực dụng:

  • Tối ưu dữ liệu trong đường nóng (hot loop),
  • Không micro-optimize khi chưa có profile.

4) GC và Latency

GC của Go đã rất tốt, nhưng latency-sensitive service vẫn bị ảnh hưởng nếu allocation rate cao.

Theo dõi:

import "runtime"

func SnapshotMem() runtime.MemStats {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    return m
}

Metrics cần theo dõi:

  • HeapAlloc, HeapInuse
  • NumGC
  • p95/p99 latency theo thời gian

Nếu thấy p99 rung cùng nhịp với GC cycles, thử:

  • giảm allocations trong request path,
  • tái sử dụng buffer có kiểm soát,
  • tách object lớn khỏi hot path.

5) Profiling và Benchmark: Tin vào data

CPU/Heap profile với pprof

import _ "net/http/pprof"
import "net/http"

func StartDebugServer() {
    go func() {
        _ = http.ListenAndServe("127.0.0.1:6060", nil)
    }()
}

Lệnh hay dùng:

go tool pprof http://127.0.0.1:6060/debug/pprof/profile?seconds=30
go tool pprof http://127.0.0.1:6060/debug/pprof/heap

Benchmark đúng cách

func BenchmarkSerialize(b *testing.B) {
    payload := []byte(`{"id":1,"name":"khoa"}`)
    b.ReportAllocs()
    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        _ = fastPath(payload)
    }
}

Nguyên tắc:

  • Luôn bật -benchmem để thấy allocations.
  • Chạy benchmark on-demand trong môi trường ổn định.
  • So sánh trước/sau bằng công cụ thống kê (benchstat).

6) Data Races, Deadlocks, Goroutine Leaks

Race detector

go test -race ./...

Race detector không tìm được 100%, nhưng nó bắt được rất nhiều bug "ban ngày".

Goroutine leak pattern

Dấu hiệu:

  • số goroutine tăng đều theo uptime,
  • memory tăng nhẹ nhưng bền bỉ,
  • eventually latency degrade.

Nguyên nhân phổ biến:

  • channel không có consumer,
  • retry loop không dừng,
  • timeout không propagate context.

Fix mindset:

  • Mỗi goroutine phải có điều kiện thoát rõ ràng.
  • Mỗi I/O call phải có deadline/timeout.

7) API Design cho Package Go (Advanced)

Khi viết package cho team khác dùng:

  • Export ít nhất có thể.
  • Interface đặt ở phía consumer, không đặt ở package producer một cách vô tội vạ.
  • Trả về concrete type khi có thể, interface khi cần abstraction cho caller.

Pattern thuong dung:

type Option func(*Client)

func WithTimeout(d time.Duration) Option {
    return func(c *Client) { c.timeout = d }
}

func NewClient(opts ...Option) *Client {
    c := &Client{timeout: 2 * time.Second}
    for _, opt := range opts {
        opt(c)
    }
    return c
}

Trade-off:

  • Functional options linh hoạt,
  • nhưng nếu quá nhiều option, API có thể trở nên mờ hồ.

8) Production Checklist (Go Service)

  • context timeout cho mỗi outbound call.
  • Có graceful shutdown (SIGTERM, drain requests).
  • Có health endpoints (/live, /ready) tách biệt.
  • Có metrics: QPS, error rate, latency histogram, goroutines, GC pause.
  • Có profiling gate cho production troubleshooting.
  • Có load test cho critical paths trước khi release.

Nếu thiếu checklist này, service vẫn chạy... cho đến lúc traffic tăng gấp 5.


9) Interview Deep-Dive — Các câu hỏi hay gặp

  1. Tại sao goroutine "rẻ" nhưng không "free"?
  2. Khi nào dùng mutex, khi nào dùng channel?
  3. Cách debug memory leak trong Go service đang chạy production?
  4. Làm sao biết bottleneck nằm ở CPU, lock contention hay I/O?
  5. Nếu p99 tăng sau release, quy trình điều tra của bạn là gì?

Nếu trả lời được các câu này bằng dữ liệu đo được, bạn đã ở level senior thật sự.


10) Tài liệu để đi sâu hơn