Cloudflare WorkersではLINE bot SDKの初期化をハンドラーの外でやらないほうがいい #linedc

Cloudflare Workersでは起動時間制限があるため、LINE Bot SDKの初期化をグローバルスコープで行わず、ハンドラー内で必要な時だけ行う「遅延初期化」が効果的です。

広告ここから
広告ここまで

目次

    最近、LINE BotをCloudflare Workersで実装していた際に、以下のようなエラーに遭遇しました:

    ✘ [ERROR] Your Worker failed validation because it exceeded startup limits.
    
      To ensure fast responses, there are constraints on Worker startup, such as how much CPU it can
      use, or how long it can take. Your Worker has hit one of these startup limits. Try reducing the
      amount of work done during startup (outside the event handler), either by removing code or
      relocating it inside the event handler.
      
      A CPU Profile of your Worker's startup phase has been written to
      .wrangler/tmp/startup-profile-TBYPlL/worker.cpuprofile - load it into the Chrome DevTools profiler
      (or directly in VSCode) to view a flamegraph.
      
      Refer to https://developers.cloudflare.com/workers/platform/limits/#worker-startup-time for more
      details

    この記事では、この問題の原因と解決方法について共有したいと思います。

    問題の詳細

    エラーの背景

    Cloudflare Workersには、高速なレスポンスを確保するために、Worker起動時(スタートアップフェーズ)のCPU使用量や実行時間に制限があります。

    • 起動時間制限:50ミリ秒
    • メモリ使用量制限:128MB

    問題のコード

    当初、以下のようなコードでLINE BotとGoogle Calendar APIのクライアントを初期化していました:

    // src/lib/line/bot-clients.ts
    export const rootLINEBotClient = new Client(getLINEBotSDKSecrets(':root:'))
    export const schoolOsakaLINEBotClient = new Client(getLINEBotSDKSecrets(':school_osaka:'))
    export const schoolAichiLINEBotClient = new Client(getLINEBotSDKSecrets(':school_aichi:'))
    
    // src/webhooks/line/osaka.ts
    const auth = new google.auth.GoogleAuth({
      credentials: {
        client_email: GOOGLE_CLIENT_EMAIL,
        private_key: GOOGLE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
      },
      scopes: ['https://www.googleapis.com/auth/calendar.readonly'],
      projectId: GOOGLE_PROJECT_ID
    });

    問題の原因

    プロファイラの分析結果から、以下の問題点が判明しました:

    1. 複数のクライアント初期化

    • 3つのLINE Botクライアントを同時に初期化
    • Google Calendar APIクライアントの初期化
    • これらがグローバルスコープで実行されていた

    1. 実行時間

    • 全体の実行時間が約214ミリ秒
    • Workersの制限(50ミリ秒)を大幅に超過

    1. メモリ使用量

    • 複数のガベージコレクション(GC)呼び出しが発生
    • メモリ使用量の急激な増加

    解決策

    1. 遅延初期化(Lazy Initialization)の導入

    クライアントの初期化をハンドラー内で必要な時のみ行うように変更しました:

    // src/lib/line/bot-clients.ts
    let schoolOsakaLINEBotClient: Client | null = null;
    
    export function getSchoolOsakaLINEBotClient(env: Env) {
      if (!schoolOsakaLINEBotClient) {
        schoolOsakaLINEBotClient = new Client({
          channelAccessToken: env.LINE_CHANNEL_ACCESS_TOKEN,
          channelSecret: env.LINE_CHANNEL_SECRET,
        });
      }
      return schoolOsakaLINEBotClient;
    }

    2. ハンドラー内での初期化

    Workerのハンドラー内で必要なクライアントのみを初期化するように変更:

    export const osakaWebhookHandler = async (c: Context) => {
      const client = getSchoolOsakaLINEBotClient(c.env);
      const auth = new google.auth.GoogleAuth({
        // ... 認証情報の設定
      });
    
      // ... 処理の続き
    };

    結果

    これらの変更により:

    1. デプロイが成功
    2. スタートアップ時間が50ミリ秒以内に収まる
    3. メモリ使用量の削減
    4. 必要な時のみリソースを初期化することでより効率的な実行が可能に

    教訓

    1. 初期化のタイミング

    • グローバルスコープでの初期化は慎重に
    • 必要な時のみリソースを初期化する

    1. Workersの制限理解

    • プラットフォームの制限を理解することの重要性
    • パフォーマンスプロファイリングの有用性

    1. 最適化の重要性

    • サーバーレス環境での効率的なリソース使用
    • コールドスタート時間の最適化

    まとめ

    Cloudflare Workersでアプリケーションを開発する際は、特にスタートアップ時の処理に注意を払う必要があります。遅延初期化やハンドラー内での初期化など、適切な設計パターンを採用することで、プラットフォームの制限内で効率的に動作するアプリケーションを構築することができます。

    関連

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark