GameWith Developer Blog

GameWith のエンジニア、デザイナーが技術について日々発信していきます。

CDN移行の実録:AkamaiからCloudflareへ切り替えた話 #GameWith #TechWith

こんにちは。サービス開発部の神崎です。
この記事は GameWith アドベントカレンダー2025 18日目の記事です。

GameWithでは2025年の11月より静的コンテンツの配信に利用しているCDNをAkamaiからCloudflareへ切り替えました。 本記事では移行にあたって直面した技術的な課題とその解決策を中心にご紹介します。

移行の主な理由はコスト削減ですが詳細は本題から外れるため割愛します。
ここでは実際の移行作業で考慮が必要だったポイントと、その乗り越え方に焦点を当てます。

移行してみての率直な所感

Akamai vs Cloudflare: CDN機能の比較

正直なところ、CDNサービスとしての機能面ではAkamaiに軍配が上がるかなと個人的には感じています。

Akamaiの優れている点:

  • 安全なデプロイフロー: ステージング環境での検証後に本番反映する仕組みが整っている
  • 運用機能の充実: 設定変更履歴の管理やロールバック機能が標準で提供されている
  • 設定の柔軟性: 細かなキャッシュ制御やルーティング設定が可能
  • 分析ツールの充実: トラフィック分析、パフォーマンス分析など多様なレポート機能

Cloudflareの強みは総合力

一方でCloudflareはCDN単体では劣るものの、エコシステム全体での価値が高いと感じています。

Cloudflareの特徴:

  • WAF/DDoS対策: Cloudflareの本来の強み。セキュリティ機能は非常に充実している
  • Workers、Pagesなどのエッジコンピューティング: 低コストで高性能なサーバーレス実行環境
  • 統合された管理画面: CDN、DNS、セキュリティを一元管理できる利便性
  • 今後の発展性: CDN機能も継続的にアップデートされており、将来性に期待

Cloudflareは元々WAF/DDoS対策を主軸とした企業でありセキュリティ面では非常に強力です。
ただし今回のユースケースは静的コンテンツ配信が目的だったため、WAFなどのセキュリティ機能は特に必要としていませんでした。

CDN機能は後から拡充されてきた分野で完成度には差がありますが、コスト削減という目的を考えれば妥当な選択だったと評価しています。

課題1: バージョン管理機能の制約

問題点

Cloudflareにもバージョン管理機能は存在します。
ただし、このブログの執筆時点ではかなりの機能に制限があります。

developers.cloudflare.com

Limitations

Version Management does not currently support or have limited support for the following products or features:

  • API Shield

  • Authenticated Origin Pull

  • Cache

  • Cache Rules when used with Cloudflare Images

  • Workers Cache API

  • China Network

  • Cloudflare API

  • Domain-scoped Roles

  • Image Transformations

  • Network Error Logging

  • Page Shield

  • Rules

  • Security Insights

  • Terraform

  • WAF Attack Score

  • Waiting Room

  • Wrangler

特にGameWithではImage Transformationsを使って画像コンテンツをリアルタイムで次世代フォーマットに変換する処理を多数の画像で適用しています。
しかしバージョン管理はImage Transformationsに対応していないため、この機能を利用しているゾーンではバージョン管理を活用することができませんでした。

解決策

現在は以下の運用体制で対応しています:

  • Terraformによる設定のコード管理で変更履歴を担保
  • Git上でのレビュープロセスを導入
  • 設定変更は基本的に手動で実施
  • 非対応機能以外はバージョン管理機能で利用できる開発環境を動作確認に利用

とはいえ Image Transformations を利用するルールを動作確認なしで本番環境に適用するのは危険です。 そのため以下の様な形で特定の条件を満たすアクセスのときだけ適用するテストを事前に用意しています。

  • 受信リクエストのフィルタのみ特定のIPかつ特定のcookieが入っている時の条件を追加
  • それ以外はテスト対象のルールと同じ

フィルタの例:

