[アップデート] Amazon Bedrockで、最初のトークンを受信するまでの時間と1分あたりのクォータ消費量をAmazon CloudWatchメトリクスとして取得できるようになりました
はじめに
おのやんです。
今回のAWSアップデートにより、Amazon Bedrock(以下、Bedrock)のAmazon CloudWatch(以下、CloudWatch)メトリクスが2つ追加されました。
追加されたのはTimeToFirstTokenとEstimatedTPMQuotaUsageの2つです。
TimeToFirstTokenは、リクエストを送信してから最初のトークンを受信するまでの時間(TTFT: Time To First Token)です。特にAIチャットのようなアプリを監視するときに、回答が始まるまでの待ち時間を監視したいようなケースでは、こちらのメトリクスを使います。
EstimatedTPMQuotaUsageは、1分あたりのトークン(TPM: Tokens Per Minute)から算出されるクォータ消費量です。トークン消費数ではありません。
Bedrockには以前からInputTokenCountやOutputTokenCountなどのトークン数を表すメトリクスはありましたが、クォータ消費量のメトリクスはありませんでした。クォータ消費量の計算式は次のとおりです。
InputTokenCount + CacheWriteInputTokens + (OutputTokenCount x burndown rate)
特にClaudeの3.7以降のモデルでは、このburndown rate(バーンダウン率)は5倍なので、出力トークン1つに対してクォータを5消費します(他のモデルはバーンダウン率1倍)。この計算を自分でやらないとクォータ消費量がわからなかったのが、EstimatedTPMQuotaUsageメトリクスでそのまま見えるようになっています。
CloudWatchメトリクスの名前空間はAWS/Bedrockです。またディメンションはModelIdです。こちらのアップデートは全リージョンで対応していて、クロスリージョン推論プロファイル・リージョン内推論の両方で取得できます。
やってみた
ということで、CloudWatchアラームとダッシュボードを作成し、BedrockでClaude Sonnet 4.6を呼び出してデータが記録されることを確認します。また、CloudWatchアラームのアクションとしてAmazon SNS(以下、SNS)トピックを指定して、メール通知も一緒にやってみます。
といっても、やり方は通常のCloudWatchアラームの設定方法と同じです。上記で書いたとおり、名前空間はAWS/Bedrock、ディメンションとしてClaude Sonnet 4.6のモデルIDを指定して、メトリクスはそれぞれTimeToFirstTokenとEstimatedTPMQuotaUsageを指定します。
検証で使ったAWS CloudFormationテンプレートをこちらに記載しておきます。
検証で使ったAWS CloudFormationテンプレート
AWSTemplateFormatVersion: "2010-09-09"
Description: >
Amazon Bedrock observability - CloudWatch alarms and dashboard
for TimeToFirstToken and EstimatedTPMQuotaUsage metrics
Parameters:
ModelId:
Type: String
Default: "global.anthropic.claude-sonnet-4-6"
Description: >
Bedrock model ID to monitor (cross-region inference profile or in-region model ID)
AlertEmail:
Type: String
Description: Email address to receive CloudWatch alarm notifications
TTFTAlarmThresholdMs:
Type: Number
Default: 1000
Description: >
Threshold in milliseconds for TimeToFirstToken p99 alarm.
An alarm is triggered when p99 TTFT exceeds this value.
TPMQuotaAlarmThreshold:
Type: Number
Default: 3
Description: >
Threshold in tokens per minute for EstimatedTPMQuotaUsage alarm.
Set this to approximately 80% of your actual TPM quota limit for the target model.
Resources:
# SNS Topic for alarm notifications
AlarmTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: aws-test-topic-bedrock-observability
Subscription:
- Protocol: email
Endpoint: !Ref AlertEmail
# CloudWatch Alarm: TimeToFirstToken p99
TTFTAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: aws-test-alarm-ttft-bedrock-observability
AlarmDescription: !Sub >
TimeToFirstToken p99 exceeded ${TTFTAlarmThresholdMs}ms for model ${ModelId}.
This may indicate latency degradation in the Bedrock model.
Namespace: AWS/Bedrock
MetricName: TimeToFirstToken
Dimensions:
- Name: ModelId
Value: !Ref ModelId
ExtendedStatistic: p99
Period: 60
EvaluationPeriods: 3
DatapointsToAlarm: 2
Threshold: !Ref TTFTAlarmThresholdMs
ComparisonOperator: GreaterThanThreshold
TreatMissingData: notBreaching
AlarmActions:
- !Ref AlarmTopic
OKActions:
- !Ref AlarmTopic
# CloudWatch Alarm: EstimatedTPMQuotaUsage
TPMQuotaAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: aws-test-alarm-tpm-quota-bedrock-observability
AlarmDescription: !Sub >
EstimatedTPMQuotaUsage exceeded ${TPMQuotaAlarmThreshold} tokens/min for model ${ModelId}.
Consider requesting a quota increase before rate limiting occurs.
Namespace: AWS/Bedrock
MetricName: EstimatedTPMQuotaUsage
Dimensions:
- Name: ModelId
Value: !Ref ModelId
Statistic: Sum
Period: 60
EvaluationPeriods: 3
DatapointsToAlarm: 2
Threshold: !Ref TPMQuotaAlarmThreshold
ComparisonOperator: GreaterThanThreshold
TreatMissingData: notBreaching
AlarmActions:
- !Ref AlarmTopic
OKActions:
- !Ref AlarmTopic
# CloudWatch Dashboard for both metrics
ObservabilityDashboard:
Type: AWS::CloudWatch::Dashboard
Properties:
DashboardName: BedrockObservabilityDashboard
DashboardBody: !Sub |
{
"widgets": [
{
"type": "metric",
"x": 0,
"y": 0,
"width": 12,
"height": 6,
"properties": {
"title": "TimeToFirstToken (p50 / p90 / p99)",
"view": "timeSeries",
"stacked": false,
"metrics": [
[ "AWS/Bedrock", "TimeToFirstToken", "ModelId", "${ModelId}", { "stat": "p50", "period": 60, "label": "p50" } ],
[ "AWS/Bedrock", "TimeToFirstToken", "ModelId", "${ModelId}", { "stat": "p90", "period": 60, "label": "p90" } ],
[ "AWS/Bedrock", "TimeToFirstToken", "ModelId", "${ModelId}", { "stat": "p99", "period": 60, "label": "p99" } ]
],
"region": "${AWS::Region}",
"period": 60,
"yAxis": {
"left": {
"label": "Milliseconds",
"min": 0
}
},
"annotations": {
"horizontal": [
{
"label": "TTFT alarm threshold",
"value": ${TTFTAlarmThresholdMs},
"color": "#d62728"
}
]
}
}
},
{
"type": "metric",
"x": 12,
"y": 0,
"width": 12,
"height": 6,
"properties": {
"title": "EstimatedTPMQuotaUsage (tokens/min)",
"view": "timeSeries",
"stacked": false,
"metrics": [
[ "AWS/Bedrock", "EstimatedTPMQuotaUsage", "ModelId", "${ModelId}", { "stat": "Sum", "period": 60, "label": "Sum" } ]
],
"region": "${AWS::Region}",
"period": 60,
"yAxis": {
"left": {
"label": "Tokens",
"min": 0
}
},
"annotations": {
"horizontal": [
{
"label": "Quota alarm threshold",
"value": ${TPMQuotaAlarmThreshold},
"color": "#d62728"
}
]
}
}
},
{
"type": "alarm",
"x": 0,
"y": 6,
"width": 24,
"height": 3,
"properties": {
"title": "Alarm Status",
"alarms": [
"arn:aws:cloudwatch:${AWS::Region}:${AWS::AccountId}:alarm:aws-test-alarm-ttft-bedrock-observability",
"arn:aws:cloudwatch:${AWS::Region}:${AWS::AccountId}:alarm:aws-test-alarm-tpm-quota-bedrock-observability"
]
}
}
]
}
Outputs:
AlarmTopicArn:
Description: SNS Topic ARN for alarm notifications
Value: !Ref AlarmTopic
TTFTAlarmArn:
Description: CloudWatch Alarm ARN for TimeToFirstToken
Value: !GetAtt TTFTAlarm.Arn
TPMQuotaAlarmArn:
Description: CloudWatch Alarm ARN for EstimatedTPMQuotaUsage
Value: !GetAtt TPMQuotaAlarm.Arn
DashboardName:
Description: CloudWatch Dashboard name
Value: BedrockObservabilityDashboard
Claude Sonnet 4.6は、Boto3経由で実行します。
import boto3
import time
client = boto3.client("bedrock-runtime", region_name="ap-northeast-1")
model_id = "global.anthropic.claude-sonnet-4-6"
for i in range(5):
response = client.converse_stream(
modelId=model_id,
messages=[
{
"role": "user",
"content": [{"text": f"じゅげむを全文出力してください({i+1}回目)"}],
}
],
)
# ストリームを最後まで消費する
for event in response["stream"]:
pass
print(f"{i+1}回目完了")
time.sleep(2)
CloudWatchアラームやSNSの設定が終わった上で、上記のPythonファイルを実行して、アラームが発火するか確認してみます。
Pythonファイルを3回実行し、TimeToFirstTokenのCloudWatchアラームが発火した状態がこちらです。今回は、検証用途でしきい値を低く設定しています。また、事前の検証で取れているデータやOK / Alarmステータスも表示されています。

SNSのメール通知も、このように受信しました。

同様に、EstimatedTPMQuotaUsageのCloudWatchアラームが発火した状態がこちらです。

SNSのメール通知も、このように受信しました。

まとめ
Amazon BedrockのCloudWatchメトリクスが新たに2つ追加され、TimeToFirstTokenとEstimatedTPMQuotaUsageが監視できるようになりました。これにより、リクエストを送信してから最初のトークンを受信するまでの時間と、1分あたりのクォータ消費量を追跡できます。
特に、クォータ消費量はクォータ制限やレート制限に直接関わってくるので、追跡したいケースも多いのではないでしょうか?
みなさんもBedrockの監視メトリクスは見直してみてください。では!








