この記事は GMOインターネットグループ Advent Calendar 2025 23日目の記事です。
みなさん、おはようございます!こんにちは!こんばんは!
GMOペパボ株式会社の横山です!!!!!!
目次
はじめに
こんにちは!GMOペパボ株式会社所属の横山です。社内ではあだ名で呼び合う文化があり、はるおつと呼ばれています。普段はロリポップ・ムームードメイン事業部にてムームードメインの開発を主に行なっています。
今回のアドベントカレンダーでは、先月11月18日にリリースされたばかりの、「Google Workspace カード」の信頼性向上に向けて、「可観測性ってなに?」状態から行なった可観測性向上とSRE的アプローチについて、つまづきポイントとそこからの学びについてまとめたいと思います。
Google Workspaceカードとは?
みなさん、Google Workspaceカードを知っていますか?!?!
ムームードメインは、ドメイン取得サービスの一環として、2025年2月からは Google Workspace の販売も開始し、ドメインと Google Workspace のシームレスな連携を提供してきました。
今回、このドメインとGoogle Workspaceの連携を、家電量販店などでカードを購入することで、購入者に対して Google Workspace の契約管理からドメイン取得・管理まで一貫して提供することが可能になりました! (ぜひお店で見かけた際には、あの記事のあれだ!と思ってくれたら嬉しいです)

システム概要
ユーザーの購入フローは以下の通りです:
店舗でGoogle Workspace カードを購入
⇩
プロダクトキーを入力
⇩
ムームードメインでドメインを選択 (.com, .jp, .devなど)
⇩
Google Workspace情報の入力 (組織名, adminメールアドレスなど)
⇩
ムームードメインにログイン・登録
⇩
決済情報を入力
⇩
ドメイン取得やGoogle Workspaceのセットアップ処理開始
このフローでは、複数の外部APIとの連携、決済処理、ドメイン取得、Google Workspace設定など、多くのステップが連鎖的に実行されます。
Saga Orchestrationパターンの採用
このような長時間にわたる複数サービス間のトランザクションを安全に処理するため、Saga Orchestrationパターンを採用しました。
Saga Orchestrationパターンの特徴は以下の通りです:
- 長時間トランザクションを小さなローカルトランザクションの連鎖として実装
- 各トランザクションには補償トランザクションを定義
- 失敗時は実行済みステップを逆順で補償
具体的なステップ構成は以下のようになっています:
[正常フロー]
Step1: アカウント検証
↓
Step2: カードの利用
↓
Step3: 契約作成(DB書き込み)
↓
Step4: 決済完了(3DS認証)
↓
Step5: クレカDB登録
↓
Step6: ドメイン取得
↓
Step8: GWS設定(Google API)
↓
Step9: 注文確定
[補償フロー(失敗時は逆順に実行)]
Step2の補償: カードの再有効化
↑
Step3の補償: 取得契約の削除
↑
Step4の補償: 決済返金
↑
Step5の補償: skip (ドメインは取得済みのため)
↑
Step6の補償: skip (クレカの登録は削除する必要がないため)
↑
Step8の補償: GWS設定解除job作成

