Suốt sê-ri này ta dùng benchmark để đo hiệu năng — nhưng benchmark có giới hạn cố hữu: nó đo cái bạn nghĩ là nóng. Trong production, điểm nóng thật thường ở chỗ bất ngờ: một hàm serialize tưởng rẻ hóa ra ngốn 40% CPU dưới tải thật, một chỗ cấp phát ẩn gây áp lực GC chỉ với dữ liệu thật, một mutex tranh chấp chỉ khi đủ đồng thời. Benchmark không lộ những thứ đó vì nó không có tải thật, dữ liệu thật, tương tác thật.
Continuous profiling giải bằng cách thu hồ sơ (profile) từ dịch vụ đang chạy, liên tục, để thấy CPU và bộ nhớ thực sự đi đâu. Go có công cụ tích hợp mạnh: package net/http/pprof phơi các endpoint cho phép thu profile từ một service live mà không dừng nó. Bài này dựng một service với pprof endpoint, thu CPU và heap profile từ nó khi đang chạy, và phân tích để tìm hàm nóng — nền tảng của mọi hệ continuous profiling.
Bật endpoint pprof: chỉ một dòng import
import _ "net/http/pprof" // đăng ký /debug/pprof/* lên DefaultServeMux
func main() {
go func() {
// mở cổng riêng cho profiling (KHÔNG lộ ra Internet)
http.ListenAndServe("localhost:6060", nil)
}()
// ... dịch vụ chính chạy bình thường ...
}
Chỉ cần import _ "net/http/pprof" (import blank — chạy hàm init của package để đăng ký handler) và mở một HTTP server. Không code thêm gì; các endpoint profiling tự sẵn sàng.
Các endpoint pprof phơi ra
/debug/pprof/profile // hồ sơ CPU (mặc định 30s, ?seconds=N)
/debug/pprof/heap // bộ nhớ đang cấp phát
/debug/pprof/allocs // tổng cấp phát tích lũy
/debug/pprof/goroutine // stack mọi goroutine (tìm rò rỉ goroutine)
/debug/pprof/block // chờ trên channel/mutex
/debug/pprof/mutex // tranh chấp khóa
Mỗi endpoint cho một góc nhìn khác nhau: CPU (thời gian chạy), heap (bộ nhớ giữ), goroutine (tìm rò rỉ goroutine — số goroutine tăng mãi), block/mutex (điểm nghẽn đồng thời).

Hình 1: net/http/pprof phơi các endpoint /debug/pprof/ chỉ bằng một dòng import blank; thu profile từ dịch vụ đang chạy bằng curl (không dừng nó) rồi phân tích bằng go tool pprof (top hoặc flame graph web).*
Đo thật: thu CPU profile từ dịch vụ đang chạy
Chạy service (có một goroutine chạy hàm congViecNong tốn CPU liên tục), rồi thu CPU profile 3 giây từ nó khi đang chạy:
$ curl "localhost:6060/debug/pprof/profile?seconds=3" -o cpu.prof
-rw-r--r-- 455 cpu.prof // thu về mà KHÔNG dừng dịch vụ
Điểm mấu chốt: profile được thu từ service đang chạy, không dừng nó — production tiếp tục phục vụ trong khi bạn lấy hồ sơ. Phân tích:
$ go tool pprof -top cpu.prof
Duration: 3.15s, Total samples = 2.99s (95.03%)
flat flat% sum% cum cum%
2.99s 100% 100% 2.99s 100% main.congViecNong (inline)
0 0% 100% 2.99s 100% main.main.func2
pprof chỉ đúng: main.congViecNong ngốn 100% CPU (2.99s trên tổng 3.15s lấy mẫu). Đây là điểm nóng thật lộ ra từ dịch vụ đang chạy — nếu đây là production, bạn vừa tìm ra chính xác hàm cần tối ưu, không phải đoán. Heap profile thu tương tự (curl .../heap), cho biết bộ nhớ đang cấp phát ở đâu — tìm chỗ ngốn RAM hoặc rò rỉ.
go tool pprof còn có giao diện web (-http=:8080) vẽ flame graph trực quan — nhìn một cái là thấy hàm nào chiếm bao nhiêu, ai gọi ai — mạnh hơn nhiều so với danh sách text cho profile phức tạp.

