エッジコンピューティング本番6ヶ月、レイテンシ50msの壁を32msまで削った現実

「競合は50ms切ってる」と詰められて始めたエッジ移行。AWS Wavelength・Cloudflare Workers・KubeEdgeを実際に運用して痛感した、銀の弾丸じゃない現実と使い分けの判断基準。

エッジに手を出したきっかけは「レイテンシ50msの壁」だった

去年の春、担当していたリアルタイム映像解析サービスでどうしても越えられない壁にぶつかった。東京リージョンのLambdaで推論を回していたんだけど、エンドユーザーの体感レスポンスが平均80ms前後で張り付いてしまって、プロダクトオーナーから「競合は50ms切ってる」とプレッシャーをかけられていた。

インフラ的な最適化はやり尽くしていた。Lambda SnapStartも試したし(Lambda SnapStart導入の実録はこちら)、CloudFrontのキャッシュ戦略も見直した。それでも物理的な距離の問題は解決しない。そこでエッジコンピューティングに本腰を入れることにした。

結論から言うと、6ヶ月でレイテンシは平均32msまで落ちた。ただしその道のりは「エッジは銀の弾丸」みたいな期待を粉砕されるものだったので、正直に書き残しておく。


2026年のエッジコンピューティング、選択肢が増えすぎて逆に迷う

2026年時点で選択肢がかなり増えていて、正直どれを使えばいいか最初は全く分からなかった。主要な選択肢を整理するとこんな感じだ。

プラットフォーム主なユースケースレイテンシ目安月額コスト感特記事項
AWS Wavelengthモバイル通信キャリア網直結5〜15ms高め(専用ゾーン課金)KDDI・ソフトバンク対応済
Cloudflare Workersグローバルエッジ処理10〜30ms従量課金、安価V8 isolates、Durable Objects
Fastly Computeカスタムロジック + CDN15〜40ms中程度Rust/WASM ネイティブ
KubeEdge + EKSオンプレ/ハイブリッド1〜10ms自前インフラコストK8sエコシステム活用
AWS IoT Greengrass v3IoTデバイスエッジ処理1〜5ms(ローカル)デバイス台数課金2026年にML推論機能強化
Akamai EdgeWorkersエンタープライズCDN連携10〜25ms高め大規模グローバル展開向け

うちのプロジェクトでは最終的に Cloudflare Workers + AWS Wavelength の組み合わせで落ち着いたが、それぞれ用途が全然違う。最初にこの使い分けを理解していなかったのが、最初の2ヶ月で時間を無駄にした原因だった。


実際に動かしてみた:Cloudflare Workersで画像最適化パイプライン

最初に手をつけたのはCloudflare Workers。ユーザーがアップロードした画像をリサイズ・WebP変換するパイプラインをエッジに移植した。個人的にはここが一番「思ったより簡単だった」と感じたポイントで、wrangler dev でローカルからそのまま試せるのが地味に便利だった。

// Cloudflare Workers - 画像最適化エッジ処理
// 2026年版:Durable Objectsと組み合わせたキャッシュ管理

