3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Google Cloud Workflows 変数メモリ上限に関する検証

Posted at

はじめに

WorkflowsからCloudRunを呼び出して、処理結果のログをJSONで受け取ろうとしたところ、ログが渡せない事象に遭遇しました。

公式ドキュメントにある「割り当てと上限」には「レスポンス サイズ:2MB」といった記載のほかに「データ サイズ: 512KB」といったような記載があることに気が付きました。

こちらの上限により、ログの受け渡しができなかったのかと思い、検証をしてみました。

公式ドキュメント:Workflows 割り当てと上限

上限項目 説明
レスポンス サイズ 2 MB HTTP レスポンスの最大サイズ(変数に保存する場合、変数のメモリ上限が適用されます)
データ サイズ 512 KB 変数、引数、イベントの累積最大サイズ

結論

公式ドキュメントには「Response size: 2MB」と記載されていますが、CloudRun等の出力結果をWorkflowsに渡す場合、Workflowsの変数にレスポンスを保存するという動きをするため、受け渡しできるサイズが512KBに制限されてしまう。

検証内容

  • Workflow実行時に引数でサイズ(400KB/512KB/600KB)それぞれを指定し、CloudRunでそのサイズのJSONを生成して返却。
    サイズの大きさによってWorkflowsで受け取ることが可能かの(Workflowsの変数に保存してログ出力できるか)成功/失敗を比較。

検証に利用したコード

CloudRun

Workflows変数メモリ上限検証用API
引数で指定されたサイズ(KB)のJSONレスポンスを生成して返却する関数

コードの詳細
import json
import os

from flask import Flask, jsonify, request


app = Flask(__name__)

# 1KB = 1024バイト
BYTES_PER_KB = 1024


@app.route("/health", methods=["GET"])
def health():
    """
    ヘルスチェック用エンドポイント
    
    CloudRunのヘルスチェックで使用する。
    """
    return jsonify({"status": "healthy"})


@app.route("/generate", methods=["GET"])
def generate():
    """
    指定サイズのJSONを生成して返却する。
    
    Workflowsから呼び出され、指定されたサイズのJSONレスポンスを返す。
    これにより、Workflowsの変数メモリ上限を検証できる。
    
    Query Parameters:
        size (int): 生成するJSONのサイズ(KB)。必須。1〜3000の範囲。
    
    Returns:
        JSON: 以下のフィールドを含むレスポンス
            - requested_size_kb: リクエストされたサイズ(KB)
            - actual_size_bytes: 実際のレスポンスサイズ(バイト)
            - padding: サイズ調整用のダミー文字列
    
    Examples:
        GET /generate?size=400  -> 400KBのJSONを返却
        GET /generate?size=512  -> 512KBのJSONを返却(Workflows上限)
    """
    # クエリパラメータからサイズを取得
    size_kb = request.args.get("size", type=int)

    # バリデーション: sizeパラメータは必須
    if size_kb is None:
        return jsonify({"error": "size parameter is required"}), 400

    # バリデーション: サイズは正の値
    if size_kb <= 0:
        return jsonify({"error": "size must be positive"}), 400

    # バリデーション: サイズ上限(メモリ保護のため)
    if size_kb > 3000:
        return jsonify({"error": "size must be 3000KB or less"}), 400

    # 目標バイト数を計算
    target_bytes = size_kb * BYTES_PER_KB

    # ベースとなるレスポンス構造を作成
    
    # Step1: paddingが空の状態でサイズを計算
    # requested_size_kb:引数で与えられた値(確認用)
    # actual_size_bytes:最終的なJSONのサイズ(確認用、後で上書き)
    # padding:サイズ調整用のダミー文字列(後で"a"を詰める)
    base_response = {
        "requested_size_kb": size_kb,
        "actual_size_bytes": 0,
        "padding": "",
    }

    # Step2: ベースレスポンスのサイズを計算
    # 例: {"requested_size_kb": 400, "actual_size_bytes": 0, "padding": ""} → 約60バイト
    base_size = len(json.dumps(base_response))

    # Step3: paddingに必要なサイズを計算
    # 目標サイズからベースサイズ(Step2の値)を引いた分だけ"a"で埋める
    padding_size = target_bytes - base_size
    if padding_size < 0:
        padding_size = 0

    # Step4: ダミー文字列を生成
    padding = "a" * padding_size

    # Step5: 最終レスポンスを構築
    response = {
        "requested_size_kb": size_kb,
        "actual_size_bytes": target_bytes,
        "padding": padding,
    }

    # Step6: 実際のサイズを再計算して設定
    # Step3でpaddingサイズを計算したが、paddingを詰めると全体サイズが微妙にずれる可能性があるため再計算
    actual_size = len(json.dumps(response))
    response["actual_size_bytes"] = actual_size

    # Step7: Workflowsへ値を返却
    return jsonify(response)


