Slide 1

Slide 1 text

アドフリくんにおけるマイクロサービ ス間における一貫したトレース実現 - アドフリくんRTBシステムにおけるObservabilityの実装事例 - グリーエックス社 エンジニア 樋口雅拓

Slide 2

Slide 2 text

樋口雅拓 Go / k8s / RecSys / Android / Kotlin / Scala / PHP / Python / Swift / AWS / GCP グリーグループのグリーエックス株式会社で、ソフトウェ ア開発に従事。広告システム開発、GREE Platformの立ち 上げ、不正利用対策、チャットアプリ開発、メディア開発 を経て2025年2月より現職。 グリーエックス社 エンジニア 2

Slide 3

Slide 3 text

背景と目的 ● 複数のマイクロサービスを連携して1つのHTTPリ クエストを処理するシステムを構築した。 ● ログやトレース情報がマイクロサービス毎に生成 され、統合して確認することができなかった。 ● これを統合して確認できるようにしたため、その 事例について発表する。 発表構成 ● 前提知識 ○ サービス及びシステム概要 ○ システムの一部であるRTBシステムについて ○ RTBシステムのObservabilityについて ● 提案方式 ○ 一貫したトレースの実現方法 前提知識を説明した後、提案方式について説明します。 アドフリくん管理画面などRTB以外のサーバー機能、レ ポート機能については、時間の都合で割愛させていただ きます。 3

Slide 4

Slide 4 text

サービス及びシステム概要 4

Slide 5

Slide 5 text

アドフリくん - サービス概要 ● モバイルアプリ向けの広告配信プラット フォーム ● 「動画リワード広告」の取扱量は国内No.1 5 動画リワード広告とは Pangle/Unity/APP LOVIN/Mintegral/Info Source/AdMob/Digital Turbine/LINE Ads Network/nend/Zucks/ maio/AMoAD

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

アドフリくん - 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

Slide 8

Slide 8 text

アドフリくん - アドフリくんサーバー概要 アドフリくんサーバーは、複数のアドネットワークの配信比率を適 切に調整して、収益最大化するものです。 アドフリくんでは、以下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) ↓ (広告表示)

Slide 9

Slide 9 text

システムの一部である RTBシステムについて 9

Slide 10

Slide 10 text

RTBシステム RTB機能を提供するシステムです。以下のよう な特徴があります。 ここでは、Observability以外の部分について説 明します。 システム特徴 ● 組織: スクラム開発の導入と運用 ● 設計: ドメイン駆動設計(DDD)とC4 Modelsによる可視化 ● 実装: Clean Architectureの適用とテスト 駆動開発の実践 ● 構築: マイクロサービスとして機能ごとに 分離されたサービス群 ● 運用: Google Cloud Observabilityを利用 したログ、トレース、メトリックス 10

Slide 11

Slide 11 text

RTBシステムの特徴 - 組織 スクラム開発の採用 1週間スプリント - 新規技術と不明確な要件への対応 - 素早い学習サイクルの実現 - 課題の早期発見と対応 会議体の制定 - スプリントプランニング: 毎週1時間 - デイリースクラム: 毎日30分 - レトロスペクティブ: 毎週1時間 透明性の向上 - スプリントバックログの可視化 - バーンダウンチャートによる進捗管理 - ステークホルダーとの情報共有 導入効果と課題 メリット - チームコミュニケーションの改善 - 問題解決の文化醸成 - プロジェクト進捗の可視化 - 適切なフィードバックの獲得 デメリット - ベロシティの不安定化 - 中長期スケジュール見積もりの困難 - 会議コストの増加 今後の改善 - 2週間スプリントへの移行を検討 - より安定したベロシティの確立 11

Slide 12

Slide 12 text

RTBシステムの特徴 - 設計 C4 Modelsは、ソフトウェアアーキテクチャを4つの抽象レベルで表現する設計手法です。 従来のUMLや複雑な設計書に比べ、ステークホルダー全員が理解しややすい図で表現できます。 4階層の構成 ● Level 1 (Context): システム全体と外部システムとの関係 ● Level 2 (Container): システム内の主要コンポーネントと技術スタック ● Level 3 (Component): 各コンテナ内部の詳細設計 ● Level 4 (Code): クラス図やシーケンス図レベルの実装詳細 DDDとC4 Modelsの組み合わせにより、複雑なドメインを適切に設計・可視化できます。 チーム全員での設計議論が活発化し、より良い設計につながりました。 12