interface Env {
  IMAGE_CACHE: DurableObjectNamespace;
  ORIGIN_URL: string;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);
    const imageId = url.searchParams.get('id');
    const width = parseInt(url.searchParams.get('w') ?? '800');
    const format = url.searchParams.get('fmt') ?? 'webp';

    if (!imageId) {
      return new Response('Missing image id', { status: 400 });
    }

    // Durable Objectsでキャッシュチェック(エッジ間で状態共有)
    const cacheId = env.IMAGE_CACHE.idFromName(`${imageId}-${width}-${format}`);
    const cacheStub = env.IMAGE_CACHE.get(cacheId);
    
    const cached = await cacheStub.fetch(request);
    if (cached.status === 200) {
      return new Response(cached.body, {
        headers: {
          'Content-Type': `image/${format}`,
          'Cache-Control': 'public, max-age=86400',
          'X-Edge-Cache': 'HIT',
        },
      });
    }

    // オリジンから取得してエッジで変換
    const originResponse = await fetch(`${env.ORIGIN_URL}/images/${imageId}`);
    if (!originResponse.ok) {
      return new Response('Origin fetch failed', { status: 502 });
    }

    // Cloudflare Image Resizing API(2026年:アニメーションWebP対応済み)
    const resized = await fetch(request.url, {
      cf: {
        image: {
          width,
          format: format as 'webp' | 'avif' | 'jpeg',
          quality: 85,
          fit: 'cover',
        },
      },
    });

    // 非同期でDurable Objectsに保存
    ctx.waitUntil(cacheStub.fetch(new Request('cache://store', {
      method: 'POST',
      body: resized.clone().body,
    })));

    return new Response(resized.body, {
      headers: {
        'Content-Type': `image/${format}`,
        'Cache-Control': 'public, max-age=86400',
        'X-Edge-Cache': 'MISS',
        'X-Edge-PoP': request.cf?.colo ?? 'unknown',
      },
    });
  },
};

これを本番投入した結果、画像配信のレイテンシはこう変化した。

xychart-beta
  title "画像配信レイテンシ改善(p95値, ms)"
  x-axis ["移行前", "Week1", "Week2", "Week4", "Week8", "Week12"]
  y-axis "レイテンシ (ms)" 0 --> 200
  bar [185, 120, 95, 68, 45, 38]
  line [185, 120, 95, 68, 45, 38]

p95で185msから38msに落ちた。これは地味に嬉しかった。ただし、Durable Objectsのコールドスタートが思ったより発生して最初の1週間はCACHE MISSが多かった。事前にキャッシュウォームアップの仕組みを作っておくべきだったと反省している。


AWS Wavelengthは本当に必要な場面が限られる

Cloudflare Workersで満足していたんだけど、モバイルアプリのリアルタイム映像解析という要件に対してはまだ足りなかった。そこで試したのがAWS Wavelength。

WavelengthはKDDI・ソフトバンクの通信キャリア網の中にAWSのコンピューティングリソースを置くサービスで、5Gモバイルデバイスからのトラフィックが通信キャリア網を出ることなくAWSのリソースに届く。理論上のレイテンシが他と桁が違う。

実際の構成はこんな感じになった。

graph TB
  subgraph Mobile["モバイルデバイス (5G)"]
    App["映像解析アプリ"]
  end

  subgraph Carrier["通信キャリア網 (KDDI/SoftBank)"]
    subgraph WZ["Wavelength Zone - 大阪"]
      WEC2["EC2 g5.xlarge\n(GPU推論)"]    
      WNLB["NLB"]
      WEC2 --> WNLB
    end
  end

  subgraph VPC["VPC - ap-northeast-1"]
    subgraph AZ_A["AZ: ap-northeast-1a"]
      ALB["ALB"]
      ECS["ECS Fargate\n(ビジネスロジック)"]
    end
    subgraph AZ_B["AZ: ap-northeast-1c"]
      RDS[("Aurora PostgreSQL")]
      ElastiCache[("ElastiCache Redis")]
    end
    subgraph Mgmt["管理層"]
      CW["CloudWatch"]
      XRay["X-Ray"]
    end
  end

  subgraph Storage["ストレージ"]
    S3[("S3\n(解析結果保存)")]
  end

  App -->|"5G超低遅延"| WNLB
  WEC2 -->|"Carrier Gateway"| ALB
  ALB --> ECS
  ECS --> RDS
  ECS --> ElastiCache
  ECS --> S3
  WEC2 --> CW
  ECS --> XRay

Wavelength Zoneに置いたEC2はGPUインスタンス(g5.xlarge)で推論だけを担当させて、ビジネスロジックは東京リージョンのECS Fargateに分離した。この分離が正解だった。実測レイテンシは以下のとおり。

構成p50p95p99
東京リージョン Lambda52ms89ms145ms
Cloudflare Workers + Lambda38ms71ms110ms
Wavelength Zone (GPU推論)18ms31ms52ms

