Trace Annotationとは#
runtime/traceには、プログラムに意味のあるラベルを付けるためのアノテーションAPIがあります。これにより、単なる「goroutineが実行された」という情報だけでなく、「どの注文を処理しているか」「どのステップを実行しているか」といった業務ロジックの文脈をトレースに埋め込めます。
Task vs Region の違い#
| 特徴 | Task | Region |
|---|---|---|
| スコープ | 複数goroutineにまたがる | 単一goroutine内 |
| 用途 | 論理的な操作の追跡 | 時間区間の測定 |
| 表示場所 | User-defined tasksビュー | View traceのタイムライン |
| ネスト | 可能(親子関係) | 可能(階層構造) |
| 代表例 | Webリクエスト、ジョブ、バッチ | 関数、ステップ、フェーズ |
使い分けの指針#
graph TD
START{何を追跡したい?}
START -->|複数goroutineにまたがる処理| TASK[Task を使う]
START -->|単一goroutine内のステップ| REGION[Region を使う]
TASK --> TASK_EX[・Webリクエスト<br/>・ワーカージョブ<br/>・バッチ処理]
REGION --> REGION_EX[・関数の実行時間<br/>・パイプラインのステージ<br/>・処理のフェーズ]演習1: Task Annotation#
演習の目的#
複数のgoroutineにまたがる処理(注文処理)を題材に、Taskアノテーションで処理を追跡します。
演習ディレクトリ: exercises/trace/02-task/
問題の概要#
注文処理システムで、各注文は以下の3つのフェーズを持ちます:
- 在庫確認
- 決済処理
- 配送手配
各フェーズは異なるgoroutineで実行され、どの注文がどのgoroutineで処理されているか追跡できないのが問題です。
Task 演習手順#
ステップ1: 問題版のトレース取得#
cd exercises/trace/02-task/
go run main.go -trace=trace.out
go tool trace trace.out問題点の確認#
View traceで:
- 複数のgoroutineが動いているが、どれがどの注文か不明
- goroutine間の関係がわからない
ステップ2: 改善版のトレース取得#
go run main_fixed.go -trace=trace_fixed.out
go tool trace trace_fixed.outUser-defined tasks ビュー#
新しいビューが出現!
トレースビューアのメニューにUser-defined tasksが追加されます。
- 各Task(注文)がリストされる
- Taskごとの処理時間(レイテンシ)が表示される
確認ポイント:
processOrderTaskが5回実行されている- 各Taskの開始時刻と終了時刻
- Taskごとのレイテンシ分布
View traceでの可視化#
Taskを選択すると:
- そのTaskに関連するgoroutineがハイライトされる
- Taskのログイベントが表示される
Task APIの使い方#
基本パターン#
import (
"context"
"runtime/trace"
)
func processOrder(ctx context.Context, orderID int) {
// Taskを作成
ctx, task := trace.NewTask(ctx, "processOrder")
defer task.End()
// ログを記録
trace.Logf(ctx, "order", "Processing order %d", orderID)
// 処理を実行
checkStock(ctx, orderID)
processPayment(ctx, orderID)
arrangeShipping(ctx, orderID)
}複数goroutineにまたがる処理#
func processOrder(ctx context.Context, orderID int) {
ctx, task := trace.NewTask(ctx, "processOrder")
defer task.End()
// 在庫確認(メインgoroutine)
checkStock(ctx, orderID)
// 決済と配送を並列実行
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
processPayment(ctx, orderID) // ctxを渡すことでTaskに紐付く
}()
wg.Add(1)
go func() {
defer wg.Done()
arrangeShipping(ctx, orderID) // 同じTaskに紐付く
}()
wg.Wait()
}重要: ctxを各goroutineに渡すことで、全てのgoroutineが同じTaskに紐付きます。
ログの記録#
trace.Log(ctx, "category", "message")
trace.Logf(ctx, "category", "OrderID: %d, Total: %d", orderID, total)ログのカテゴリ例:
"order": 注文関連"stock": 在庫関連"payment": 決済関連"delivery": 配送関連
演習2: Region Annotation#
演習の目的#
画像処理パイプライン(読み込み → リサイズ → フィルタ → 保存)を題材に、Regionアノテーションで各ステップの時間を可視化します。
演習ディレクトリ: exercises/trace/03-region/
問題の概要#
画像処理は以下のステップで構成されますが、各ステップにどれだけ時間がかかっているか不明なのが問題です:
- 画像の読み込み
- リサイズ処理
- フィルタ適用(blur, sharpen, colorAdjust)
- 画像の保存
Region 演習手順#
ステップ1: 問題版のトレース取得#
cd exercises/trace/03-region/
go run main.go -trace=trace.out
go tool trace trace.out問題点の確認#
View traceで:
- goroutineが実行されているのは見えるが、何をしているか不明
- 処理の区切りがわからない
ステップ2: 改善版のトレース取得#
go run main_fixed.go -trace=trace_fixed.out
go tool trace trace_fixed.outView traceでの可視化#
Regionが色分けされて表示!
goroutineの実行区間に色付きブロックが表示されます:
loadImage: 緑色(短い)resizeImage: 青色(中程度)applyFilter: 赤色(長い)- サブRegion:
filter:blur,filter:sharpen,filter:colorAdjust
- サブRegion:
saveImage: 黄色(短い)
クリック時の詳細:
- Region名
- 実行時間(Duration)
- 開始時刻(Start time)
Region APIの使い方#
方法1: WithRegion(推奨)#
trace.WithRegion(ctx, "regionName", func() {
// この中の処理がRegionとして記録される
doWork()
})利点:
- 自動的にRegionが終了する
- スコープが明確
方法2: StartRegion / End#
region := trace.StartRegion(ctx, "regionName")
doWork()
region.End()
// deferパターン
defer trace.StartRegion(ctx, "regionName").End()
doWork()利点:
- 柔軟性が高い
- 複数のreturnパスがある場合に便利
ネストしたRegion#
trace.WithRegion(ctx, "applyFilter", func() {
// 外側のRegion
trace.WithRegion(ctx, "filter:blur", func() {
// 内側のRegion(ネスト)
applyBlur()
})
trace.WithRegion(ctx, "filter:sharpen", func() {
applySharpen()
})
})トレースでは、内側のRegionが外側のRegionの中に表示されます。
実践例#
例1: Webリクエスト処理#
func handleRequest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Taskで全体を追跡
ctx, task := trace.NewTask(ctx, "handleRequest")
defer task.End()
// 各フェーズをRegionで測定
trace.WithRegion(ctx, "authentication", func() {
authenticate(r)
})
trace.WithRegion(ctx, "parseRequest", func() {
parseRequest(r)
})
trace.WithRegion(ctx, "queryDatabase", func() {
queryDB(ctx)
})
trace.WithRegion(ctx, "renderTemplate", func() {
renderHTML(w)
})
}例2: データ処理パイプライン#
func processBatch(ctx context.Context, items []Item) {
ctx, task := trace.NewTask(ctx, "processBatch")
defer task.End()
trace.WithRegion(ctx, "validation", func() {
for _, item := range items {
validate(item)
}
})
trace.WithRegion(ctx, "transformation", func() {
for _, item := range items {
// サブRegion
trace.WithRegion(ctx, "transformItem", func() {
transform(item)
})
}
})
trace.WithRegion(ctx, "persistence", func() {
saveItems(items)
})
}例3: 複雑な計算#
func complexCalculation(ctx context.Context, data []float64) {
defer trace.StartRegion(ctx, "complexCalculation").End()
trace.WithRegion(ctx, "preprocessing", func() {
normalize(data)
})
trace.WithRegion(ctx, "fft", func() {
fft(data)
})
trace.WithRegion(ctx, "analysis", func() {
analyze(data)
})
}ベストプラクティス#
1. Taskの粒度#
良い例:
// リクエスト単位でTask
ctx, task := trace.NewTask(ctx, "handleRequest")悪い例:
// 細かすぎる(オーバーヘッド大)
ctx, task := trace.NewTask(ctx, "addTwoNumbers")2. Regionの粒度#
良い例:
// 意味のある処理単位
trace.WithRegion(ctx, "parseJSON", func() {
json.Unmarshal(data, &result)
})悪い例:
// 細かすぎる
trace.WithRegion(ctx, "i++", func() { i++ })3. 命名規則#
良い例:
trace.WithRegion(ctx, "database:query", func() {...})
trace.WithRegion(ctx, "cache:get", func() {...})
trace.Logf(ctx, "payment", "Amount: %d", amount)推奨:
- カテゴリを含める(
category:action) - 説明的な名前
- 一貫した命名規則
4. Contextの伝播#
良い例:
ctx, task := trace.NewTask(ctx, "main")
go worker(ctx) // ctxを渡す悪い例:
ctx, task := trace.NewTask(ctx, "main")
go worker() // ctxを渡さない → 別のTaskとして扱われる5. TaskとRegionの組み合わせ#
// Taskで全体を追跡
ctx, task := trace.NewTask(ctx, "processOrder")
defer task.End()
// Regionで各ステップを測定
trace.WithRegion(ctx, "validation", validate)
trace.WithRegion(ctx, "processing", process)
trace.WithRegion(ctx, "notification", notify)トラブルシューティング#
User-defined tasksが表示されない#
原因: Taskアノテーションがない
解決: trace.NewTask()を追加
Taskがグループ化されない#
原因: ctxが伝播していない
解決: goroutineにctxを渡す
Regionが表示されない#
原因: RegionのEnd()が呼ばれていない
解決: WithRegionを使うか、defer region.End()
まとめ#
Trace Annotationを使うことで:
Task: 複数goroutineにまたがる処理を追跡
- Webリクエスト、ジョブ、バッチ処理
Region: goroutine内のステップを可視化
- 各フェーズの処理時間を測定
- ボトルネック特定
Log: 重要なイベントを記録
- マイルストーン、エラー、メトリクス
次はFlight Recorderで本番環境でのトレース取得戦略を学びます。