if __name__ == "__main__":
    # CloudRunはPORT環境変数でポートを指定する
    port = int(os.environ.get("PORT", 8080))
    app.run(host="0.0.0.0", port=port)

Workflows

コードの詳細
main:
  # Workflow実行時に渡される引数を受け取る
  # 例: gcloud workflows run xxx --data='{"size": 400}'
  params: [args]

  steps:
    # Step1: 変数の初期化
    - init:
        assign:
          # 引数から検証するサイズ(KB)を取得
          - size: ${args.size}
          
          # 環境変数からCloudRunのURLを取得
          - cloudrun_url: ${sys.get_env("CLOUDRUN_URL")}
          
    # Step2: CloudRunを呼び出し
    - call_cloudrun:
        call: http.get
        args:
          # CloudRunのエンドポイントにサイズを指定してリクエスト
          url: ${cloudrun_url + "/generate?size=" + string(size)}

          # 認証のためOIDCトークンを付与
          auth:
            type: OIDC
            
        # レスポンスを変数に格納(512KB以上の場合失敗)
        result: api_response

    # Step3: 結果を返却
    - return_result:
        return:
          # リクエストしたサイズ(確認用)         
          requested_size_kb: ${size}
          
          # 受け取ったレスポンスのサイズ(確認用)
          response_body_size: ${len(json.encode_to_string(api_response.body))}
          
          # 成功したことを示す
          status: "success"

検証結果

サマリ

ケース リクエストサイズ 結果
Case 1 400KB 成功
Case 2 512KB 失敗
Case 3 600KB 失敗

Case 1: 400KB(成功)

400KB(409,600バイト)のレスポンスは正常に変数へ格納され、Workflowは成功。

# レスポンス抜粋
argument: '{"size":400}'
result: '{"requested_size_kb":400,"response_body_size":409600,"status":"success"}'
state: SUCCEEDED

Case 2: 512KB(失敗)

512KBでのレスポンスで MemoryLimitExceededError が発生し、Workflowは失敗。
公式ドキュメントには「変数、引数、イベントの累積最大サイズ」と記載されています。

つまり、変数サイズ制限(512KB)は純粋なボディのサイズだけでなく、変数名やその他のメタデータを含めた累積サイズで計算されます。
そのため、安全に受け渡しができるサイズは500KB弱と見積もっておくのが良いでしょう。

# レスポンス抜粋
argument: '{"size":512}'
error:
  context: |-
    Memory usage limit exceeded
    in step "call_cloudrun", routine "main", line: 10
  payload: '{"message":"Memory usage limit exceeded","tags":["MemoryLimitExceededError","ResourceLimitError"]}'
state: FAILED

Case 3: 600KB(失敗)

600KBのレスポンスでも同様に MemoryLimitExceededError が発生し、Workflowは失敗。

# レスポンス抜粋
argument: '{"size":600}'
error:
  context: |-
    Memory usage limit exceeded
    in step "call_cloudrun", routine "main", line: 10
  payload: '{"message":"Memory usage limit exceeded","tags":["MemoryLimitExceededError","ResourceLimitError"]}'
state: FAILED

検証と公式ドキュメントから考えるレスポンス設計の方針

Workflowsと連携するCloudRunでは、Workflowsのステップで必要となる値だけを返す設計をした方が良いです。
アプリケーションの実行ログなどの、記録しておきたいだけのデータはCloudRun側でCloud Logging等に直接出力させ、Workflowsには渡さないように設計するようにしましょう。

必要なものだけを保存する

メモリ使用量を制御して、リソース上限または、ResourceLimitError、MemoryLimitExceededError、ResultSizeLimitExceededError などを示すエラーが発生しないようにします。
変数に保存するものを選択し、必要なものだけをフィルタして保存します。サービスから返されるペイロードが大きすぎる場合は、別の関数を使用して呼び出しを行い、必要なものだけを返すようにします。

ー 公式ドキュメント:Workflowsのベストプラクティス

まとめ

WorkflowsからCloudRun等を呼び出す場合、公式ドキュメントに記載されている「レスポンスサイズ2MB」ではなく、「変数のメモリ上限512KB」が実質的な上限となることが分かりました。

これは、WorkflowsがHTTPレスポンスを利用する際に、変数へ格納する動作をすることが理由となります。

Workflowsと連携するサービスを設計する際は、次のステップで必要な値だけをレスポンスに含め、詳細ログはCloud Logging等に出力する構成にして、こちらの上限を回避したアーキテクチャを構築しましょう。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?