Wavelengthは圧倒的だった。ただしコストも圧倒的で、月額でWavelength専用コストが東京リージョンの約2.3倍かかる。プロダクトのビジネス要件と照らし合わせて「本当に必要か」を慎重に判断すべきだと思う。うちは映像解析という特性上、レイテンシに直接的なビジネス価値があったので正当化できたけど、普通のWebアプリなら過剰投資になる可能性が高い。正直、コストを見た瞬間は「これ、マネジメントにどう説明しよう」と頭が痛くなった。


KubeEdgeで工場IoTを試した話:クラウドとの役割分担が全て

Wavelengthの検証と並行して、別のプロジェクトでKubeEdge + EKSを使ったIoT系の仕事も担当した。これはまた性格が全然違う話で、工場の製造ラインに設置したカメラから映像を取り込んで、ローカルで外観検査をするシステムだった。

こっちはネットワーク遅延よりも「工場のネットワークが不安定でも止まらないこと」の方が重要だった。オフライン耐性の設計が肝になる。

# KubeEdge EdgeCore設定(2026年版 v1.18対応)
apiVersion: edgecore.config.kubeedge.io/v1alpha2
kind: EdgeCore
modules:
  edged:
    clusterDNS: "169.254.20.10"
    clusterDomain: "cluster.local"
    # エッジノードのリソース予約
    kubeReserved:
      cpu: "200m"
      memory: "256Mi"
    evictionHard:
      memory.available: "100Mi"
  eventbus:
    mqttMode: 2  # external MQTT broker使用
    mqttServerExternal: "tcp://localhost:1883"
  edgehub:
    heartbeat: 15
    # 切断時のメッセージキュー設定
    messageQueueCapacity: 100
    # 2026年追加: 自動再接続の改善
    reconnectInterval: 5
    tlsCaFile: /etc/kubeedge/ca/rootCA.crt

KubeEdgeで一番ハマったのは、エッジノードがクラウド(EKS)と接続を失ったときの挙動だった。最初の設計ではクラウドからのConfigMap更新がエッジに即座に反映される前提だったんだけど、ネットワーク断が10分以上続くと推論モデルのパラメータ更新が止まる問題が発生した。これに気づいたのが本番稼働後だったのが痛かった。

解決策として、エッジ側にモデルのローカルキャッシュを持たせて、クラウドと接続できているときだけ更新を受け取る「Eventually Consistent」な設計に切り替えた。

flowchart LR
  subgraph Cloud["AWS Cloud"]
    EKS["EKS Control Plane"]
    S3M[("S3: モデルストレージ")]
    KEB["KubeEdge CloudCore"]
  end

  subgraph Factory["工場ネットワーク"]
    subgraph Edge1["エッジノード 1"]
      ECore1["EdgeCore"]
      ModelCache1[("ローカルモデルキャッシュ")]
      InferEngine1["推論エンジン"]
    end
    subgraph Edge2["エッジノード 2"]
      ECore2["EdgeCore"]
      ModelCache2[("ローカルモデルキャッシュ")]
      InferEngine2["推論エンジン"]
    end
    LocalMQ["MQTT Broker\n(Mosquitto)"]
  end

  EKS --> KEB
  S3M -->|"モデル更新通知"| KEB
  KEB <-->|"WebSocket (断続的接続)"| ECore1
  KEB <-->|"WebSocket (断続的接続)"| ECore2
  ECore1 --> ModelCache1
  ModelCache1 --> InferEngine1
  ECore2 --> ModelCache2
  ModelCache2 --> InferEngine2
  InferEngine1 <--> LocalMQ
  InferEngine2 <--> LocalMQ

「クラウドと繋がっていなくても動く」設計を最初から考慮していなかったのが一番の反省点だった。エッジはクラウドの延長線上にあると思いがちだけど、「本質的にオフラインファーストで設計してクラウドはアップストリーム」という考え方の転換が必要だった。

