Serverless Operations, inc

>_cd /blog/id_udhdmatvsz

title

Amazon Bedrock AgentCore Runtime V2を試してみた。V1とのコールドスタート性能を実測比較

2026年9月18日にAWSが新しいAgentCore Runtimeを発表しました。

https://aws.amazon.com/jp/blogs/machine-learning/the-new-agentcore-runtime-elastic-optimized-and-consistently-fast-starts/

従来のRuntimeをさらに高速かつコスト効率をよくするものです。

アップデートの概要

シンプルに言えば、コールドスタート時の起動速度が向上し、メモリも利用分のみが課金されるようになりました。より真のサーバレスに近づいた形でいうならば、SnapStartにより起動時間が安定したLamba関数が利用した実際のメモリ量のみに対して課金されるようになった感じです。

従来のAgentCore Runtimeは以下の課題を抱えていました。

  • メモリコストがピークに合わせて課金される:セッションは割り当てた瞬間から終了までメモリを保持し続け、途中で解放されないため、使用量が最大値(high watermark)をベースに確保されていました。たまにスパイクするだけで大半はアイドルなエージェントでも、ピーク分を常時払う羽目になっていました。

  • 起動時間のバラつき:初期化済み環境に当たれば100ミリ秒未満で起動するが、それを保証するにはコンピュートを予約し続ける必要があります。そのため多くの場合コールドスタートになり、イメージサイズや同時実行数が増えるほど遅延が拡大します。旧バージョンはイメージサイズにもよりますが最大30秒程度の遅延がありました。

今回のアップデートで以下の機能が加わり上記の課題が解決されることになりました。

1. オンデマンドなメモリ管理とコスト削減
新ランタイムは、完全にプロビジョニングされたフットプリントではなく、小さく効率的なメモリプロファイルから各セッションを開始し、ワークロードが必要とする分だけオンデマンドで追加メモリを割り当ててページインします。 数十億セッションの割り当てパターン分析に基づき、コールドになって再アクセスされる見込みが低いメモリを回収するようチューニングされており、セッション終了を待たずに解放されます。課金は実使用量の推移に追従します。

2. 一貫して高速なコールドスタート
環境を一度だけ準備してスナップショットを取得し、新しいインスタンスごとにそのスナップショットを復元する方式を採用。 スナップショットが小さく一定に保たれるため、イメージサイズや同時実行数に関係なく起動時間も一定になります。LambdaのSnapStartと似た感じです。

一方後述しますが、ウォームスタート時はV1の方が早いようです。

3. 課金モデルの変更
新ランタイムはコンテナイメージ全体をセッション中ずっとメモリに保持する分ではなく、エージェントが実際に使うメモリ(オンデマンドでロードされアイドル時に回収される分)に対して課金します。 注意点ですが、単価(レート)は高くなりますが、課金対象のGB時間が大幅に減るため、多くのエージェントでは使用量の減少幅が単価上昇を上回り、結果的に請求額は下がります。

さっそくやってみる

では早速試していきます。

1. 環境設定

まずは以下のコマンドで環境を設定します。

mkdir agentcore-v2-blog && cd agentcore-v2-blog
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install --upgrade bedrock-agentcore bedrock-agentcore-starter-toolkit boto3

2. エージェント本体の作成

ではAgent本体のコードを作成します。

cat > app.py << 'EOF'
import json
from http.server import BaseHTTPRequestHandler, HTTPServer


class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        # ヘルスチェック用エンドポイント
        if self.path == "/ping":
            self._send(200, {"status": "healthy"})
        else:
            self._send(404, {"error": "not found"})

    def do_POST(self):
        # 受け取った入力をそのまま返すだけのechoエージェント
        if self.path == "/invocations":
            length = int(self.headers.get("Content-Length", 0))
            body = self.rfile.read(length) if length else b"{}"
            try:
                payload = json.loads(body or b"{}")
            except json.JSONDecodeError:
                payload = {"_raw": body.decode("utf-8", "replace")}
            self._send(200, {"echo": payload})
        else:
            self._send(404, {"error": "not found"})

    def _send(self, code, obj):
        data = json.dumps(obj).encode("utf-8")
        self.send_response(code)
        self.send_header("Content-Type",

次にDockerfileを作成します。

cat > Dockerfile << 'EOF'
# AgentCore Runtime は linux/arm64 が既定
FROM --platform=linux/arm64 python:3.13-slim
WORKDIR /app
COPY app.py .
EXPOSE 8080
CMD ["python", "app.py"]
EOF

3. コンテナイメージを格納するECRレポジトリを作成します。

export AWS_REGION=us-west-2
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export ECR_REPO=agentcore-echo
export ECR_URI="${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO}"
echo "ACCOUNT_ID=$ACCOUNT_ID"
echo "ECR_URI=$ECR_URI"
aws ecr create-repository \
  --repository-name "$ECR_REPO" \
  --region "$AWS_REGION" \
  --query 'repository.repositoryUri' --output text

ECRにログインしてコンテナイメージをPushします。

aws ecr get-login-password --region "$AWS_REGION" \
  | docker login --username AWS --password-stdin "${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"

docker buildx build \
  --platform linux/arm64 \
  -t "${ECR_URI}:v1" \
  --push \
  .
aws ecr describe-images \
  --repository-name "$ECR_REPO" \
  --region "$AWS_REGION" \
  --query 'imageDetails[].{tag:imageTags[0],pushedAt:imagePushedAt,sizeMB:imageSizeInBytes}' \
  --output table

4. IAMロールの作成

AgentCore Runtime がコンテナを動かす際に引き受ける(assume する)実行ロールを作ります。まず信頼ポリシー(誰がこのロールを使えるか)を作成します。

cat > trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "bedrock-agentcore.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

このロールに与える権限ポリシー(ECRからのイメージ取得と CloudWatch Logs への出力)を作成します。

cat > permissions-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ECRImagePull",
      "Effect": "Allow",
      "Action": [
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer",
        "ecr:GetAuthorizationToken"
      ],
      "Resource": "*"
    },
    {
      "Sid": "CloudWatchLogs",
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:DescribeLogStreams",
        "logs:DescribeLogGroups"
      ],
      "Resource": "*"
    }
  ]
}
EOF

ロールを作成してポリシーをアタッチし、ARN を環境変数に控えます。

export ROLE_NAME=AgentCoreEchoRole
aws iam create-role \
  --role-name "$ROLE_NAME" \
  --assume-role-policy-document file://trust-policy.json \
  --query 'Role.Arn' --output text

aws iam put-role-policy \
  --role-name "$ROLE_NAME" \
  --policy-name AgentCoreEchoPolicy \
  --policy-document file://permissions-policy.json

export ROLE_ARN=$(aws iam get-role --role-name "$ROLE_NAME" --query 'Role.Arn' --output text)
echo "ROLE_ARN=$ROLE_ARN"

5. boto3 で V2 ランタイムを作成

cat > create_runtime_v2.py << 'EOF'
import os, sys, time, boto3

region = os.environ["AWS_REGION"]
account = os.environ["ACCOUNT_ID"]
ecr_uri = os.environ["ECR_URI"]
role_arn = os.environ["ROLE_ARN"]

image = f"{ecr_uri}:v1"
name = "echo_v2"

c = boto3.client("bedrock-agentcore-control", region_name=region)

print(f"Creating runtime '{name}' (platformVersion=V2) with image {image} ...")
resp = c.create_agent_runtime(
    agentRuntimeName=name,
    agentRuntimeArtifact={"containerConfiguration": {"containerUri": image}},
    networkConfiguration={"networkMode": "PUBLIC"},
    roleArn=role_arn,
    platformVersion="V2",
)
runtime_id = resp["agentRuntimeId"]
runtime_arn = resp["agentRuntimeArn"]
print("agentRuntimeId:", runtime_id)
print("agentRuntimeArn:", runtime_arn)

# READY になるまでポーリング(V2はスナップショット準備で数分かかる)
start = time.time()
while True:
    d = c.get_agent_runtime(agentRuntimeId=runtime_id)
    status = d["status"]
    elapsed = int(time.time() - start)
    print(f"[{elapsed:4d}s] status={status}")
    if status in ("READY", "CREATE_FAILED", "FAILED"):
        # 確認: 実際に V2 で作られたか(get系のみが返すフィールド)
        print("platformVersion (from get):", d.get("platformVersion"))
        if status != "READY":
            print("failureReason:", d.get("failureReason"))
            sys.exit(1)
        break
    time.sleep(15)

print("DONE. Runtime is READY.")
print("Save this ARN for invoke:", runtime_arn)
EOF
python create_runtime_v2.py

数分待つと以下の様な出力が行われます。 V2と表示されていれば成功です。

Creating runtime 'echo_v2' (platformVersion=V2) with image 917561075114.dkr.ecr.us-west-2.amazonaws.com/agentcore-echo:v1 ...
agentRuntimeId: echo_v2-oQzC14EP9a
agentRuntimeArn: arn:aws:bedrock-agentcore:us-west-2:917561075114:runtime/echo_v2-oQzC14EP9a
[   0s] status=CREATING
[  15s] status=CREATING
[  30s] status=CREATING
[  45s] status=CREATING
[  61s] status=CREATING
[  76s] status=CREATING
[  91s] status=CREATING
[ 106s] status=CREATING
[ 121s] status=CREATING
[ 137s] status=CREATING
[ 152s] status=CREATING
[ 167s] status=CREATING
[ 182s] status=CREATING
[ 197s] status=READY
platformVersion (from get): V2
DONE. Runtime is READY.
Save this ARN for invoke: arn:aws:bedrock-agentcore:us-west-2:917561075114:runtime/echo_v2-oQzC14EP9a

6. テスト実行

arnの値は上記で出力されたものに置き換えます、皆さんごとに値は異なります。

export RUNTIME_ARN_V2="arn:aws:bedrock-agentcore:us-west-2:917561075114:runtime/echo_v2-oQzC14EP9a"
echo "$RUNTIME_ARN_V2"
arn:aws:bedrock-agentcore:us-west-2:917561075114:runtime/echo_v2-oQzC14EP9a
cat > invoke_runtime.py << 'PYEOF'
import os, sys, json, uuid, boto3

region = os.environ["AWS_REGION"]
runtime_arn = os.environ["RUNTIME_ARN_V2"]

c = boto3.client("bedrock-agentcore", region_name=region)

# 新規セッションを1つ作って呼ぶ(33文字以上必要)
session_id = "blogtest-" + uuid.uuid4().hex

payload = {"prompt": "hello v2"}
print("invoking:", runtime_arn)
print("session :", session_id)

resp = c.invoke_agent_runtime(
    agentRuntimeArn=runtime_arn,
    runtimeSessionId=session_id,
    payload=json.dumps(payload).encode("utf-8"),
)

# レスポンスは StreamingBody で返る
body = resp["response"].read()
print("raw response:", body.decode("utf-8", "replace"))
PYEOF

実行を試みます。

python -m py_compile invoke_runtime.py && echo "OK: syntax valid" && python invoke_runtime.py
OK: syntax valid
invoking: arn:aws:bedrock-agentcore:us-west-2:917561075114:runtime/echo_v2-oQzC14EP9a
session : blogtest-89221c043262436884639d0c64d462f0
raw response: {"echo": {"prompt": "hello v2"}}

無事呼び出しが成功です。

7. V1 との対比

では以下でV1環境を作成します。

cat > create_runtime_v1.py << 'PYEOF'
import os, sys, time, boto3

region = os.environ["AWS_REGION"]
ecr_uri = os.environ["ECR_URI"]
role_arn = os.environ["ROLE_ARN"]

image = f"{ecr_uri}:v1"
name = "echo_v1"

c = boto3.client("bedrock-agentcore-control", region_name=region)

print(f"Creating runtime '{name}' (platformVersion=V1) with image {image} ...")
resp = c.create_agent_runtime(
    agentRuntimeName=name,
    agentRuntimeArtifact={"containerConfiguration": {"containerUri": image}},
    networkConfiguration={"networkMode": "PUBLIC"},
    roleArn=role_arn,
    platformVersion="V1",
)
runtime_id = resp["agentRuntimeId"]
runtime_arn = resp["agentRuntimeArn"]
print("agentRuntimeId:", runtime_id)
print("agentRuntimeArn:", runtime_arn)

start = time.time()
while True:
    d = c.get_agent_runtime(agentRuntimeId=runtime_id)
    status = d["status"]
    elapsed = int(time.time() - start)
    print(f"[{elapsed:4d}s] status={status}")
    if status in ("READY", "CREATE_FAILED", "FAILED"):
        print("platformVersion (from get):", d.get("platformVersion"))
        if status != "READY":
            print("failureReason:", d.get("failureReason"))
            sys.exit(1)
        break
    time.sleep(15)

print("DONE. Runtime is READY.")
print("Save this ARN for invoke:", runtime_arn)
PYEOF
python -m py_compile create_runtime_v1.py && echo "OK" && python create_runtime_v1.py
 python -m py_compile create_runtime_v1.py && echo "OK" && python create_runtime_v1.py
OK
Creating runtime 'echo_v1' (platformVersion=V1) with image 917561075114.dkr.ecr.us-west-2.amazonaws.com/agentcore-echo:v1 ...
agentRuntimeId: echo_v1-vq2fv09Ntn
agentRuntimeArn: arn:aws:bedrock-agentcore:us-west-2:917561075114:runtime/echo_v1-vq2fv09Ntn
[   0s] status=CREATING
[  15s] status=READY
platformVersion (from get): V1
DONE. Runtime is READY.
Save this ARN for invoke: arn:aws:bedrock-agentcore:us-west-2:917561075114:runtime/echo_v1-vq2fv09Ntn