現状の課題とやるべきこと
可観測性向上に取り組むにあたり、まずムームードメインにおける現状を整理しました。
| 観点 | 現状 | やるべきこと |
|---|---|---|
| ログ | 分散、非統一 | 集中、構造化 |
| トレース | 一部のAPM | End-to-End |
| 外部API | 部分的 | 全て |
| Saga | ログのみ | トレースで状態追跡、分散トレーシング |
私はこのGoogle Workspace カードという新機能追加を切り口として、長年続くサービスに可観測性を組み込むチャンスと捉えました。
段階的アプローチ
一度に全てを実装するのではなく、価値の高い部分から小さくリリースしていく方針を取りました。
Phase1: OpenTelemetry基盤構築
→ 全ての前提、ベンダーロックインを回避し観測の標準化
Phase2: BaseControllerトレーシング追加
→ 全コントローラアクションでのトレース・スパン自動付与
Phase3: Sagaトレーシング
→ GWSカードにおける処理の根幹、最も影響が大きい
Phase4: 外部API可視化
→ 外部API起因の障害の見える化、既存システムの信頼性向上
Phase5: アラート、SLI/SLO設定
→ 検知自動化で運用負荷削減
つまずきポイント①:SpanContextの永続化問題
問題
Sagaパターンでは、セッションとして状態を保存・復元する必要があります。補償処理を実行する際に、元のステップのSpanと関連づけたいと考えました。
OpenTelemetryのLink機能を使うためにSpanContextを使いたかったので、最初はセッションにSpanContextをそのまま保存しようとしました。
# 各ステップでSpanContextをそのまま保存
@step_results[step] = result.merge(
span_context: span.context # ← OpenTelemetryオブジェクト
)
session.saga = @step_results.to_jsonエラー発生
span.context.to_jsonを試みましたが、NoMethodErrorが発生して頭を抱えていました。
undefined method `valid?' for
"#<OpenTelemetry::Trace::SpanContext:0x000055f2740a3c70>":Stringなぜシリアライズできないのか
span.context
# => #<OpenTelemetry::Trace::SpanContext:0x000055f2740a3c70
# @trace_id="\xA1\xB2\xC3...", (16バイトのバイナリ)
# @span_id="\x1A\x2B\x3C...", (8バイトのバイナリ)
# @trace_flags=1,
# @trace_state=...>SpanContextの実体を見てみると:
つまり、単にSpanContextは実行時の一時オブジェクトなのですね。
改めてなぜOpenTelemetryがSpanContextをシリアライズ可能にしていないか考えてみると、以下のような納得感がありました。
- SpanContextは実行時の一時オブジェクトとして設計されている
- トレースはOTLP Exporterで送信されるべき
- アプリケーション側で永続化されることを想定していない
しかし、補償処理を実行する際に、元のステップのSpanと関連づけたい場合はどうしたらよいのでしょうか?そのほかにも、3DS認証などに対しても、同じような問題にあたるのではないかと思います。
解決策:Span属性による関連付け
Linkを使う代わりに、trace_idとspan_idを文字列として保存し、Span属性でオリジナルのtrace_idを関連付ける方式に変更しました。
# ステップ実行時
@step_results[step] = result.merge(
span_context: {
trace_id: span.context.trace_id.unpack1('H*'),
span_id: span.context.span_id.unpack1('H*')
}
)
# セッション保存 - 成功!
session.saga = @step_results.to_json # JSON化可能
# 補償処理実行時:Span属性として関連付け
original_span_info = @step_results.dig(step, :span_context)
tracer.in_span("saga.compensation.#{step}") do |span|
if original_span_info
span.set_attribute('muu.compensation.original_trace_id',
original_span_info[:trace_id])
span.set_attribute('muu.compensation.original_span_id',
original_span_info[:span_id])
end
# 補償処理実行
endこれにより、以下が実現可能となりました。
- セッション復元後も使用可能
- Grafana/Tempoで検索可能
- 因果関係の追跡が可能
つまずきポイント②:Sagaパターンのトレーシング設計
可視化したいもの
Sagaパターンで可視化したいものを洗い出しました:
- フロー全体: Saga実行の成功/失敗
- 個別ステップ: どのステップで失敗/遅延が起きたか
- 補償処理: ロールバックが正しく動いたか
Spanの階層設計
どこにSpanを貼るべきかを検討し、以下のような階層構造にしました:

- 階層構造で因果関係が一目瞭然
- 各ステップの実行時間を個別に測定して改善可能
- 補償処理と元のステップの関係を追跡可能
order_event_idによる全リクエスト追跡
もう一つの課題として、HTTPリクエストが分断される問題がありました。
例えば:
- リクエスト1: 申し込み開始 → Saga → 3DS認証へリダイレクト(HTTP分断)
- リクエスト2: 3DS認証完了 → Saga再開 → 完了
通常のtrace_idでは2つのリクエストが別々のトレースになってしまいます。
これを解決するため、ビジネスキーであるorder_event_idで全体の紐付けを行いました:
# rb/app/controllers/concerns/google_workspace_card/tracing.rb
def set_session_attributes(span)
# セッション情報をSpan属性として記録
span.set_attribute('gws_card.session_id', current_gws_card_db_session.id)
span.set_attribute('gws_card.current_step', current_gws_card_db_session.current_step)
span.set_attribute('gws_card.order_event_id', current_gws_card_db_session.gws_order_event_id)
span.set_attribute('gws_card.account_id', account_id)
endこれにより、ビジネスロジックとしてDBに保存しているキーからもトレースを可能にすることが実現できました!
成果
これらの取り組みにより、Grafanaで以下のようなアラートルールを設定できるようになり、Google Workspaceカード、そしてそれを取り巻くムームードメインの既存資産の可観測性を向上させることができました。
- Saga処理失敗
- 補償処理失敗(データ不整合の可能性)
- 各ステップの失敗(GWSカード利用、ドメイン注文、決済準備、決済完了、GWS設定など)
- 外部APIの接続失敗
- エラー率上昇
- レート制限
- DB接続エラー
など、アラートルールを設定し、問題の早期検知が可能になりました。
まとめ
SpanContextを保存したい時の注意点
- SpanContextはJSON化できない – OpenTelemetryの設計思想として、永続化は想定されていない
- trace_id, span_idを文字列として保存し、Linkではなくspan属性で関連付ける
分散システムには分散トレーシングが効果的
- Sagaパターンのような分散システムにはログのみで確認するのは厳しい
- OpenTelemetryで階層構造のトレーシングを実現
- 小さな切り口から開始して、価値のある場所から攻める
「可観測性って何?」から始まった取り組みでしたが、新機能開発を切り口にすることで、段階的に可観測性を高めることができました。同じような課題を持つ方の参考になれば幸いです。
最後に、
Google Workspace カードは家電量販店等で販売中です。
クリスマスプレゼントにいかがでしょうか?!?!
ブログの著者欄
採用情報
関連記事
KEYWORD
CATEGORY
-
技術情報(547)
-
イベント(200)
-
カルチャー(54)
-
デザイン(53)
-
インターンシップ(1)
TAG
- "eVTOL"
- "Japan Drone"
- "ロボティクス"
- "空飛ぶクルマ"
- 5G
- Adam byGMO
- AdventCalender
- AGI
- AI
- AIエージェント
- AI人財
- AMD
- APT攻撃
- AWX
- BIT VALLEY
- Blade
- blockchain
- Canva
- ChatGPT
- ChatGPT Team
- Claude Team
- cloudflare
- cloudnative
- CloudStack
- CM
- CNDO
- CNDT
- CODEBLUE
- CODEGYM Academy
- ConoHa
- ConoHa、Dify
- CS
- CSS
- CTF
- DC
- design
- Designship
- Desiner
- DeveloperExper
- DeveloperExpert
- DevRel
- DevSecOpsThon
- DiceCTF
- Dify
- DNS
- Docker
- DTF
- Excel
- Expert
- Experts
- Felo
- GitLab
- GMO AIR
- GMO AIロボティクス大会議&表彰式
- GMO DESIGN AWARD
- GMO Developers Day
- GMO Developers Night
- GMO Developers ブログ
- GMO Flatt Security
- GMO GPUクラウド
- GMO Hacking Night
- GMO kitaQ
- GMO SONIC
- GMOアドパートナーズ
- GMOアドマーケティング
- GMOイエラエ
- GMOインターネット
- GMOインターネットグループ
- GMOクラウド]
- GMOグローバルサイン
- GMOコネクト
- GMOサイバーセキュリティbyイエラエ
- GMOサイバーセキュリティ大会議
- GMOサイバーセキュリティ大会議&表彰式
- GMOソリューションパートナー
- GMOデジキッズ
- GMOブランドセキュリティ
- GMOペイメントゲートウェイ
- GMOペパボ
- GMOメディア
- GMOリサーチ
- GMO大会議
- Go
- GPU
- GPUクラウド
- GTB
- Hardning
- Harvester
- HCI
- INCYBER Forum
- iOS
- IoT
- ISUCON
- JapanDrone
- Java
- JJUG
- K8s
- Kaigi on Rails
- Kids VALLEY
- KidsVALLEY
- Linux
- LLM
- MCP
- MetaMask
- MySQL
- NFT
- NVIDIA
- NW構成図
- NW設定
- Ollama
- OpenStack
- Perl
- perplexity
- PHP
- PHPcon
- PHPerKaigi
- PHPカンファレンス
- Python
- QUIC
- Rancher
- RPA
- Ruby
- Selenium
- Slack
- Slack活用
- Spectrum Tokyo Meetup
- splunk
- SRE
- sshd
- SSL
- Terraform
- TLS
- TypeScript
- UI/UX
- vibe
- VLAN
- VS Code
- Webアプリケーション
- WEBディレクター
- XSS
- アドベントカレンダー
- イベントレポート
- インターンシップ
- インハウス
- オブジェクト指向
- オンボーディング
- お名前.com
- カルチャー
- クリエイター
- クリエイティブ
- コーディング
- コンテナ
- サイバーセキュリティ
- サマーインターン
- システム研修
- スクラム
- スペシャリスト
- セキュリティ
- ソフトウェアテスト
- チームビルディング
- デザイナー
- デザイン
- テスト
- ドローン
- ネットのセキュリティもGMO
- ネットワーク
- ビジネス職
- ヒューマノイド
- ヒューマノイドロボット
- フィジカルAI
- プログラミング教育
- ブロックチェーン
- ベイズ統計学
- マイクロサービス
- マルチプレイ
- ミドルウェア
- モバイル
- ゆめみらいワーク
- リモートワーク
- レンタルサーバー
- ロボット
- 京大ミートアップ
- 人材派遣
- 出展レポート
- 動画
- 協賛レポート
- 基礎
- 多拠点開発
- 大学授業
- 宮崎オフィス
- 展示会
- 広告
- 強化学習
- 形
- 応用
- 情報伝達
- 技育プロジェクト
- 技術広報
- 技術書典
- 採用
- 採用サイトリニューアル
- 採用活動
- 新卒
- 新卒研修
- 日本科学未来館
- 映像
- 映像クリエイター
- 暗号
- 業務効率化
- 業務時間削減
- 機械学習
- 決済
- 物理暗号
- 生成AI
- 色
- 視覚暗号
- 開発生産性
- 開発生産性向上
- 階層ベイズ
- 高機能暗号
PICKUP