SRE Prep
เมนูโมดูล

8. Go Programming (beginner)

ทำไมหัวข้อนี้สำคัญกับ interview

เครื่องมือ SRE ส่วนใหญ่ (Kubernetes, Prometheus, Terraform, Docker) เขียนด้วย Go และ หลายทีมให้เขียน tooling/automation เป็น Go. interview ระดับ beginner มักถาม concurrency (goroutine/channel), error handling แบบ Go, และ "ทำไม SRE ชอบ Go". ไม่ต้องเก่งมาก แต่ต้องอธิบายแนวคิดหลักและอ่านโค้ดออก.

ทำไม SRE teams ถึงชอบ Go

  • Single static binary — compile ออกมาไฟล์เดียว ไม่มี runtime dependency → deploy/ship ง่าย (ใส่ container เล็ก ๆ ได้).
  • Cross-compile ง่าย — build ให้ Linux/ARM จากเครื่องไหนก็ได้.
  • Concurrency ในภาษา — goroutine/channel เขียนงาน concurrent ได้เรียบง่าย เหมาะกับ tooling ที่คุยหลาย service.
  • stdlib แข็งแรงnet/http, encoding/json ครบ เขียน service/CLI ได้โดยแทบไม่ต้องพึ่ง framework.
  • compile เร็ว + อ่านง่าย — โค้ดสไตล์เดียว (gofmt) ทีมอ่านกันรู้เรื่อง.

Core Concepts (แบบเริ่มต้น)

Syntax พื้นฐาน

Go
package main

import "fmt"

type Server struct {
    Name string
    Port int
}

func main() {
    s := Server{Name: "checkout", Port: 8080} // := ประกาศ+กำหนดค่า
    ports := []int{8080, 9090}                 // slice
    healthy := map[string]bool{"checkout": true} // map

    for i, p := range ports {                  // range loop
        fmt.Printf("port[%d] = %d\n", i, p)
    }
    fmt.Println(s.Name, healthy["checkout"])
}

Error handling — error เป็น "ค่า" ไม่ใช่ exception

Go ไม่มี try/catch. ฟังก์ชันคืน error เป็นค่าสุดท้าย และเราต้องเช็กเอง — ทำให้ flow ของ error ชัดเจน (แต่ verbose).

Go
func fetchUser(id string) (*User, error) {
    u, err := db.Get(id)
    if err != nil {
        // ห่อ error เพื่อเก็บ context (%w ให้ unwrap ได้ทีหลัง)
        return nil, fmt.Errorf("fetchUser %s: %w", id, err)
    }
    return u, nil
}

// ตรวจชนิด error ด้วย errors.Is / errors.As
if errors.Is(err, sql.ErrNoRows) {
    // จัดการกรณี not found โดยเฉพาะ
}

อย่ากลืน error

การเขียน val, _ := doSomething() (ทิ้ง error ด้วย _) คือกับดักคลาสสิก — error ที่ ถูกกลืนทำให้ debug ยากมากตอน production. เช็กและ handle หรือ return ต่อเสมอ.

Concurrency — goroutine & channel

Goroutine = lightweight thread ที่ Go runtime จัดการ (ถูกกว่า OS thread มาก รันเป็นแสนตัวได้). สั่งด้วยคำว่า go. Channel = ท่อส่งข้อมูลระหว่าง goroutine อย่างปลอดภัย (ไม่ต้องแชร์ memory ตรง ๆ).

Go
// ปรัชญา Go: "อย่าสื่อสารด้วยการแชร์ memory จงแชร์ memory ด้วยการสื่อสาร"
func main() {
    results := make(chan string)      // channel ของ string

    for _, url := range urls {
        go func(u string) {           // แต่ละ url รันใน goroutine ของตัวเอง
            results <- check(u)        // ส่งผลเข้า channel
        }(u)
    }

    for range urls {
        fmt.Println(<-results)        // รับผลจาก channel (block จนกว่าจะมี)
    }
}
fan-in: หลาย goroutine ส่งผลเข้า channel เดียว แล้ว main รวบรวม

Context — timeout & cancellation (สำคัญมากสำหรับ SRE)

context.Context ใช้ส่งสัญญาณ "ยกเลิก/หมดเวลา" ข้าม goroutine — หัวใจของการกัน goroutine leak และตั้ง timeout ให้ request.

Go
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // สำคัญ: ปล่อย resource ของ context เสมอ

req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := http.DefaultClient.Do(req) // ถ้าเกิน 2s ถูกยกเลิกอัตโนมัติ

Race condition & -race

ถ้าหลาย goroutine เขียน/อ่านตัวแปรเดียวกันโดยไม่ป้องกัน (mutex/channel) → data race ผลลัพธ์ไม่แน่นอน. ตรวจด้วย flag -race:

bash
go test -race ./...      # ตรวจ data race ตอน test
go run -race main.go     # ตรวจตอนรัน

ป้องกันด้วย sync.Mutex (ล็อกก่อนแก้ shared state) หรือส่งผ่าน channel แทนการแชร์.