V2 Runtimeは約197秒かかりましたが、V1は約15秒でできています。ブログにも「V1 は作成が速いが、V2 は作成時にスナップショットを準備するぶん数分かかる」という記載があり今回の観測内容と合致します。

ではV1の起動実測を行います。arnの内容は上記の値に置き換えて実行します。

export RUNTIME_ARN_V1="arn:aws:bedrock-agentcore:us-west-2:917561075114:runtime/echo_v1-vq2fv09Ntn"
echo "V1=$RUNTIME_ARN_V1"; echo "V2=$RUNTIME_ARN_V2"

では以下のV1起動スクリプトを作成し実行します。

cat > bench.py << 'PYEOF'
import os, sys, json, uuid, time, statistics, boto3

region = os.environ["AWS_REGION"]

if len(sys.argv) < 3:
    print("usage: python bench.py <RUNTIME_ARN> <N> [label]")
    sys.exit(1)

runtime_arn = sys.argv[1]
n = int(sys.argv[2])
label = sys.argv[3] if len(sys.argv) > 3 else runtime_arn.split("/")[-1]

c = boto3.client("bedrock-agentcore", region_name=region)
payload = json.dumps({"prompt": "cold start test"}).encode("utf-8")

lat = []
print(f"# benchmarking {label}: {n} cold invocations")
for i in range(n):
    sid = "bench-" + uuid.uuid4().hex  # 毎回新セッション = コールドスタート
    t0 = time.perf_counter()
    try:
        resp = c.invoke_agent_runtime(
            agentRuntimeArn=runtime_arn,
            runtimeSessionId=sid,
            payload=payload,
        )
        _ = resp["response"].read()
        dt = (time.perf_counter() - t0) * 1000.0
        lat.append(dt)
        print(f"  [{i+1:3d}/{n}] {dt:8.1f} ms")
    except Exception as e:
        print(f"  [{i+1:3d}/{n}] ERROR: {e}")

if not lat:
    print("no successful invocations")
    sys.exit(1)

lat_sorted = sorted(lat)
def pct(p):
    k = max(0, min(len(lat_sorted) - 1, int(round((p/100.0) * (len(lat_sorted)-1)))))
    return lat_sorted[k]

print(f"\n=== {label} (n={len(lat)}) ===")
print(f"min : {min(lat):8.1f} ms")
print(f"P50 : {pct(50):8.1f} ms")
print(f"P75 : {pct(75):8.1f} ms")
print(f"P90 : {pct(90):8.1f} ms")
print(f"max : {max(lat):8.1f} ms")
print(f"mean: {statistics.mean(lat):8.1f} ms")

# CSVも残す(記事のグラフ用)
csv = f"latency_{label}.csv"
with open(csv, "w") as f:
    f.write("index,latency_ms\n")
    for i, v in enumerate(lat):
        f.write(f"{i+1},{v:.1f}\n")
print(f"wrote {csv}")
PYEOF
python bench.py "$RUNTIME_ARN_V1" 50 v1
# benchmarking v1: 50 cold invocations
  [  1/50]    987.7 ms
  [  2/50]    690.9 ms
  [  3/50]    510.0 ms
  [  4/50]    633.9 ms
  [  5/50]    651.7 ms
  [  6/50]    544.1 ms
  [  7/50]    511.7 ms
  [  8/50]    625.9 ms
  [  9/50]    534.2 ms
  [ 10/50]    626.8 ms
  [ 11/50]   3466.8 ms
  [ 12/50]   3479.5 ms
  [ 13/50]   3473.8 ms
  [ 14/50]    531.5 ms
  [ 15/50]    483.8 ms
  [ 16/50]    608.2 ms
  [ 17/50]    616.4 ms
  [ 18/50]    629.1 ms
  [ 19/50]    507.9 ms
  [ 20/50]    565.2 ms
  [ 21/50]    562.8 ms
  [ 22/50]    599.9 ms
  [ 23/50]    614.6 ms
  [ 24/50]   3404.0 ms
  [ 25/50]   3447.1 ms
  [ 26/50]   3439.1 ms
  [ 27/50]    486.8 ms
  [ 28/50]    498.6 ms
  [ 29/50]    506.9 ms
  [ 30/50]    497.6 ms
  [ 31/50]    513.3 ms
  [ 32/50]    547.0 ms
  [ 33/50]    469.6 ms
  [ 34/50]    522.1 ms
  [ 35/50]    516.9 ms
  [ 36/50]    486.4 ms
  [ 37/50]   3447.7 ms
  [ 38/50]   3437.7 ms
  [ 39/50]   3451.1 ms
  [ 40/50]    596.2 ms
  [ 41/50]    509.8 ms
  [ 42/50]   3478.8 ms
  [ 43/50]    499.9 ms
  [ 44/50]    576.3 ms
  [ 45/50]    499.8 ms
  [ 46/50]    546.2 ms
  [ 47/50]    514.3 ms
  [ 48/50]    534.6 ms
  [ 49/50]    528.1 ms
  [ 50/50]    639.0 ms