Hình 2: Đo thật — thu CPU profile 3 giây từ dịch vụ đang chạy (không dừng nó) qua endpoint pprof; go tool pprof -top chỉ đúng main.congViecNong ngốn 100% CPU (2.99s/3.15s); heap profile thu cùng lúc để tìm chỗ ngốn bộ nhớ.
Đánh đổi cần cân nhắc
Endpoint pprof TUYỆT ĐỐI không được lộ ra Internet. Đây là rủi ro bảo mật nghiêm trọng nhất. /debug/pprof/* phơi thông tin nội bộ (tên hàm, stack, cấu trúc code) và cho phép kích hoạt profiling tốn tài nguyên (một kẻ tấn công spam /profile làm tăng tải). Luôn bind vào localhost hoặc một cổng nội bộ chỉ truy cập được qua mạng riêng/VPN, không bao giờ để nó nghe trên cổng công khai. Nhiều sự cố lộ dữ liệu đến từ việc vô tình mount pprof lên cổng chính.
Thu profile có chi phí, dù nhỏ. CPU profiling lấy mẫu (mặc định 100 lần/giây) nên overhead thấp (~vài %), an toàn để chạy trên production. Nhưng block/mutex profiling cần bật rõ ràng (runtime.SetBlockProfileRate, SetMutexProfileFraction) và có chi phí cao hơn — chỉ bật khi cần điều tra, không để mặc định. Heap profile thì gần như miễn phí (Go luôn thu mẫu cấp phát). Hiểu chi phí từng loại để không tự làm chậm chính dịch vụ mình đo.
"Continuous" cần hạ tầng thu thập, không chỉ endpoint. Bài này thu một profile thủ công bằng curl. Continuous profiling thật nghĩa là một hệ thống (Pyroscope, Parca, Grafana Phlare, hoặc dịch vụ cloud như Google Cloud Profiler) tự động thu profile định kỳ từ mọi instance, lưu trữ, và cho bạn xem xu hướng theo thời gian — so profile hôm nay với tuần trước, tìm hồi quy hiệu năng sau một release. Endpoint pprof là nguồn dữ liệu; hệ continuous profiling là phần thu thập và trực quan hóa lịch sử. Với dịch vụ nhỏ, thu thủ công lúc cần đã đủ; quy mô lớn thì đáng đầu tư hệ liên tục.
Ba ý mang về
- Continuous profiling tìm điểm nóng thật mà benchmark không lộ: benchmark đo cái bạn nghĩ là nóng, còn profiling từ dịch vụ đang chạy cho thấy CPU/bộ nhớ thực sự đi đâu dưới tải và dữ liệu thật — đo thật, thu CPU profile 3 giây từ service live và pprof chỉ đúng hàm nóng 100% CPU.
- net/http/pprof bật bằng một dòng import:
import _ "net/http/pprof"phơi các endpoint (profile, heap, goroutine, block, mutex...), thu bằngcurl .../profile?seconds=Nkhông dừng dịch vụ, phân tích bằnggo tool pprof(top hoặc flame graph web). - Nêu rõ rủi ro và giới hạn: endpoint pprof TUYỆT ĐỐI không lộ ra Internet (bind localhost/cổng nội bộ), profiling có chi phí (CPU/heap rẻ, block/mutex đắt phải bật rõ ràng), và "continuous" thật cần hạ tầng thu thập (Pyroscope/Parca...) để xem xu hướng theo thời gian, không chỉ endpoint.
Phần sau ta chuyển sang gỡ lỗi sâu: gỡ lỗi từ xa với Delve trong Go — gắn debugger vào tiến trình đang chạy (kể cả trong container), đặt breakpoint, xem biến, để soi lỗi mà log không lộ.