#はじめに

「サーバーをユーザーの近くに置けたら」——この願いを実現するのがエッジコンピューティングです。

エッジコンピューティングは、ユーザーに近い場所(エッジ)でコードを実行する技術です。従来のサーバーレスよりも低いレイテンシーでレスポンスを返すことができます。

この記事を読むと、以下のことができるようになります:

  • エッジコンピューティングの利点を理解できる
  • 主要なエッジプラットフォームを把握できる
  • 適切なユースケースを判断できる

#エッジコンピューティングとは

エッジコンピューティングとは、ユーザーに物理的に近いサーバー(エッジサーバー)でコードを実行する技術です。

従来:
[ユーザー(東京)] ─────────────────> [サーバー(バージニア)]
                 往復 200ms

エッジ:
[ユーザー(東京)] ──> [エッジ(東京)]
                 往復 20ms

CDNのエッジロケーションでJavaScriptを実行できると考えるとわかりやすいです。

#従来のサーバーレスとの違い

#従来のサーバーレス(AWS Lambda等)

[ユーザー] → [CDN] → [API Gateway] → [Lambda] → [DB]

                    リージョンは限定的
  • 特定のリージョンで実行
  • コールドスタートが発生
  • 実行時間の制限が緩い

#エッジコンピューティング

[ユーザー] → [エッジ] → 必要に応じて [オリジン]

        世界中に分散
  • ユーザーに最も近いエッジで実行
  • コールドスタートがほぼない
  • 実行時間の制限が厳しい

#比較表

項目従来のサーバーレスエッジ
レイテンシー数十〜数百ms数ms〜数十ms
コールドスタート数百ms〜数秒ほぼなし
実行時間上限数分〜15分数十ms〜数秒
メモリ最大10GB最大128MB程度
ランタイム多様JavaScript/WASM中心
データアクセス自由制限あり

#Cloudflare Workers

Cloudflareが提供するエッジコンピューティングプラットフォームです。

#基本的なWorker

// src/index.js
export default {
  async fetch(request, env, ctx) {
    return new Response('Hello from the edge!', {
      headers: { 'Content-Type': 'text/plain' },
    });
  },
};

#リクエストの処理

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);

    // パスによる振り分け
    if (url.pathname === '/api/hello') {
      return new Response(JSON.stringify({ message: 'Hello!' }), {
        headers: { 'Content-Type': 'application/json' },
      });
    }

    // 地理情報の取得
    const country = request.cf?.country || 'Unknown';
    return new Response(`You are accessing from: ${country}`);
  },
};

#KV(Key-Value ストレージ)

エッジで利用できる分散KVストアです。

export default {
  async fetch(request, env, ctx) {
    // 読み取り
    const value = await env.MY_KV.get('key');

    // 書き込み
    await env.MY_KV.put('key', 'value');

    // 削除
    await env.MY_KV.delete('key');

    return new Response(value);
  },
};

#D1(SQLiteデータベース)

エッジで利用できるSQLiteベースのデータベースです。

export default {
  async fetch(request, env, ctx) {
    const { results } = await env.DB.prepare(
      'SELECT * FROM users WHERE id = ?'
    ).bind(1).all();

    return new Response(JSON.stringify(results), {
      headers: { 'Content-Type': 'application/json' },
    });
  },
};

#wrangler.toml

name = "my-worker"
main = "src/index.js"
compatibility_date = "2024-01-01"

[[kv_namespaces]]
binding = "MY_KV"
id = "xxxxx"