=== v1 (n=50) ===
min :    469.6 ms
P50 :    562.8 ms
P75 :    651.7 ms
P90 :   3447.7 ms
max :   3479.5 ms
mean:   1141.0 ms
wrote latency_v1.csv

多くは 500〜650ms 台なのに、時々 3,400〜3,480ms の塊が数回連続で発生していることがわかりコールドスタートが頻発しています。ばらつきが多く出ていることがポイントです。

次にV2で同じテストを行います。

python bench.py "$RUNTIME_ARN_V2" 50 v2
# benchmarking v2: 50 cold invocations
  [  1/50]   2278.0 ms
  [  2/50]   2065.1 ms
  [  3/50]   1828.7 ms
  [  4/50]   1770.9 ms
  [  5/50]   2176.2 ms
  [  6/50]   1935.7 ms
  [  7/50]   1826.2 ms
  [  8/50]   1650.7 ms
  [  9/50]   1660.5 ms
  [ 10/50]   1749.4 ms
  [ 11/50]   1716.5 ms
  [ 12/50]   2547.9 ms
  [ 13/50]   1668.8 ms
  [ 14/50]   1745.4 ms
  [ 15/50]   1791.7 ms
  [ 16/50]   2143.3 ms
  [ 17/50]   1745.0 ms
  [ 18/50]   1706.5 ms
  [ 19/50]   1820.9 ms
  [ 20/50]   1776.6 ms
  [ 21/50]   1800.0 ms
  [ 22/50]   1844.7 ms
  [ 23/50]   1887.9 ms
  [ 24/50]   1643.0 ms
  [ 25/50]   1687.4 ms
  [ 26/50]   1742.8 ms
  [ 27/50]   1693.6 ms
  [ 28/50]   1606.7 ms
  [ 29/50]   1663.8 ms
  [ 30/50]   2028.6 ms
  [ 31/50]   1625.5 ms
  [ 32/50]   1727.1 ms
  [ 33/50]   1974.7 ms
  [ 34/50]   1668.6 ms
  [ 35/50]   1642.1 ms
  [ 36/50]   1672.1 ms
  [ 37/50]   1714.1 ms
  [ 38/50]   1750.8 ms
  [ 39/50]   2422.2 ms
  [ 40/50]   1678.5 ms
  [ 41/50]   1742.5 ms
  [ 42/50]   1914.3 ms
  [ 43/50]   2164.7 ms
  [ 44/50]   1768.7 ms
  [ 45/50]   1874.7 ms
  [ 46/50]   1759.1 ms
  [ 47/50]   1645.9 ms
  [ 48/50]   1905.4 ms
  [ 49/50]   1562.1 ms
  [ 50/50]   1737.4 ms

=== v2 (n=50) ===
min :   1562.1 ms
P50 :   1749.4 ms
P75 :   1887.9 ms
P90 :   2143.3 ms
max :   2547.9 ms
mean:   1823.1 ms
wrote latency_v2.csv

纏めると以下の通りとなっています。V2そのもの全体として高速化しているわけではないようです。一方V1は呼び出しごとに大きいばらつきあありましたがV2では呼び出しのレイテンシが安定していることがわかります。

指標

V1

V2

min

469.6 ms

1,562.1 ms

P50

562.8 ms

1,749.4 ms

P75

651.7 ms

1,887.9 ms

P90

3,447.7 ms

2,143.3 ms

max

3,479.5 ms

2,547.9 ms

mean

1,141.0 ms

1,823.1 ms

レンジ幅(max-min)

3,009.9 ms

985.8 ms

今回のテストの様に連続的に呼び出しを行わなければV1は高い確率でコールドスタートとなることが想定されますので、このような場合V2は速いといえます。一方連続的に呼び出しが行われる場合V1の方が速いためしばらくは使い分けになりそうです。

Written by
編集部

亀田 治伸

Kameda Harunobu

  • Facebook->
  • X->
  • GitHub->

Share

Facebook->X->
Back
to list
<-