はじめに
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等に出力する構成にして、こちらの上限を回避したアーキテクチャを構築しましょう。