🍣

重たい処理はQueueへ!Azure FunctionsでAPIを軽量化する方法

に公開

はじめに

REST APIで受け付けた処理が重くてタイムアウトしがち...
そんな悩みを解決するために、Azure FunctionsのQueueTriggerを活用して「APIとバッチ処理を疎結合化」し、非同期にスケールする構成を組んだときの設計と実装を紹介します。


なぜQueueTrigger?

  • REST APIに時間のかかる処理を入れると、ユーザー体験が悪くなりやすい
  • 処理の失敗・リトライ管理も大変
  • Queueをはさむことで、APIは即レス → 重処理は非同期という分離が可能
  • Functions + QueueTrigger なら Azure標準でスケーラブルなバッチ処理を構築できる

システム構成

  • API層は即レス(200 OK or 202 Accepted)
  • 重たい処理(例:PDF生成、DB集計など)はQueueTrigger関数で非同期に処理
  • Queueがバッファになってスパイクにも耐えやすい

実装概要

Queueに送信するメッセージ形式(例)

{
  "userId": "abc123",
  "operation": "generateReport",
  "parameters": {
    "year": 2024,
    "type": "summary"
  }
}

HTTP Trigger関数(API)

[FunctionName("RequestHandler")]
public async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
    [Queue("report-requests", Connection = "AzureWebJobsStorage")] IAsyncCollector<string> queueCollector)
{
    var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    await queueCollector.AddAsync(requestBody);
    return new AcceptedResult(); // 202 Accepted
}

QueueTrigger関数(バッチ処理)

[FunctionName("ReportGenerator")]
public async Task Run(
    [QueueTrigger("report-requests", Connection = "AzureWebJobsStorage")] string message,
    ILogger log)
{
    var request = JsonConvert.DeserializeObject<ReportRequest>(message);
    log.LogInformation($"Processing request for {request.UserId}");

    // 実処理:DBからデータ取得 → PDF生成 → ストレージ保存など
}

実装時のTips

1. Queueのリトライ戦略とPoison Queue

  • 処理中に例外を出すと最大5回リトライ(既定)

  • 失敗が続くと自動的に<queue-name>-poisonに入る

  • Poison Queueを監視して、通知や補正処理を導入すべし

2. メッセージサイズの制限(64KB)

  • Azure Storage Queueは 64KB制限あり

  • 超える場合はBlobに実体を保存 → メッセージにBlob URLを載せる方式に

3. 並列実行数の制御

  • host.json の batchSize, newBatchThreshold, maxConcurrentCalls で調整可能

  • ストレージやDBへの負荷に応じて適切に設定

まとめ

Functions + QueueTrigger を活用することで…

  • REST APIの応答速度を高速化(非同期化)

  • 重処理のスケーラビリティと信頼性を向上

  • アーキテクチャ上も疎結合で保守しやすい構成に

単なる小さな関数ではなく、Azure Functionsは非同期バッチアーキテクチャの強力な選択肢になり得ることを実感しました。

参考リンク

Discussion