[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "xxxxx"

#Vercel Edge Functions

Vercelが提供するエッジランタイムです。

#基本的なEdge Function

// app/api/hello/route.ts (App Router)
export const runtime = 'edge';

export async function GET(request: Request) {
  return new Response('Hello from the edge!');
}

#Middleware

すべてのリクエストの前に実行されるエッジ関数です。

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // 地理情報によるリダイレクト
  const country = request.geo?.country || 'US';

  if (country === 'JP') {
    return NextResponse.redirect(new URL('/ja', request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: '/:path*',
};

#Edge Config

エッジで読み取れる設定ストアです。

import { get } from '@vercel/edge-config';

export const runtime = 'edge';

export async function GET() {
  const featureFlag = await get('newFeature');

  return new Response(JSON.stringify({ enabled: featureFlag }));
}

#その他のエッジプラットフォーム

#Deno Deploy

Denoランタイムベースのエッジプラットフォームです。

Deno.serve((req: Request) => {
  return new Response('Hello from Deno Deploy!');
});

#Fastly Compute

Fastlyが提供するエッジコンピューティングです。

addEventListener('fetch', (event) => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  return new Response('Hello from Fastly!');
}

#AWS CloudFront Functions / Lambda@Edge

項目CloudFront FunctionsLambda@Edge
実行場所全エッジロケーションリージョナルエッジ
実行時間最大1ms最大5秒(Viewer)/ 30秒(Origin)
メモリ2MB最大10GB
言語JavaScriptNode.js, Python

#ユースケース

#1. A/Bテスト

export default {
  async fetch(request, env) {
    const bucket = Math.random() < 0.5 ? 'A' : 'B';

    const response = await fetch(request);
    const html = await response.text();

    const modifiedHtml = html.replace(
      '{{VARIANT}}',
      bucket === 'A' ? 'オリジナル' : '新デザイン'
    );

    return new Response(modifiedHtml, {
      headers: { 'Content-Type': 'text/html' },
    });
  },
};

#2. 地理情報によるリダイレクト

export default {
  async fetch(request) {
    const country = request.cf?.country;

    const redirectMap = {
      JP: 'https://example.com/ja',
      US: 'https://example.com/en',
      DE: 'https://example.com/de',
    };

    if (redirectMap[country]) {
      return Response.redirect(redirectMap[country], 302);
    }

    return fetch(request);
  },
};

#3. 認証・認可

export default {
  async fetch(request, env) {
    const token = request.headers.get('Authorization')?.replace('Bearer ', '');

    if (!token) {
      return new Response('Unauthorized', { status: 401 });
    }

    // JWTの検証
    const isValid = await verifyToken(token, env.JWT_SECRET);

    if (!isValid) {
      return new Response('Forbidden', { status: 403 });
    }

    return fetch(request);
  },
};

#4. レスポンスの変換

export default {
  async fetch(request) {
    const response = await fetch(request);
    const html = await response.text();

    // HTMLを変換
    const modifiedHtml = html
      .replace(/http:\/\//g, 'https://')
      .replace(/<script/g, '<script defer');

    return new Response(modifiedHtml, {
      headers: response.headers,
    });
  },
};

#5. API のキャッシュ

export default {
  async fetch(request, env, ctx) {
    const cacheKey = new Request(request.url, request);
    const cache = caches.default;

    // キャッシュを確認
    let response = await cache.match(cacheKey);

    if (!response) {
      // オリジンからフェッチ
      response = await fetch(request);
      response = new Response(response.body, response);
      response.headers.set('Cache-Control', 'max-age=60');

      // キャッシュに保存
      ctx.waitUntil(cache.put(cacheKey, response.clone()));
    }

    return response;
  },
};

#制限事項

#実行時間

エッジ関数は高速な処理が求められます。

プラットフォーム制限
Cloudflare Workers10ms(無料)/ 50ms(有料)CPU時間
Vercel Edge25秒(壁時計時間)
Deno Deploy50ms CPU時間

#メモリ

プラットフォーム制限
Cloudflare Workers128MB
Vercel Edge128MB

#Node.js API

エッジランタイムでは、Node.jsの一部APIが利用できません。

使用不可:

  • fs(ファイルシステム)
  • child_process
  • net
  • ネイティブモジュール

使用可能:

  • fetch
  • crypto(Web Crypto API)
  • TextEncoder/TextDecoder
  • URL
  • HeadersRequestResponse

#デプロイとテスト

#Cloudflare Workers

# インストール
npm install -g wrangler

# ログイン
wrangler login

# ローカル開発
wrangler dev

# デプロイ
wrangler deploy

#Vercel

# ローカル開発
vercel dev

# デプロイ
vercel deploy

#まとめ

  • エッジコンピューティングはユーザーに近い場所でコードを実行
  • 低レイテンシー、コールドスタートがほぼない
  • 主要プラットフォーム: Cloudflare Workers、Vercel Edge Functions
  • 適したユースケース: 認証、リダイレクト、A/Bテスト、キャッシュ
  • 制限: 実行時間、メモリ、Node.js API
  • 従来のサーバーレスと適材適所で使い分け

#次のステップ

エッジコンピューティングを理解したところで、最後にWebSocketとServer-Sent Eventsについて学びましょう。リアルタイム通信を実現する技術を紹介します。

#参考リンク