インシデント対応の観点でも、エッジ障害はクラウド障害と性質が全然違う。インシデント対応のベストプラクティスで書かれているフレームワークをエッジ運用に適用しようとしたんだけど、物理デバイスの介在や現地エンジニアとの連携が必要になる場面が多くて、そのままでは使えなかった。エッジ特有のRunbookを別途整備するハメになった。


6ヶ月運用してわかった、エッジとクラウドの正しい役割分担

いろいろ試してみて、「どんな処理をエッジに置くべきか」の判断基準がようやく固まってきた。一言でまとめると「データが生まれる場所の近く=エッジ、データが集まる場所=クラウド」というメンタルモデルが一番しっくりくる。

エッジに置くべき処理

  • レイテンシに直接的なビジネス価値があるもの(ゲーム・AR・リアルタイム映像)
  • データをオリジンに送る前に間引く必要があるもの(IoTセンサーデータの前処理)
  • プライバシー規制でデータをデバイス外に出せないもの(医療・工場の機密情報)
  • ネットワーク帯域を節約したいもの(4Kストリーミングのトランスコード)

クラウドに残すべき処理

実際にコスト効率の差を数値で見ると判断がしやすくなる。レイテンシ要件が高くてデータ量が多いユースケースほどエッジが有利で、逆に整合性やスケールが重要なユースケースはクラウドの方が安い。当たり前といえば当たり前なんだけど、やっぱり実測値で見ると腹落ち感が違う。

xychart-beta
  title "用途別:エッジvsクラウド処理のコスト効率比較(クラウド=100として)"
  x-axis ["映像配信", "IoT前処理", "トランザクション", "バッチ分析", "ML学習"]
  y-axis "相対コスト" 0 --> 150
  bar [45, 38, 110, 130, 125]

セキュリティは正直まだ検証中

エッジのセキュリティが一番頭を悩ませている。物理アクセスができてしまう環境でのデバイス認証、Workersのサプライチェーンリスク(npm依存の問題はコンテナセキュリティの記事と共通する課題が多い)、mTLSの管理……と、クラウド単体のときには意識しなくてよかった問題がいっぺんに押し寄せてくる感じだ。

現状は証明書ローテーションをAWS IoT Device Defenderに任せているが、KubeEdgeとの統合部分が若干手作業で、ここは正直まだ改善余地がある。皆さんはエッジのセキュリティどうやって運用してます?もしいい知見があれば教えてほしい。


まとめ

6ヶ月エッジを本番で動かして気づいたことを整理すると、以下の5点に集約される。

  1. 「なんとなくエッジが最新だから」という理由では使わない方がいい。Wavelengthは確かに速いけど、コストが2倍以上になる。明確なレイテンシ要件(50ms以下が必要、など)がある場合だけ正当化できる

  2. Cloudflare Workersは最初の一歩として最適。ローカル開発環境(wrangler dev)が整っていて、JavaScriptエンジニアならすぐ書ける。段階的にエッジ化するには使いやすいプラットフォームだった

  3. エッジはオフラインファーストで設計する。クラウドとの接続が安定していることを前提にすると、必ず後で設計を作り直すことになる

  4. モニタリング設計はクラウド以上に重要。エッジノードが何百台になると、どこで何が起きているか把握するだけで大変になる。最初からCloudWatchとの統合を設計に組み込むべき

  5. セキュリティは現時点でベストプラクティスが固まっていない部分も多い。OWASP IoT Top 10あたりを参照しながら地道にやるしかないのが現実で、ここは業界全体がまだ模索中だと思う

次のアクション:まずCloudflare Workersの無料プランで小さなユースケース(認証トークン検証やA/Bテストのルーティングなど)から試してみるのがおすすめ。いきなりWavelengthに手を出すのは、よほどの要件がない限り避けた方が無難だと思う。

エッジ、面白いは面白いんだけどまだ「どこでも使える」技術ではなく「特定の問題を解くための道具」という段階だと感じている。クラウドネイティブが成熟したような運用の洗練さはまだ途上なので、最初はスモールスタートで経験を積むのが現実的だと思う。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事