はじめまして!
同志社大学大学院 修士 1 年の那須 渚です。
2025 年 4 月 2 日〜4 月 30 日の 1 ヶ月間、CyberAgent が主催するインターンシップ「CA Tech JOB」に参加しました。

私が配属されたのは、WINTICKETのバックエンドチームです。

本稿では、インターンシップに参加した背景や目的、実際に取り組んだタスク、使用した技術、そして学んだことについてご紹介します。

今回のインターンシップに参加した目的は、以下の 2 点です。

  • Golang の理解を深めること
     本インターンシップ前に、Go Collegeに参加し、Golang の基礎を学びました。
     その知識を実際のプロダクト開発の現場で活かし、より実践的なスキルを習得したいと考えていました。
  • 高トラフィックかつ高セキュリティが求められる現場での開発経験
     toC 向けの環境では、トラフィック量やセキュリティ要件への対応が不可欠です。これまでそのような高負荷・高セキュリティな環境での開発経験がなかったため、今回のインターンを通じてそのような現場での対応力を身につけたいと考えていました。

タスク概要

インターンシップ期間中に、以下の 3 つのタスクに取り組みました。

  • 管理画面 API の実装
  • テストコードのリファクタ
  • 本番環境への分散トレーシングの導入

本ブログではこのうち、本番環境への分散トレーシングの導入について紹介します。

分散トレーシングを導入する目的

WINTICKETのバックエンドはマイクロサービスアーキテクチャを採用しており、サービス間の呼び出しが複雑に絡み合っています。こうした構成において、どのサービスでどれくらいの処理時間が発生しているのかを把握し、ボトルネックを特定するには、分散トレーシングが不可欠です。

実際、開発環境にはすでに分散トレーシングが導入されていましたが、本番環境では一部のサービスのみに留まっており、システム全体としてトレーシングが十分に活用されているとは言えない状況でした。

また、WINTICKETはトラフィックが多く、すべてのリクエストを記録しようとすると、トレースデータの転送・保存に関するコストが膨大になるという課題があります。さらに、適切にサンプリングレートを設計しないと、トレース収集が制限されたり、重要なトレースが漏れてしまうリスクもあります。

そのため、本番環境でも最適な設定で分散トレーシングを導入し、全体の可観測性を高めながら、コストやシステム負荷を抑える仕組みづくりが求められていました。

 

サンプリングレートの課題

サンプリングレートの設定

WINTICKETは高トラフィックなサービスであるため、すべてのリクエストに対してトレースを取得することは、リソースの逼迫や高コストにつながる可能性があります。したがって、適切なサンプリングレートの設定が必要でした。

サンプリングレートを単純に各マイクロサービスへ設定した場合の問題

すべてのマイクロサービスに対してサンプリングレートを 0.01(1%)に設定した場合、完全なトレースが取得される確率が極端に低くなるという問題があります。
具体例として、以下のようなサービス構成があるとします:

APIリクエスト:serviceA  serviceB  serviceC

このとき、3 つすべてのサービスでトレースが収集される確率は
1% × 1% × 1% = 0.000001(= 0.0001%)
となり、完全なトレースがほとんど得られなくなってしまいます。

開発環境ではサンプリングレートを高く(あるいは常に収集する設定)にしていたため、スパン(各サービス間の処理単位)が正しく取得されており、この問題に気づくことができませんでした。
実際に確認してみると、開発環境でも一部のスパンが切れているケースがあり、本番環境ではより顕著にトレースが欠損する可能性があることが分かりました。

サンプリングによるトレースの分断

解決:ParentBased サンプリング

上述のように、すべてのサービスで個別にサンプリングレートを1%に設定した場合、トレース全体の整合性が保てず、途中でトレースが途切れてしまうという課題が発生しました。

この問題に対する解決策として、ParentBased サンプリングを採用しました。

ParentBased サンプリングとは?

ParentBased は、親スパンのサンプリング状態に従って、子スパンのサンプリング可否を決定する戦略です。

  • 親スパンがサンプリングされている場合 → 子スパンも必ずサンプリングされる
  • 親スパンがサンプリングされていない場合 → 子スパンも記録されない
  • 親が存在しない(ルートスパン)の場合 → サンプリングレートの設定に従って判断される

この戦略により、一貫したトレースの収集が可能になります。

