Skip to content

Weekly Review — Month 6 · Week 3 (Days 155–161)

Journal index · Roadmap › this week

📅 The Week in One Line

Turned a working service into a fast and hardened one: profile with pprof, eliminate allocations via escape analysis, recycle the rest with sync.Pool, prove it under load with k6/vegeta, scan it with govulncheck/gosec, and defend it with AuthN/Z and rate limiting — all measurement-first.

✅ What I Completed

  • Day 155 — pprof: CPU (sampled) & heap (snapshot) profiling; runtime/pprof + net/http/pprof; go tool pprof top/list
  • Day 156 — Escape analysis: stack vs heap, -gcflags=-m, -benchmem/AllocsPerRun, append-into-buffer, dead-code-elimination trap
  • Day 157 — sync.Pool: reuse hot-path objects; Reset-after-Get; pointers only; GC may drain; drop outliers
  • Day 158 — Load testing: vegeta (open model) vs k6 (closed/arrival-rate); p50/p95/p99; coordinated omission; the knee
  • Day 159 — govulncheck (reachable CVEs, source/binary modes) + gosec (insecure patterns); honest triage; CI gating
  • Day 160 — AuthN vs AuthZ; constant-time compare; identity in context via private key type; 401/403/429; token-bucket rate limiting
  • Day 161 — Week review + recall
  • Stdlib examples: pprof, escape, syncpool, ratelimit
  • Exercises solved: 3 (tokenbucket, allocfree, bufpool) — all go test green

💡 Lessons Learned

  • Measure before optimizing. Profile → cut allocations → pool, in that order. A pool added before a profile is a guess.
  • CPU profiles are statistical samples; heap profiles are live snapshots — different questions (where's time vs what's retained), and runtime.GC() before a heap dump matters.
  • "Pointer" doesn't mean "heap." Escape analysis keeps a local *T on the stack as long as it doesn't outlive the frame; -gcflags=-m is the source of truth.
  • sync.Pool objects are not zeroed and the GC may drain the pool — Reset after Get, keep New cheap, store pointers, never use after Put.
  • Report percentiles, not means; avoid coordinated omission with an open/arrival-rate load model.
  • govulncheck is reachability-based (low noise) and complements gosec (insecure code patterns) — different layers.
  • Constant-time compares (crypto/subtle) and unexported context-key types are small habits that close real holes (timing leak, key collision).

💪 Strengths (what clicked)

  • The six-step pipeline (profile → escape → pool → load → scan → harden) reads as one coherent method.
  • testing.AllocsPerRun as a quick alloc oracle — and remembering the sink to defeat DCE.
  • The token-bucket algorithm and its 401/403/429 middleware ordering are now writable from memory.

🧩 Weaknesses (what's still fuzzy)

  • Reading flame graphs / go tool pprof -http and go tool trace fluently on real profiles.
  • benchstat workflow for statistically comparing before/after.
  • Distributed rate limiting (shared bucket) and JWT/PASETO validation pitfalls.

🔁 Spaced-Repetition Re-quiz (topics from earlier weeks)

  1. Q: (Slices, M1) Why can append mutate a slice the caller still holds, and when does it stop?
    A

If the backing array has spare capacity, append writes in place, so an aliasing slice sees the change. Once it must grow, it allocates a new array and the two slices decouple. Pass a re-sliced copy or slices.Clone when you must avoid aliasing. 2. Q: (Channels, M2) Send/recv/close behavior on nil vs closed channels?

A

nil: send and recv block forever; closing panics. Closed: send panics, recv returns the zero value immediately with ok==false, closing again panics.

3. Q: (Errors, M3) errors.Is vs errors.As?
A

errors.Is walks the chain testing for a target sentinel value. errors.As walks the chain for the first error assignable to a target type and assigns it so you can read its fields.

4. Q: (Context, M5) Where does ctx go and what must you never do with it?
A

First parameter, named ctx. Never store it in a struct for later, never pass nil (use context.TODO()), and only use context.Value for request-scoped data with an unexported key type.

5. Q: (Production, M6 W2) Why does CGO_ENABLED=0 unlock FROM scratch/distroless?
A

It forces a pure-Go static binary with no dynamic libc dependency, so a minimal image with no shared libraries can still run it. A CGO-linked binary needs glibc and crashes on scratch.

🎯 Action Items

  • Add go test -bench=. -benchmem + benchstat to compare a before/after optimization on a real handler.
  • Wire govulncheck ./... and gosec ./... as blocking CI steps (reachable HIGH/CRITICAL fails the build).
  • Put the rate-limit + auth middleware (constant-time compare, 429+Retry-After) in the capstone server.
  • Practice go tool pprof -http and go tool trace on a load-test capture.

🚀 Next Week Goals

  • Capstone (Week 4): assemble architecture (ports & adapters), REST/gRPC API, Postgres + Redis, and the Week-1 observability stack into one deployable service.
  • Apply this week's discipline: profile the capstone, gate it with the scanners, and defend it with the limiter/auth from Day 160.

📊 Metrics

Hours Days hit Exercises Commits Avg confidence
9.5 7/7 3 7 3.⅗

Suggested commit: docs(journal): month 6 week 3 review