
こんにちは。よっしーです(^^)
本日は、Go言語のパフォーマンス分析ついて解説しています。
背景
Go言語でアプリケーションを開発していると、必ずと言っていいほど直面するのがパフォーマンスの問題や予期しないバグです。「なぜこんなにメモリを消費しているのか?」「どこで処理が遅くなっているのか?」「このゴルーチンはなぜデッドロックしているのか?」—— こうした疑問に答えるために、Goのエコシステムには強力な診断ツール群が用意されています。
しかし、これらのツールは種類が多く、それぞれ異なる目的と特性を持っているため、どのような場面でどのツールを使うべきか迷ってしまうことも少なくありません。プロファイリング、トレーシング、デバッギング、ランタイム統計…それぞれのツールが解決できる問題は異なり、時には相互に干渉し合うこともあります。
本記事では、Go公式ドキュメントの診断ツールに関する解説を日本語で紹介しながら、実際の開発現場でどのように活用できるかを探っていきます。適切なツールを選択し、効果的に問題を診断することで、より高品質で高性能なGoアプリケーションの開発が可能になるはずです。特に、パフォーマンスのボトルネックを特定し改善することは、ユーザー体験の向上やインフラコストの削減に直結する重要なスキルと言えるでしょう。
Goライブラリでトレースヘッダーをどのように伝播すべきですか?
context.Contextでトレース識別子とタグを伝播できます。業界にはまだ標準的なトレースキーやトレースヘッダーの共通表現は存在しません。各トレーシングプロバイダーは、それぞれのGoライブラリで伝播ユーティリティを提供する責任があります。
トレースヘッダーの伝播 – リクエストの「バトンパス」
トレースヘッダーとは?
例え: 駅伝のたすきに付けられた選手番号とタイム記録
サービス間でリクエストを追跡するための「目印」です。
Service A → Service B → Service C
↓ ↓ ↓
TraceID: abc-123(全員が同じIDを持つ)
なぜ標準がないのか?
各トレーシングシステムが異なる形式を使用:
| システム | ヘッダー形式 | 例 |
|---|---|---|
| Jaeger | uber-trace-id | abc123:def456:0:1 |
| Zipkin | X-B3-TraceId | abc123def456 |
| AWS X-Ray | X-Amzn-Trace-Id | Root=1-abc-123 |
| W3C標準 | traceparent | 00-abc123-def456-01 |
X-Cloud-Trace-Context | abc123/1;o=1 |
Context.Contextを使った伝播
基本的な仕組み
package main
import (
"context"
"fmt"
)
// トレース情報を保持する構造体
type TraceInfo struct {
TraceID string
SpanID string
ParentID string
Sampled bool
}
// contextのキー(外部に公開しない)
type traceKey struct{}
// トレース情報をcontextに格納
func WithTraceInfo(ctx context.Context, info TraceInfo) context.Context {
return context.WithValue(ctx, traceKey{}, info)
}
// contextからトレース情報を取得
func GetTraceInfo(ctx context.Context) (TraceInfo, bool) {
info, ok := ctx.Value(traceKey{}).(TraceInfo)
return info, ok
}
// 使用例
func main() {
// トレース情報を作成
traceInfo := TraceInfo{
TraceID: "abc-123-def",
SpanID: "span-456",
Sampled: true,
}
// contextに格納
ctx := context.Background()
ctx = WithTraceInfo(ctx, traceInfo)
// 別の関数で取得
processRequest(ctx)
}
func processRequest(ctx context.Context) {
if info, ok := GetTraceInfo(ctx); ok {
fmt.Printf("Processing request with TraceID: %s\n", info.TraceID)
}
}
HTTPリクエスト間での伝播
サービス間通信の実装例
package tracing
import (
"context"
"net/http"
)
// HTTPヘッダーとcontextの相互変換インターフェース
type Propagator interface {
// HTTPヘッダーからcontextに注入
Extract(ctx context.Context, headers http.Header) context.Context
// contextからHTTPヘッダーに注入
Inject(ctx context.Context, headers http.Header)
}
// Jaeger形式の実装
type JaegerPropagator struct{}
func (p *JaegerPropagator) Extract(ctx context.Context, headers http.Header) context.Context {
traceHeader := headers.Get("uber-trace-id")
if traceHeader == "" {
return ctx
}
// ヘッダーをパース(簡略化)
info := parseJaegerHeader(traceHeader)
return WithTraceInfo(ctx, info)
}
func (p *JaegerPropagator) Inject(ctx context.Context, headers http.Header) {
info, ok := GetTraceInfo(ctx)
if !ok {
return
}
// Jaeger形式でヘッダーを設定
headers.Set("uber-trace-id",
fmt.Sprintf("%s:%s:%s:%d",
info.TraceID, info.SpanID, info.ParentID,
boolToInt(info.Sampled)))
}
// W3C TraceContext形式の実装
type W3CPropagator struct{}
func (p *W3CPropagator) Extract(ctx context.Context, headers http.Header) context.Context {
traceParent := headers.Get("traceparent")
if traceParent == "" {
return ctx
}
// W3C形式をパース
info := parseW3CHeader(traceParent)
return WithTraceInfo(ctx, info)
}
func (p *W3CPropagator) Inject(ctx context.Context, headers http.Header) {
info, ok := GetTraceInfo(ctx)
if !ok {
return
}
// W3C形式でヘッダーを設定
headers.Set("traceparent",
fmt.Sprintf("00-%s-%s-%02x",
info.TraceID, info.SpanID,
boolToInt(info.Sampled)))
}
実践的な実装パターン
1. HTTPクライアントミドルウェア
// トレースを自動的に伝播するHTTPクライアント
type TracedHTTPClient struct {
client *http.Client
propagator Propagator
}
func NewTracedHTTPClient(propagator Propagator) *TracedHTTPClient {
return &TracedHTTPClient{
client: &http.Client{},
propagator: propagator,
}
}
func (c *TracedHTTPClient) Do(ctx context.Context, req *http.Request) (*http.Response, error) {
// contextからトレース情報をHTTPヘッダーに注入
c.propagator.Inject(ctx, req.Header)
// リクエスト実行
return c.client.Do(req)
}
// 使用例
func callExternalService(ctx context.Context) error {
client := NewTracedHTTPClient(&JaegerPropagator{})
req, _ := http.NewRequest("GET", "http://api.example.com/users", nil)
// トレースが自動的に伝播される
resp, err := client.Do(ctx, req)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}
2. HTTPサーバーミドルウェア
// トレースを自動的に抽出するミドルウェア
func TracingMiddleware(propagator Propagator) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// HTTPヘッダーからトレース情報を抽出
ctx := propagator.Extract(r.Context(), r.Header)
// トレース情報がない場合は新規作成
if _, ok := GetTraceInfo(ctx); !ok {
ctx = WithTraceInfo(ctx, TraceInfo{
TraceID: generateTraceID(),
SpanID: generateSpanID(),
Sampled: shouldSample(),
})
}
// 更新されたcontextで次のハンドラーを実行
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// 使用例
func main() {
propagator := &JaegerPropagator{}
mux := http.NewServeMux()
mux.HandleFunc("/api/users", handleUsers)
// ミドルウェアを適用
handler := TracingMiddleware(propagator)(mux)
http.ListenAndServe(":8080", handler)
}
主要なトレーシングライブラリの実装例
OpenTelemetry(推奨・標準化を目指している)
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func setupOpenTelemetry() {
// W3C TraceContextとBaggageの伝播を設定
otel.SetTextMapPropagator(
propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{}, // W3C標準
propagation.Baggage{},
),
)
}
// HTTPクライアント
client := &http.Client{
Transport: otelhttp.NewTransport(http.DefaultTransport),
}
// HTTPサーバー
handler := otelhttp.NewHandler(mux, "server",
otelhttp.WithPropagators(propagation.TraceContext{}),
)
Jaeger
import (
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/config"
)
func setupJaeger() {
cfg := config.Configuration{
ServiceName: "my-service",
Sampler: &config.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
}
tracer, closer, _ := cfg.NewTracer()
defer closer.Close()
}
// HTTPヘッダーの伝播
span := opentracing.GlobalTracer().StartSpan("operation")
carrier := opentracing.HTTPHeadersCarrier(req.Header)
span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, carrier)
ベストプラクティス
1. ライブラリ作成時の指針
// ライブラリは特定のトレーシングシステムに依存しない
type MyLibrary struct {
// Propagatorインターフェースを受け入れる
propagator Propagator
}
// オプションパターンでカスタマイズ可能に
type Option func(*MyLibrary)
func WithPropagator(p Propagator) Option {
return func(l *MyLibrary) {
l.propagator = p
}
}
func NewMyLibrary(opts ...Option) *MyLibrary {
lib := &MyLibrary{
propagator: &NoOpPropagator{}, // デフォルトは何もしない
}
for _, opt := range opts {
opt(lib)
}
return lib
}
2. 複数形式のサポート
// 複数の伝播形式をサポート
type MultiPropagator struct {
propagators []Propagator
}
func (m *MultiPropagator) Extract(ctx context.Context, headers http.Header) context.Context {
// 最初に成功したものを使用
for _, p := range m.propagators {
if newCtx := p.Extract(ctx, headers); newCtx != ctx {
return newCtx
}
}
return ctx
}
func (m *MultiPropagator) Inject(ctx context.Context, headers http.Header) {
// 全ての形式で注入(互換性のため)
for _, p := range m.propagators {
p.Inject(ctx, headers)
}
}
実装チェックリスト
□ トレーシングシステムの選定
□ 伝播形式の決定(W3C推奨)
□ Contextベースの実装
□ HTTPミドルウェアの作成
□ gRPCインターセプターの作成(必要な場合)
□ エラー時の処理
□ サンプリングの考慮
□ ログとの統合
□ ドキュメント化
まとめ
トレースヘッダーの伝播は:
- 標準がない → 各システムごとの実装が必要
- Context.Contextが中心 → Go標準の伝播メカニズム
- ライブラリは中立的に → 特定システムに依存しない
- OpenTelemetryが有望 → 業界標準を目指している
将来的にはOpenTelemetryが事実上の標準になる可能性が高いため、新規プロジェクトではOpenTelemetryの採用を検討することをお勧めします!
おわりに
本日は、Go言語のパフォーマンス分析について解説しました。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)

コメント