เชื่อมกับ Cloud / งานจริง

  • เขียน health check / metrics exporter ด้วย net/http แล้ว compile เป็น binary เดียวใส่ container จิ๋ว (scratch/distroless).
  • Lambda รองรับ Go runtime (custom runtime / provided.al2) — เขียน function เบา ๆ ได้.
  • Automation ที่เรียก AWS API ใช้ AWS SDK for Go v2 (มี context, retry+backoff ในตัว).
  • controller/operator บน Kubernetes เขียนด้วย Go (client-go) — reconcile loop คือ pattern ที่คุ้นจาก K8s.

ประโยคปิดท้ายเวลาโดนถาม 'ทำไม Go'

"Go ให้ single static binary + concurrency ที่เขียนง่าย + stdlib ที่ครบ ทำให้เขียน operational tooling ที่ deploy ง่ายและ maintain ง่าย— เหตุผลเดียวกับที่ Kubernetes และ Prometheus เลือกใช้."

คำถาม interview ที่เจอบ่อย + แนวคำตอบ

  1. "goroutine ต่างจาก OS thread ยังไง?" → goroutine เบากว่ามาก (เริ่มที่ ~KB stack, โตได้) และ Go runtime multiplex หลาย goroutine ลงบน OS thread ไม่กี่ตัว → สร้างเป็นแสนตัวได้ ต่างจาก thread ที่แพง.

  2. "channel คืออะไร ใช้ทำไม?" → ท่อส่งข้อมูลระหว่าง goroutine อย่างปลอดภัย ใช้ประสาน/ส่งผลลัพธ์แทนการแชร์ memory ตรง ๆ (ลด race). unbuffered = ส่ง/รับต้องพร้อมกัน; buffered = มี buffer.

  3. "Go จัดการ error ยังไง ต่างจาก exception?" → error เป็นค่าที่ return ออกมา ต้องเช็ก if err != nil เอง; ห่อด้วย %w เพื่อเก็บ context และ unwrap ด้วย errors.Is/As. ข้อดี: flow ชัด; ข้อเสีย: verbose.

  4. "race condition คืออะไร ตรวจยังไง?" → หลาย goroutine เข้าถึง shared state พร้อมกันโดยไม่ sync → ผลไม่แน่นอน. ตรวจด้วย go test -race; ป้องกันด้วย mutex หรือ channel.

  5. "ทำไม context สำคัญ?" → ใช้ส่ง timeout/cancellation ข้าม call chain — กัน goroutine leak และตั้ง deadline ให้ request/DB call, เลิกทำงานที่ผู้ใช้ยกเลิกไปแล้ว.

Pitfalls — จุดที่ผู้สมัครมักพลาด

ระวังกับดักเหล่านี้

  • Goroutine leak: spawn goroutine ที่ block บน channel ตลอดกาลเพราะไม่มี cancellation/context.
  • กลืน error ด้วย _: ทำให้ปัญหาเงียบและ debug ยาก.
  • Deadlock จาก unbuffered channel: ส่งเข้า channel ที่ไม่มีใครรับ → block ค้าง.
  • แชร์ตัวแปรข้าม goroutine โดยไม่มี mutex/channel: data race (รันด้วย -race เพื่อจับ).
  • ลืม defer cancel() หลังสร้าง context → resource รั่ว.
  • ไม่ตั้ง timeout ให้ network call: hang ค้างเหมือนหัวข้อ microservices.

Quiz ท้ายบท

Quiz ท้ายบท

ตอบแล้ว 0/7
  1. 1.ทำไม SRE teams ถึงนิยมใช้ Go สำหรับเขียน tooling?

  2. 2.goroutine ต่างจาก OS thread อย่างไร?

  3. 3.ใน Go การจัดการ error ทำอย่างไร?

  4. 4.channel ใน Go มีไว้เพื่ออะไร?

  5. 5.จะตรวจหา data race ในโปรแกรม Go ได้อย่างไร?

  6. 6.บทบาทหลักของ context.Context ในงาน SRE คืออะไร?

  7. 7.ข้อใดคือสาเหตุของ 'goroutine leak' ที่พบบ่อย?

Cheat Sheet — อ่านก่อนเข้าห้องสัมภาษณ์

สรุปเร็ว 30 วินาที

  • ทำไม Go: single static binary · cross-compile · concurrency ในภาษา · stdlib ครบ
  • Error = ค่า ที่ return (if err != nil), ห่อด้วย %w, unwrap errors.Is/As — ไม่มี try/catch
  • goroutine = lightweight thread (สั่งด้วย go); channel = ท่อสื่อสารที่ปลอดภัย
  • context = timeout/cancellation ข้าม goroutine (อย่าลืม defer cancel())
  • data race ตรวจด้วย -race; ป้องกันด้วย mutex/channel
  • Pitfalls: goroutine leak · กลืน error (_) · unbuffered channel deadlock · ลืม timeout
อ่านจบแล้ว? ทำเครื่องหมายไว้เพื่อติดตามความคืบหน้า