期待される効果

  • ルートスパンで 1% の確率でサンプリングされたリクエストに関しては、その後の全てのサービスのスパンも記録される
  • トレース全体が途切れずに完全な形で収集される
  • Grafana 上でも 一貫した可視化が可能となり、ボトルネックの特定や障害解析のやりやすくなる
  • 無駄なトレーシングデータが減るため、コスト削減が期待される
    スパンがつながった完全なトレース

    実際の設定例(Go)

    go

    // OpenTelemetry のサンプラー設定
    
    tp := sdktrace.NewTracerProvider(
    
      sdktrace.WithSampler(
    
        sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.01)),
    
      ),
    
    )


    トレースコンテキスト伝播でつまずいたところ

    Google Cloudの一部サービス(例:Application Load Balancer(ALB)、Cloud Runなど)は、リクエストヘッダーに対して自動的に X-Cloud-Trace-Contexttransparentを付与または上書きします。

    • X-Cloud-Trace-Context : Google Cloud独自仕様のトレースヘッダーです。形式は TRACE_ID/SPAN_ID;o=TRACE_TRUE となり、Google Cloudサービス(ALB、Cloud Runなど)が自動的に付与・書き換えを行います。OpenTelemetryとは直接互換性がありません。
    • traceparent : OpenTelemetryやW3C Trace Context標準に従った、サービス間でトレース情報(trace_id, span_id, サンプリングフラグなど)を伝播するための汎用的なヘッダーです。

    詳しくは、Google Cloudのトレースヘッダーに関する(公式ドキュメント)をご参照ください。

    Google CloudのALBによるトレースヘッダーの自動付与

    問題点

    ParentBased サンプリングを使用している場合、ALB が自動で付与するX-Cloud-Trace-Context のサンプリングレートに依存してしまい、Serverで必要なトレース情報が取れない。


  • 対応策:Fastly でのヘッダー付与

    Google Cloudの ALBに到達する前段階で、ヘッダーを明示的に付与することで、ALB によるヘッダー上書きの問題を防止する対策を行いました。WINTICKETの場合は、Google Cloud の ALB に到達する前の Fastly(CDN レイヤ)において、traceparentX-Cloud-Trace-Context の両方を同一の値で明示的に付与しています。 これにより、ALB が既存のヘッダーを上書きせずにそのまま通す挙動(上記パターン①)を誘発し、意図したトレース ID を維持したままリクエストを処理することが可能になります。

    これにより、ALBでの不要なヘッダー上書きを回避し、OpenTelemetryのトレース伝播を正確に制御できます。

    なお、今回のケースではすべてのAPIリクエストが必ずCDN(Fastly)を経由する構成になっていたため、 この方法で安全に全リクエストに対して一貫したヘッダー付与と制御を実現できました。

    Fastlyでのトレースヘッダー明示付与

*追記

インターン終了後、ALBではX-Cloud-Trace-Contextに付与されているスパンIDを上書きしリクエストを流していることが発覚しました。また、ALBからはトレース、スパンを収集基盤へとエクスポートしないため[参考]、親スパンを示すX-Cloud-Trace-ContextのスパンIDがALBで途切れてしまい、クライアントからサーバーにかけた一貫したトレースの取得ができないことがわかり、別途対策をとることとなりました。

学んだこと

  • 実務レベルのGolang
    自分はGolang歴2ヶ月程度とまだ初心者でしたが、コードレビューではプロフェッショナルなエンジニアの方々から具体的で実践的なフィードバックをいただき、自分のコードの癖や改善点を客観的に知ることができました。
  • チーム開発の体制と運用方法
    特に印象的だったのが、レビューを最優先とする文化や、週1回の「サーバータイム」でチーム全体でリファクタリングに取り組む時間を設けている点です。開発だけでなく、コードの保守性・可読性を意識する重要性を実感しました。
  • 大規模開発ならではの視点
    高トラフィックな環境下でのシステム開発においては、単なる実装だけでなく「リクエスト数をどう効率的にトレース・サンプリングするか」といったパフォーマンスや観測性の観点も考慮する必要があることを学びました。
  • 他チームとの連携
    バックエンドチームだけでなく、インフラチームとの協力の中で、実際の運用を支える仕組みやセキュリティ、信頼性設計についても学ぶことができました。

さいごに

まずは、一ヶ月という限られた時間の中で、WINTICKETのバックエンドチーム・インフラチームの皆様ありがとうございました

WINTICKETでは、エンジニアとビジネスメンバーの距離が近く、まさに「ワンチーム」で動いている環境であることを肌で感じました。Slack上での会話や週次のMTGなどでも、エンジニア視点とビジネス視点の両立が日常的に意識されており、自分も自然とその空気の中で成長できたように感じています。

また、オンボーディング期間を通してWINTICKETのサービスのドメイン知識を体系的に学ぶことができ、自分がこのサービスの一員として働いているという自覚を持てたことも、大きな収穫でした。

最後に、就業型インターンという形で、実際のプロダクト開発現場に参加できたことは、通常のインターンでは得られない貴重な経験となりました。開発スキルだけでなく、働くうえでの姿勢やチームコミュニケーションのあり方も学ぶことができ、自分のエンジニアとしての基礎を築く大きな一歩になったと感じています。

今後もこの経験を糧に、さらなるスキルアップを目指していきたいと思います。