Slide 13

Slide 13 text

RTBシステムの特徴 - 実装 Clean Architecture 依存関係の制御 - MVCアーキテクチャの密結合問題解決 - ビジネスロジックの外部技術からの独立 - レイヤー間の責務分離 テスタビリティの向上 - 依存性注入とインターフェース活用 - 外部APIやデータベースのモック化 - 純粋なビジネスロジックのテスト 変更容易性の確保 - 特定層の変更が他層に影響しにくい構造 - データベース変更時のリポジトリ層のみ修正 レイヤー構成と責務 - Controller: HTTP、外部とのデータ変換 - UseCase: ワークフロー、アプリ固有ルール - Entity: ドメインモデル、ビジネスロジック - Repository: DB/APIなど外部技術の詳細実装 依存関係の方向性 13 - 外側の層は内側の層に依存 - 内側の層は外側の層を知らない - インターフェースによる疎結合 Controller → UseCase → Entity

Slide 14

Slide 14 text

RTBシステムの特徴 - 構築 マイクロサービス 機能ごとにマイクロサービスとして構築してい ます。マイクロサービスには、以下のものがあ ります。 ● info API: SDKからのリクエストを受け、 オークションAPIを使ってオークション処 理を行い、レスポンスを構築する。 ● オークションAPI: オークション処理を行 い、広告コンテンツをストレージに保 存。 ● Ad API: 広告コンテンツを応答する。 ● DSP Notifier: オークション結果をDSPに 通知する。 CI/CDパイプライン GitHubActionsとCloudDeployを利用しています。 14

Slide 15

Slide 15 text

RTBシステムの Observabilityについて 15

Slide 16

Slide 16 text

Observabilityの三本柱 📝 ログ (Logs) イベントの詳細情報を記録 ● 構造化ログ: JSON形式で の統一 ● コンテキスト: TraceIDと の関連付け ● 検索性: クエリによる高速 検索 16 🔍 トレース (Traces) リクエストの流れを追跡 ● レイテンシ分析: ボトル ネックの特定 ● 依存関係: サービス間の関 係性把握 ● エラー伝搬: 障害の原因追 跡 📊 メトリクス (Metrics) システムの健康状態を数値で把握 ● エラー率: HTTP 4xx/5xx エラーの割合 ● スループット: 秒間リクエ スト数 ● リソース使用率: CPU・メ モリ・ネットワーク

Slide 17

Slide 17 text

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) ログパッケージ設計 ログ出力例

Slide 18

Slide 18 text

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, トレーシング実装

Slide 19

Slide 19 text

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), ), ) ビジネスメトリクス実装例

Slide 20

Slide 20 text

一貫したトレースの実現方法 20

Slide 21

Slide 21 text

課題: 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全体のトレースを確認したい。

Slide 22

Slide 22 text

解決策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に送信できないため。 それなら、消してしまおう。

Slide 23

Slide 23 text

解決策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の親スパン情報を消し、ルートスパンと した。 アプリケーション側の実装

Slide 24

Slide 24 text

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))

Slide 25

Slide 25 text

今後の展望 25

Slide 26

Slide 26 text

CloudDeployとPrometheusの連携によるカナリアデプロイ 課題 - リスクの高いデプロイ: 一度に全てのトラ フィックが新バージョンに流れる - 障害の影響範囲: 問題発生時に全ユーザーに影 響 - ロールバック時間: 手動での判断と実行が必要 RTBシステム特有の要件 - 低レイテンシ: 50ms以下のレスポンス要求 - 高可用性: 99.9%以上の稼働率必要 - 収益への直接影響: デプロイ失敗が売上に直結 自動判定指標 - エラー率: 0.1%以下を維持 - レスポンス時間: P99で50ms以下 - DSP成功率: 95%以上 - 収益指標: 前バージョン比で5%以上の下落なし 26 現在のデプロイフロー カナリアデプロイフロー

Slide 27

Slide 27 text

まとめ 27

Slide 28

Slide 28 text

まとめ ● アドフリくんを導入することで、メディアの広告収益を最大化することが できます。 ● アドフリくんは、継続的に改善を行う仕組みとなっています。 ● 同様の取り組みを行うことで、担当プロジェクトが継続的な改善可能とな ります。 28

Slide 29

Slide 29 text

ご清聴ありがとうございました 29

Slide 30

Slide 30 text

No content