(
  any(http.request.cookies["hogehoge"][*] == "fugafuga")
) and (
  ip.src eq X.X.X.X
) and (
:
:

課題2: モニタリング機能の不足

問題点

CDNの運用においてオフロード率の監視は非常に重要です。
オフロード率が低下するとオリジンサーバーへの負荷が増大し、パフォーマンス低下やコスト増加につながります。

しかし、Cloudflareの管理コンソールではオフロード率を継続的に監視する機能がありません。
瞬間的な数値は確認できても時系列でのトレンド分析や異常検知には不向きでした。

解決策: GraphQL Analytics API + BigQuery連携

GraphQL Analytics APIとBigQueryを連携させることで、オフロード率の長期保存と可視化を実現しました。

オフロード率の定義

今回は以下の2つの指標を計測しています:

  • リクエスト数オフロード率: (キャッシュヒット数 / 全リクエスト数) × 100
  • 転送量オフロード率: (キャッシュヒットの転送量 / 全転送量) × 100

GraphQLクエリ

CloudflareのGraphQL Analytics APIを使用して、全体のリクエストとキャッシュヒットのリクエストを同時に取得します。

query GetOffloadRate($zoneTag: string, $filterTotal: ZoneHttpRequestsAdaptiveGroupsFilter_InputObject, $filterHit: ZoneHttpRequestsAdaptiveGroupsFilter_InputObject) {
  viewer {
    zones(filter: {zoneTag: $zoneTag}) {
      total: httpRequestsAdaptiveGroups(filter: $filterTotal, limit: 24, orderBy: [datetimeHour_ASC]) {
        count
        avg { sampleInterval }
        sum { edgeResponseBytes }
        dimensions { datetimeHour }
      }
      hit: httpRequestsAdaptiveGroups(filter: $filterHit, limit: 24, orderBy: [datetimeHour_ASC]) {
        count
        avg { sampleInterval }
        sum { edgeResponseBytes }
        dimensions { datetimeHour }
      }
    }
  }
}

変数は以下のように設定します:

{
  "zoneTag": "your_zone_tag",
  "filterTotal": {
    "AND": [
      {"datetime_geq": "2024-01-01T00:00:00Z", "datetime_leq": "2024-01-02T00:00:00Z"},
      {"requestSource": "eyeball"},
      {"clientRequestHTTPHost": "img.gamewith.jp"}
    ]
  },
  "filterHit": {
    "AND": [
      {"datetime_geq": "2024-01-01T00:00:00Z", "datetime_leq": "2024-01-02T00:00:00Z"},
      {"requestSource": "eyeball"},
      {"clientRequestHTTPHost": "img.gamewith.jp"},
      {"cacheStatus": "hit"}
    ]
  }
}

ポイントはfilterHitcacheStatus: "hit"を追加することで、キャッシュヒットのみを抽出している点です。

データパイプライン

  1. 日次バッチ処理: 毎日GraphQL APIからデータを取得
  2. BigQueryへ投入: 時間帯ごとのメトリクスを保存
  3. Looker Studioで可視化: トレンドグラフやアラート設定

このようなメトリクスは数値を見るよりもグラフ化した方が傾向を把握しやすいため、Looker Studioでダッシュボード化しています。

なお、単発の数値確認であれば最近ではGraphQL Analytics APIのMCPを使ってClaude等のAIツールで直接問い合わせる方法もあります。
ただし、継続的な監視や長期トレンドの分析には今回のようなBigQuery + BIツールの組み合わせが最適です。

課題3: CDN性能の検証

要件

移行にあたり「ユーザー体験が著しく悪化しないこと」が必須要件でした。

CDNベンダーが公表する理論値やベンチマーク結果も参考になりますが、ネットワーク経路やキャッシュヒット率など実環境特有の要因が大きく影響します。
そのため今回の検証では実際のユーザー環境での性能測定が最も確実です。

解決策: RUM(Real User Monitoring)の活用

当社では既にNew Relicをモニタリングツールとして導入していたため、New Relic BrowserのRUM機能を活用しました。

RUMを選んだ理由:

  • 合成監視(Synthetic Monitoring)では実際のユーザー環境を完全に再現できない
  • デバイス、ネットワーク、地域など多様な条件での実測値が取得可能
  • 既存のモニタリング基盤を活用でき、導入コストが低い

検証の条件としては以下:

  • 本番環境で実ユーザーのデータを観測
  • 対象期間ではCloudflareを25%のウェイトで配信(DNSの加重ルーティングを利用)
  • 同期間で両CDNの静的コンテンツのDurationを計測

設定方法

基本的には設定を入れるだけで必要なデータは大体とれます。

docs.newrelic.com

今回特別な対応を入れた部分としてはどのCDNのコンテンツなのかを特定する部分だけです。

必要な手順としては下記の2つです。

1: 両方のCDNでCDNの識別子を返却するエンドポイントを作成する

今回は両CDNともにオリジンのパスを変更することで実現しています。

AkamaiだとConstruct Responseというビヘイビアを使えばもっと簡単に実現できます。 ですがCloudflareだとCDNの機能だけでは実現できないため今回は同じ条件に合わせることにしました。

2: New RelicのRUM側にカスタム属性を追加する

https://docs.newrelic.com/jp/docs/browser/new-relic-browser/browser-apis/setcustomattribute/

1で設定したエンドポイントを利用します。以下は実際のコードです。

<script type="text/javascript">
  (async () => {
      // New Relicエージェントが初期化されるのを少し待つための safeguard
      await new Promise((resolve) => setTimeout(resolve, 500));
      let cdnName = "Unknown"; // デフォルト値
      try {
          const response = await fetch(
              "https://img.gamewith.jp/XXXXXXXXX",
              {
                  cache: "no-store",
              }
          );
          const data = await response.json();
          if (data && data.cdn) {
              cdnName = data.cdn;
          }
      } catch (e) {
          console.error("CDN identification failed:", e);
      }
      if (window.newrelic) {
          window.newrelic.setCustomAttribute("cdnProvider", cdnName);
      }
  })();
</script>

検証結果

RUMによる実測データを基に移行前後のパフォーマンスを比較しました。 測定は夜のピークタイムに実施し、最もトラフィックが集中する時間帯での性能を検証しました。

※以下のグラフはNew RelicのRUMでサンプリングしたデータです。

接続性能の比較

TCP HandshakeとDNS Lookupの応答時間(p99)を測定した結果CloudflareとAkamaiともに非常に高速で有意な差は見られませんでした。
どちらもグローバルに分散されたエッジネットワークにより、ユーザーに近い位置からコンテンツが配信されています。

ダウンロード時間の比較

各パーセンタイル(p75、p95、p99)でダウンロード時間を測定した結果CloudflareとAkamaiで大きな性能差は見られませんでした。
特に重要なp99(最も遅い1%のユーザー)でも許容範囲内の性能を維持しており移行による悪影響がないことを確認できました。

この並行運用による検証を通じて、ユーザー体験を損なうことなく移行が可能と判断しました。

まとめ

様々な課題がありましたが、大きな障害なく移行を完了することができました。

移行直後のヒヤリハット

余談ですが移行完了直後にCloudflare側で大規模障害が発生しました。

当時は複数CDNによる冗長化が整っておらずリアルタイム画像変換などCloudflareに依存した仕組みも多かったため、影響範囲が大きくなってしまいました。

既存コンテンツの量が膨大で改善のコストパフォーマンスが合わないという事情はありますが、インフラの単一障害点を減らす重要性を改めて認識する機会となりました。

さいごに

CloudflareはCDN以外にも魅力的なサービスが多数あり当社でも既に活用を始めています:

  • Workers: エッジでのアプリケーション実行。軽量な処理やAPIゲートウェイとして活用
  • Turnstile: CAPTCHA代替のボット対策。ユーザー体験を損なわずにセキュリティを確保

特にWorkersは低レイテンシで高いコストパフォーマンスを実現できており、エッジコンピューティングの可能性を実感しています。

CDN機能もルールのTrace機能など使いやすい機能があり、継続的なアップデートも期待できます。
エコシステム全体としての魅力は非常に高く、今回の移行で得られた知見を活かしこれからも使い倒していきたいと思います。

GameWithではエンジニアを絶賛募集中です。ご興味ありましたら是非カジュアル面談をお申し込みください!

github.com