Upgrade to Pro — share decks privately, control downloads, hide ads and more …

アドフリくんにおけるマイクロサービス間での一貫したトレース実現

 アドフリくんにおけるマイクロサービス間での一貫したトレース実現

GREE Tech Conference 2025で発表された資料です。
https://techcon.gree.jp/2025/session/TrackB-1

Avatar for gree_tech

gree_tech PRO

October 17, 2025
Tweet

More Decks by gree_tech

Other Decks in Technology

Transcript

  1. 樋口雅拓 Go / k8s / RecSys / Android / Kotlin

    / Scala / PHP / Python / Swift / AWS / GCP グリーグループのグリーエックス株式会社で、ソフトウェ ア開発に従事。広告システム開発、GREE Platformの立ち 上げ、不正利用対策、チャットアプリ開発、メディア開発 を経て2025年2月より現職。 グリーエックス社 エンジニア 2
  2. 背景と目的 • 複数のマイクロサービスを連携して1つのHTTPリ クエストを処理するシステムを構築した。 • ログやトレース情報がマイクロサービス毎に生成 され、統合して確認することができなかった。 • これを統合して確認できるようにしたため、その 事例について発表する。

    発表構成 • 前提知識 ◦ サービス及びシステム概要 ◦ システムの一部であるRTBシステムについて ◦ RTBシステムのObservabilityについて • 提案方式 ◦ 一貫したトレースの実現方法 前提知識を説明した後、提案方式について説明します。 アドフリくん管理画面などRTB以外のサーバー機能、レ ポート機能については、時間の都合で割愛させていただ きます。 3
  3. アドフリくん - システム概要 モバイルアプリ向けの広告配信プラットフォーム • メディエーション: 複数のアドネットワークを統合管理 • 収益最適化: リアルタイムでの配信比率調整

    • RTB対応: Real-Time Biddingによる高収益化 • 運用効率: 自動化された管理・監視システム 6 アドフリくんは、以下のコンポーネントから構成されています。 • アドフリくんサーバー: 配信設定(利用するアドネットワークのリストや条件)をSDKに伝える。 • アドフリくんSDK: モバイルアプリに組み込み、サーバーから受け取った配信設定に従って広告を取得する。 つまり、アドフリくんはSDKとサーバーが連携して価値を提供しています。 次のスライドでは、アドフリくんサーバーが配信設定を作る方法について説明します。
  4. アドフリくん - SDK概要 • init()で広告設定を読み込みます。 ◦ 広告設定には、アドネットワークの配信比 率や優先順位が含まれています。 • load()で広告を読み込みます。

    ◦ 配信設定を元に選択されたアドネットワー クに要求します。 • play()で広告を表示します。 7 // SDK初期化 AdfurikunSdk.init(activity) // 広告読み込み AdfurikunSdk.load("REWARD_APP_ID") // 再生準備確認・表示 if (AdfurikunSdk.isPrepared("REWARD_APP_ID")) { AdfurikunSdk.play("REWARD_APP_ID") } 実装例: Android
  5. アドフリくん - アドフリくんサーバー概要 アドフリくんサーバーは、複数のアドネットワークの配信比率を適 切に調整して、収益最大化するものです。 アドフリくんでは、以下3種類に対応しています。 ウォーターフォール配信 優先度を予め設定しておき、その順序で問い合わせる。 一般的に単価が高いDSPほどターゲティングやキャップがキツく、 在庫が少ないため高単価から順番に問い合わせることが合理的。

    ウォーターフォールRTBコンビネーション配信 ウォーターフォールとRTBの良い所取りをしたもの。 過去の実績からRTBのeCPMを計算し、ウォーターフォールのeCPM と比較して適切な場所に差し込む。 これにより、確実に高単価が見込めるDSPへの問い合わせをRTBよ り優先させることができる。 8 優先度1: AdMob (eCPM: $5.0) ↓ (広告なし) 優先度2: AppLovin (eCPM: $4.0) ↓ (広告表示) 並列入札: ├─ AdMob: $4.2 ├─ Unity Ads: $4.7 ← 勝者(広告表示) └─ AppLovin: $4.5 RTB (Real-Time Bidding) リクエスト毎にDSPに問い合わせ、最も高い入札額を提示した DSPの広告を配信する。 確実に最高額の広告を表示できるが、仕組みが複雑。 まとめ • ウォーターフォール配信: 対象アプリに最適な配信設定 • RTB: 対象アプリとユーザーに最適な配信設定 • ウォーターフォールRTBコンビネーション配信: 良い所 取り 直感的にはRTBが最適に見えます。 しかし、RTBと通常配信で在庫が分かれているケースが多いた め、組み合わせる事でさらに良い結果を得られる事が多い。 優先度1: AdMob (eCPM: $5.0) ↓ (広告なし) 優先度2: RTB(Unity Ads) (eCPM: $4.7) ↓ (広告表示)
  6. RTBシステム RTB機能を提供するシステムです。以下のよう な特徴があります。 ここでは、Observability以外の部分について説 明します。 システム特徴 • 組織: スクラム開発の導入と運用 •

    設計: ドメイン駆動設計(DDD)とC4 Modelsによる可視化 • 実装: Clean Architectureの適用とテスト 駆動開発の実践 • 構築: マイクロサービスとして機能ごとに 分離されたサービス群 • 運用: Google Cloud Observabilityを利用 したログ、トレース、メトリックス 10
  7. RTBシステムの特徴 - 組織 スクラム開発の採用 1週間スプリント - 新規技術と不明確な要件への対応 - 素早い学習サイクルの実現 -

    課題の早期発見と対応 会議体の制定 - スプリントプランニング: 毎週1時間 - デイリースクラム: 毎日30分 - レトロスペクティブ: 毎週1時間 透明性の向上 - スプリントバックログの可視化 - バーンダウンチャートによる進捗管理 - ステークホルダーとの情報共有 導入効果と課題 メリット - チームコミュニケーションの改善 - 問題解決の文化醸成 - プロジェクト進捗の可視化 - 適切なフィードバックの獲得 デメリット - ベロシティの不安定化 - 中長期スケジュール見積もりの困難 - 会議コストの増加 今後の改善 - 2週間スプリントへの移行を検討 - より安定したベロシティの確立 11
  8. RTBシステムの特徴 - 設計 C4 Modelsは、ソフトウェアアーキテクチャを4つの抽象レベルで表現する設計手法です。 従来のUMLや複雑な設計書に比べ、ステークホルダー全員が理解しややすい図で表現できます。 4階層の構成 • Level 1

    (Context): システム全体と外部システムとの関係 • Level 2 (Container): システム内の主要コンポーネントと技術スタック • Level 3 (Component): 各コンテナ内部の詳細設計 • Level 4 (Code): クラス図やシーケンス図レベルの実装詳細 DDDとC4 Modelsの組み合わせにより、複雑なドメインを適切に設計・可視化できます。 チーム全員での設計議論が活発化し、より良い設計につながりました。 12
  9. RTBシステムの特徴 - 実装 Clean Architecture 依存関係の制御 - MVCアーキテクチャの密結合問題解決 - ビジネスロジックの外部技術からの独立

    - レイヤー間の責務分離 テスタビリティの向上 - 依存性注入とインターフェース活用 - 外部APIやデータベースのモック化 - 純粋なビジネスロジックのテスト 変更容易性の確保 - 特定層の変更が他層に影響しにくい構造 - データベース変更時のリポジトリ層のみ修正 レイヤー構成と責務 - Controller: HTTP、外部とのデータ変換 - UseCase: ワークフロー、アプリ固有ルール - Entity: ドメインモデル、ビジネスロジック - Repository: DB/APIなど外部技術の詳細実装 依存関係の方向性 13 - 外側の層は内側の層に依存 - 内側の層は外側の層を知らない - インターフェースによる疎結合 Controller → UseCase → Entity
  10. RTBシステムの特徴 - 構築 マイクロサービス 機能ごとにマイクロサービスとして構築してい ます。マイクロサービスには、以下のものがあ ります。 • info API:

    SDKからのリクエストを受け、 オークションAPIを使ってオークション処 理を行い、レスポンスを構築する。 • オークションAPI: オークション処理を行 い、広告コンテンツをストレージに保 存。 • Ad API: 広告コンテンツを応答する。 • DSP Notifier: オークション結果をDSPに 通知する。 CI/CDパイプライン GitHubActionsとCloudDeployを利用しています。 14
  11. Observabilityの三本柱 📝 ログ (Logs) イベントの詳細情報を記録 • 構造化ログ: JSON形式で の統一 •

    コンテキスト: TraceIDと の関連付け • 検索性: クエリによる高速 検索 16 🔍 トレース (Traces) リクエストの流れを追跡 • レイテンシ分析: ボトル ネックの特定 • 依存関係: サービス間の関 係性把握 • エラー伝搬: 障害の原因追 跡 📊 メトリクス (Metrics) システムの健康状態を数値で把握 • エラー率: HTTP 4xx/5xx エラーの割合 • スループット: 秒間リクエ スト数 • リソース使用率: CPU・メ モリ・ネットワーク
  12. Observability - ログ Cloud Logging SDKを利用しています。 これにより、Traceのような直感的なKeyで構造化ログを書き 込むことができます。 (逆に使わない場合、"logging.googleapis.com/trace"と いった分かりづらいKeyが必要。)

    17 ## ログ出力例 { "severity": "INFO", "trace": "projects/gree-peridot/traces/37e8e45b2fc23fd035b235bcc3a58555", "spanId": "9fb57bf9f69ab602", "logName": "projects/gree-peridot/logs/info", "jsonPayload": { … }, ... } func (l *LoggingClient) OutputSpan(...) { entry := logging.Entry{ Severity: severity, Trace: fmt.Sprintf("projects/%s/traces/%s", l.projectID, traceID), SpanID: spanID, Payload: payload, } logger.Log(entry) ログパッケージ設計 ログ出力例
  13. Observability - トレース - トレースの各要素は、スパンと呼ばれる。こ のスパンは、実装で範囲を決める。 - ログにtraceIdとspanIdを追加すると、トレー スとログを紐付けることができる。 18

    _, span := tracer.Start(ctx, "AuctionEntity", trace.WithSpanKind(trace.SpanKindInternal), trace.WithAttributes( attribute.String("component", "info"), attribute.String("layer", "entity"), ), ) defer span.End() traceID := span.SpanContext().TraceID().String() spanID := span.SpanContext().SpanID().String() e.logger.OutputJsonSpan(..., traceID, spanID, トレーシング実装
  14. Observability - メトリクス - システムメトリクスは自動収集される。 - ビジネスメトリクスは明示的に送信する必要 がある。 19 meter

    := otel.GetMeterProvider().Meter("adfurikun-business") auctionCounter, _ := meter.Int64Counter( "business_auctions_total", metric.WithDescription("Total number of auctions processed"), ) m.auctionCounter.Add(ctx, 1, metric.WithAttributes( attribute.String("auction_type", auctionType), ), ) ビジネスメトリクス実装例
  15. 課題: GCPとアプリケーションのトレース連携 Cloud Load BalancingとGKEアプリケーション間のトレース情報の問題 21 Application Span (ルートスパン) ├──

    Business Logic Span └── Request └── Other Micro Service └── Business Logic Span Load Balancer Span (見えない) └── Application Span (孤立) ├── Business Logic Span └── Request Other Micro Service └── Business Logic Span 実際の結果 全体のトレースが確認できない。 期待していたトレース RTBシステムは、複数のマイクロサービスを使って HTTPリクエストを処理する。 HTTP Request全体のトレースを確認したい。
  16. 解決策1: トレース情報の伝播 トレース情報の伝播はPropagatorの責務。そこで、以下のようにPropagatorを設定。 22 Load Balancer Span (見えない) └── Application

    Span (孤立) ├── Business Logic Span └── Request └── Other Micro Service └── Business Logic Span これにより階層構造になったが、ルートスパンが見えないため探しづらい。 X-Cloud-Trace-Context: TRACE_ID/SPAN_ID;o=TRACE_TRUE HTTP RequestのHeaderに付与されるTRACE_IDがリクエスト側から引き継がれるようになる。 otel.SetTextMapPropagator( propagation.NewCompositeTextMapPropagator( gcppropagator.CloudTraceFormatPropagator{},... )) Load Balancer Spanが見えないのは、Load BalancerがSpan情報をCloud Traceに送信できないため。 それなら、消してしまおう。
  17. 解決策2: Load Balancer Spanの削除 // middlewareが生成するスパンにattributeを設定 otelfiber.WithCustomAttributes(func(...) ... { return

    []attribute.KeyValue{ // middlewareで生成するスパンに印をつける attribute.Bool("parent_span_is_lb", true), }}), processors: # otelFiberのMiddlewareで作成したスパンの親スパン IDを削除する transform: trace_statements: - context: span conditions: # 条件に一致するスパンの親スパン ID を持つ場合 - span.attributes["parent_span_is_lb"] == true statements: # 条件に一致するスパンの親スパン ID を削除 - set(span.parent_span_id, SpanID(0x00000)) # span情報を編集したことを記録 - set(span.status.message, "span edited") 23 Application Span (ルートスパン) ├── Business Logic Span └── Request └── Other Micro Service └── Business Logic Span トレース表示 期待通りの表示となった。 OpenTelemetry Collector設定 Application Spanの親スパン情報を消し、ルートスパンと した。 アプリケーション側の実装
  18. OTTL実装の詳細 OTTL構文の解説 バージョン注意点 OTTLの仕様は変更されることがあります 24 よく使用される関数 - set(): 値の設定 -

    delete_key(): キーの削除 - replace_pattern(): パターン置換 - truncate_all(): 文字列の切り詰め 主要な構文要素 - context: 処理対象の指定(span, spanevent, metric, log) - conditions: 変換を適用する条件 - statements: 実際の変換処理 transform: trace_statements: - context: span # スパンコンテキスト で処理 conditions: # 条件部分 - span.attributes["parent_span_is_lb"] == true statements: # 実行部分 - set(span.parent_span_id, SpanID(0x00000)) - set(span.status.message, "span edited") 他のプロセッサーとの使い分け - Filter Processor: 単純なフィルタリング - Attributes Processor: 属性の追加・削除 - Transform Processor: 複雑な変換ロジック 用途に応じて適切なプロセッサーを選択することが重要 # v0.120.0以降 processors: transform: trace_statements: - context: span statements: - set(span.parent_span_id, SpanID(0x00000)) # v0.120.0以前 processors: transform: trace_statements: - context: span statements: - set(parent_span_id, SpanID(0x00000))
  19. CloudDeployとPrometheusの連携によるカナリアデプロイ 課題 - リスクの高いデプロイ: 一度に全てのトラ フィックが新バージョンに流れる - 障害の影響範囲: 問題発生時に全ユーザーに影 響

    - ロールバック時間: 手動での判断と実行が必要 RTBシステム特有の要件 - 低レイテンシ: 50ms以下のレスポンス要求 - 高可用性: 99.9%以上の稼働率必要 - 収益への直接影響: デプロイ失敗が売上に直結 自動判定指標 - エラー率: 0.1%以下を維持 - レスポンス時間: P99で50ms以下 - DSP成功率: 95%以上 - 収益指標: 前バージョン比で5%以上の下落なし 26 現在のデプロイフロー カナリアデプロイフロー