エムオーテックス株式会社が運営するテックブログです。

Kiro-CLIを使ってDynamoDBのリザーブドキャパシティ購入量見積もりを効率化する

Kiro-CLIを使ってDynamoDBのリザーブドキャパシティ購入量見積もりを効率化する

はじめに

こんにちは。サービス開発1課SREチームの植松です。

エムオーテックスではリザーブドインスタンスやSavings Plansは年1回更新しており、何をどのくらい購入するかは毎年棚卸しています。

DynamoDBリザーブドキャパシティについては過去の記事で紹介した手順で確認していますが、作業の手間が多いので効率化したいなと思っていました。 Kiro CLIを使うことでかなりの作業の手間の削減と、キャパシティ使用量の可視化ができるようになったので方法を紹介したいと思います。

tech.motex.co.jp

これまでのリザーブドキャパシティの見積もり手順

過去に紹介した手順をおさらいすると、ざっくり以下のような流れでした。

  • https://console.aws.amazon.com/billing/home?#/reports/usageからDynamoDBの使用状況レポートCSVファイルをダウンロードする。
  • CSVファイルをエクセルで開き、読み込み使用量、書き込み使用量のグラフを作成する。
  • 十分な期間(少なくとも3ヶ月程度)のグラフを作成し、リザーブドキャパシティ購入量を検討する。

使用状況レポートの抽出期間は自由に変更できますが、範囲を広くし過ぎると全件出力されないため、1週間単位でのCSVファイル出力が必要です。 1週間単位のCSVファイルからグラフ作成して確認・・・を何ヶ月分も行うのは手間が多く、年に1回しかやらない作業とはいえ効率化したいと思っていました。

アップデートにより現在はリザーブドキャパシティについても購入推奨額をコンソールから確認できるとはいえ、実際の使用状況はやはり確認したいのでこの手順自体の廃止は考えていませんでした。

Kiro-CLIベースでのリザーブドキャパシティ見積もり

Kiro-CLIについて

Kiro-CLI は AWS が提供する ターミナル上で動作する AI アシスタント CLI で、自然言語を使ったコード生成・デバッグ・ワークフローの自動化などを行えるコマンドラインツールです。

これまで提供されていた Amazon Q Developer CLI の後継として 2025 年 11 月に一般提供が開始されました。

Kiro-CLIのインスール方法については、公式ドキュメントをご確認ください。

kiro.dev

Kiro-CLIを活用することで、グラフ作成の自動化、効率化ができるのではと考えました。 試行錯誤の末、このようなグラフ、チャートを出力できるようになりました。具体的な手順の前にまずはどのようなものが見れるかを紹介します。

こちらはWCU(書き込みキャパシティユニット)の日毎の推移のグラフです。赤線が現在の購入量、緑線が25パーセンタイル(全データの25%以下がこの値以下になる)、赤線とかぶっていますが、黄色線が10パーセンタイルを表します。

WCU の値はワークロードの負荷に応じて日々大きく変動します。そのため、グラフの下限付近を目安に WCU を設定することで、実際の使用状況に即したリザーブドキャパシティの購入量を検討しやすくなります。この考え方は、書き込みキャパシティ(WCU)だけでなく、読み取りキャパシティ(RCU)についても同様です。

例えば、グラフ上で 8,000 程度の水準を基準にキャパシティを設定した場合を考えると、実際の使用量がその値を下回っている時間帯が長く発生することが読み取れます。DynamoDB のリザーブドキャパシティは、WCU/RCU を一定期間(通常 1 年または 3 年)あらかじめ契約することで割引が適用される仕組みですが、契約した分については、使用量が少ない時間帯であっても料金が発生します。

そのため、実際の使用実績に対して高めの水準でキャパシティを設定すると、割引は受けられるものの、使用量と設定値の乖離が大きくなり、効率の観点で見直し余地が生じることがあります。

このように日次の推移をグラフとして可視化することで、現在の WCU/RCU の使用実績と購入量の関係を具体的に把握でき、どの水準でリザーブドキャパシティを設定するのが妥当かについて、チーム内で共通認識を持った議論がしやすくなりました。

WCUとRCU(読み込みキャパシティユニット)のヒストグラムです。WCUの方は4000~5000と10000前後の二峰性があることがわかります。

大まかなWCUとRCUの数値の傾向を把握するのに役に立ちました。

WCUとRCUの統計サマリーのボックスプロットを表示するグラフです。最小値(min)、パーセンタイル値、最大値(max)などが含まれます。

見積もり手順書

Kiro-CLIとやり取りし、上記のグラフを出力するための手順書を作成しました。 グラフを出力するためのPythonスクリプト例などが含まれています。手順書の内容についての詳細な説明は割愛させていただきます。

# DynamoDB リザーブドキャパシティ購入量見積もり手順

## 概要
DynamoDBのリザーブドキャパシティ(RI)の適切な購入量を、使用状況レポートから算出する手順書です。

## 前提条件
- DynamoDBの使用状況レポートが利用可能
- 時間単位のキャパシティユニット使用量データが含まれている
- 対象リージョンが明確(例:APN1 - 東京リージョン)

## 手順

### 1. データの準備と確認

#### 1.1 CSVファイルの構造確認
```bash
# ファイル一覧確認
ls -la report-amazondynamodb-*.csv

# データ構造確認
head -5 report-amazondynamodb-*.csv
```

期待される列:
- Service, Operation, UsageType, Resource, StartTime, EndTime, UsageValue

#### 1.2 対象リージョンの特定
```python
# リージョン別使用量確認
committed_df = df[df['Operation'] == 'CommittedThroughput']
usage_types = committed_df['UsageType'].unique()
```

重要:分析対象リージョンを明確にする(例:APN1のみ)

#### 1.3 データ品質の確認
```python
# 時系列データの連続性確認
df['StartTime'] = pd.to_datetime(df['StartTime'], format='mixed')
print(f"データ期間: {df['StartTime'].min()} ~ {df['StartTime'].max()}")
print(f"時間間隔: {df['StartTime'].diff().dropna().unique()}")
```

**注意**: データに大きな時間の飛び(例:2010年→2025年)がある場合は、分析対象期間のみを抽出する

#### 1.4 対象期間の設定
```python
# 柔軟な期間指定関数
def filter_target_period(df, start_date='2025-08-01', end_date='2025-11-01'):
    if len(df) > 0:
        return df[(df['StartTime'] >= start_date) & (df['StartTime'] < end_date)].copy()
    return df

# 使用例
apn1_wcu_normal = filter_target_period(apn1_wcu_normal, '2025-08-01', '2025-11-01')
```

**重要**: 分析期間は用途に応じて調整(1ヶ月:詳細分析、3ヶ月:季節性考慮)

### 2. データ分析

#### 2.1 基本統計の算出
対象リージョンのWCU/RCU使用量について:
- 最小値(ベースライン)
- 10パーセンタイル
- 25パーセンタイル
- 平均値
- 75パーセンタイル
- 最大値

#### 2.2 現在のRI利用率確認
```
利用率 = 平均使用量 / 現在のRI購入量 × 100
```

### 3. ベースライン戦略による見積もり

#### 3.1 WCU(WriteCapacityUnit)見積もり
**基本方針**: ベースライン(下限)に合わせてRI購入、ピーク時はオンデマンド対応

推奨購入量の選択肢:
- **保守的**: 10パーセンタイル値
- **バランス型**: 25パーセンタイル値
- **最小限**: 最小値(リスク高)

**計算例**:
```
WCU 10パーセンタイル: 4,273
WCU 25パーセンタイル: 4,708
→ 推奨購入量: 4,300-4,700
```

#### 3.2 RCU(ReadCapacityUnit)見積もり
WCUと同様の手法で算出

**計算例**:
```
RCU 10パーセンタイル: 287
RCU 25パーセンタイル: 325
→ 推奨購入量: 300-350
```

### 4. エビデンス用グラフの作成

#### 4.1 時系列グラフ
使用量の時間的変動パターンを可視化:
```python
# 対象期間のデータのみを抽出(柔軟な期間指定)
def filter_target_period(df, start_date='2025-08-01', end_date='2025-11-01'):
    if len(df) > 0:
        return df[(df['StartTime'] >= start_date) & (df['StartTime'] < end_date)].copy()
    return df

# WCU時系列グラフ
plt.figure(figsize=(20, 8))
plt.plot(wcu_data['StartTime'], wcu_data['Total_WCU'], 'b-', linewidth=1, alpha=0.8)
plt.axhline(y=current_ri_wcu, color='red', linestyle='--', linewidth=2, label='現在のRI購入量')
plt.axhline(y=wcu_10th_percentile, color='orange', linestyle=':', linewidth=2, label='10パーセンタイル')
plt.axhline(y=wcu_25th_percentile, color='green', linestyle=':', linewidth=2, label='25パーセンタイル')

# 日付軸の設定(期間に応じて調整)
import matplotlib.dates as mdates
# 1ヶ月の場合: 2日間隔
plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=2))
# 複数月の場合: 週1回表示
plt.gca().xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=mdates.MO, interval=1))
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%m/%d'))
plt.setp(plt.gca().xaxis.get_majorticklabels(), rotation=45)
```

**期待される結果**: 正弦波のような日毎の使用量変動パターン
**日付軸**: 期間に応じて適切な間隔で表示(1ヶ月:2日間隔、複数月:週1回)

#### 4.2 分布グラフ
使用量の頻度分布を可視化:
```python
# ヒストグラム + 統計値マーカー
plt.hist(wcu_data['Total_WCU'], bins=30, alpha=0.7, color='blue')
plt.axvline(x=current_ri_wcu, color='red', linestyle='--', label='現在のRI')
plt.axvline(x=wcu_10th_percentile, color='orange', linestyle=':', label='10%')
plt.axvline(x=wcu_25th_percentile, color='green', linestyle=':', label='25%')
```

#### 4.3 統計サマリーグラフ
ボックスプロットによる統計値の可視化:
```python
# ボックスプロット + 統計値テキスト表示
box = plt.boxplot([wcu_data['Total_WCU']], patch_artist=True)
plt.axhline(y=current_ri_wcu, color='red', linestyle='--', label='現在のRI')
# 統計値をテキストで併記
plt.text(1.2, wcu_stats['max'], f'最大: {wcu_stats["max"]:.0f}')
plt.text(1.2, wcu_stats['p25'], f'25%: {wcu_stats["p25"]:.0f}')
```

#### 4.4 複数案比較グラフ
意思決定支援のための比較グラフを作成:
```python
# 複数の購入案を同一グラフで比較
plt.figure(figsize=(18, 8))
plt.plot(wcu_data['StartTime'], wcu_data['Total_WCU'], 'b-', linewidth=1.5, alpha=0.8, label='実際の使用量')

# 複数案の表示
plt.axhline(y=current_purchase, color='red', linestyle='--', linewidth=3, label='現状維持案')
plt.axhline(y=data_recommendation, color='green', linestyle='--', linewidth=3, label='データ分析推奨')
plt.axhline(y=external_recommendation, color='purple', linestyle='--', linewidth=3, label='外部推奨')

# 統計値(参考)
plt.axhline(y=percentile_25, color='orange', linestyle=':', linewidth=1, alpha=0.7, label='25パーセンタイル')

plt.title('使用量と購入案比較', fontsize=16, fontweight='bold')
plt.legend(fontsize=12, loc='upper right')
```

**目的**: チーム議論での選択肢比較を視覚化

#### 4.5 利用率比較グラフ
各購入案での利用率を比較:
```python
# 利用率比較グラフ
options = ['現状維持', 'データ分析推奨', '外部推奨']
purchase_amounts = [current_purchase, data_recommendation, external_recommendation]
utilization_rates = [(average_usage / amount) * 100 for amount in purchase_amounts]

plt.figure(figsize=(10, 6))
bars = plt.bar(options, utilization_rates, color=['red', 'green', 'purple'], alpha=0.7)
plt.axhline(y=100, color='black', linestyle='--', alpha=0.5, label='100%(適正)')
plt.title('購入案別利用率比較', fontsize=14, fontweight='bold')
plt.ylabel('利用率 (%)', fontsize=12)

# 数値表示
for bar, rate in zip(bars, utilization_rates):
    height = bar.get_height()
    plt.text(bar.get_x() + bar.get_width()/2., height + 2,
             f'{rate:.1f}%', ha='center', va='bottom', fontweight='bold')
```

#### 4.6 作成されるグラフファイル
- `dynamodb_wcu_timeseries.png`: WCU時系列推移
- `dynamodb_rcu_timeseries.png`: RCU時系列推移
- `dynamodb_capacity_distribution.png`: 使用量分布
- `dynamodb_capacity_summary.png`: 統計サマリー
- `wcu_comparison_chart.png`: WCU複数案比較
- `rcu_comparison_chart.png`: RCU複数案比較
- `utilization_comparison.png`: 利用率比較

### 5. 見積もり結果の評価

### 7. チーム議論用資料の作成

#### 7.1 資料構成
意思決定支援のためのシンプルな構成:
```markdown
## 現在の購入情報
- 購入量、平均使用量、利用率、問題点

## 来年度の購入案
### 案1: 現状維持
- メリット: 変更リスクなし、予算変更なし、運用負荷なし
- デメリット: 高コスト継続、過剰購入継続、利用率悪化

### 案2: データ分析推奨
- メリット: データ根拠、コスト最適化、適正利用率
- デメリット: ピーク対応不安、成長予測不足

### 案3: 外部推奨
- メリット: 高い安全性、成長対応、運用安定
- デメリット: 高コスト、過剰購入、利用率低下
```

#### 7.2 比較表の作成
```python
# 購入案比較表
comparison_data = {
    '項目': ['WCU', 'RCU'],
    '現状維持': [current_wcu, current_rcu],
    'データ分析推奨': [recommended_wcu, recommended_rcu],
    '外部推奨': [external_wcu, external_rcu]
}
comparison_df = pd.DataFrame(comparison_data)
```

#### 7.3 議論観点の整理
- **リスク許容度**: 保守的 vs バランス vs 現状維持
- **コスト vs 安全性**: コスト重視 vs 安全性重視
- **成長予測**: 成長期待大 vs 安定成長 vs 成長不透明
- **運用負荷**: 最小 vs 中程度 vs 高め

### 8. 見積もり結果の評価

#### 8.1 現在購入量との比較
```
差分 = 推奨購入量 - 現在購入量
変更率 = 差分 / 現在購入量 × 100
```

#### 8.2 コスト影響の試算
- RI削減によるコスト削減効果
- オンデマンド増加によるコスト増加
- 純コスト影響の算出

### 9. 推奨事項の策定

#### 9.1 購入量決定の考慮要素
- **安全マージン**: 予期しない使用量増加への対応
- **成長予測**: 翌年の事業成長による使用量増加見込み
- **季節性**: 分析期間外の使用パターン変動
- **コスト効率**: RI vs オンデマンドのコスト比較

#### 9.2 段階的調整の推奨
- 大幅な変更は四半期ごとに段階実施
- 使用量モニタリングによる継続的な最適化
- 年次での見直しサイクル確立

## 実装例

## 実装例

### 完全な分析・グラフ作成スクリプト
```python
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import glob
import matplotlib.dates as mdates

# 日本語フォント設定
plt.rcParams['font.family'] = ['DejaVu Sans', 'Hiragino Sans']

# データ読み込み
csv_files = glob.glob('report-amazondynamodb-*.csv')
all_data = []
for file in csv_files:
    df = pd.read_csv(file)
    all_data.append(df)

combined_df = pd.concat(all_data, ignore_index=True)
combined_df.columns = combined_df.columns.str.strip()

# APN1のCommittedThroughputデータを抽出
committed_df = combined_df[combined_df['Operation'] == 'CommittedThroughput'].copy()
apn1_wcu_normal = committed_df[committed_df['UsageType'] == 'APN1-WriteCapacityUnit-Hrs'].copy()
apn1_wcu_ia = committed_df[committed_df['UsageType'] == 'APN1-IA-WriteCapacityUnit-Hrs'].copy()

# 時間データの処理
for df in [apn1_wcu_normal, apn1_wcu_ia]:
    if len(df) > 0:
        df['StartTime'] = pd.to_datetime(df['StartTime'], format='mixed')

# 柔軟な期間指定
def filter_target_period(df, start_date='2025-08-01', end_date='2025-11-01'):
    if len(df) > 0:
        return df[(df['StartTime'] >= start_date) & (df['StartTime'] < end_date)].copy()
    return df

apn1_wcu_normal = filter_target_period(apn1_wcu_normal)
apn1_wcu_ia = filter_target_period(apn1_wcu_ia)

# データマージと統計計算
wcu_merged = pd.merge(apn1_wcu_normal[['StartTime', 'UsageValue']], 
                     apn1_wcu_ia[['StartTime', 'UsageValue']], 
                     on='StartTime', suffixes=('_normal', '_ia'))
wcu_merged['Total_WCU'] = wcu_merged['UsageValue_normal'] + wcu_merged['UsageValue_ia']
wcu_merged = wcu_merged.sort_values('StartTime')

# ベースライン分析
baseline = wcu_merged['Total_WCU'].min()
percentile_10 = np.percentile(wcu_merged['Total_WCU'], 10)
percentile_25 = np.percentile(wcu_merged['Total_WCU'], 25)

print(f"推奨WCU購入量: {percentile_10:.0f} - {percentile_25:.0f}")

# 時系列グラフ作成
plt.figure(figsize=(20, 8))
plt.plot(wcu_merged['StartTime'], wcu_merged['Total_WCU'], 'b-', linewidth=1, alpha=0.8, label='WCU使用量')

# 現在のRI購入量(環境に応じて設定)
current_wcu_purchase = 4000  # ← 必要に応じて変更
plt.axhline(y=current_wcu_purchase, color='red', linestyle='--', linewidth=2, 
           label=f'現在のRI購入量 ({current_wcu_purchase})')
plt.axhline(y=percentile_10, color='orange', linestyle=':', linewidth=2, label=f'10パーセンタイル ({percentile_10:.0f})')
plt.axhline(y=percentile_25, color='green', linestyle=':', linewidth=2, label=f'25パーセンタイル ({percentile_25:.0f})')

plt.title('APN1 DynamoDB WCU使用量推移', fontsize=16, fontweight='bold')
plt.xlabel('日時', fontsize=12)
plt.ylabel('WCU (WriteCapacityUnit)', fontsize=12)
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)

# 期間に応じた日付軸設定
data_period_days = (wcu_merged['StartTime'].max() - wcu_merged['StartTime'].min()).days
if data_period_days <= 31:
    # 1ヶ月以下: 2日間隔
    plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=2))
else:
    # 複数月: 週1回表示
    plt.gca().xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=mdates.MO, interval=1))

plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%m/%d'))
plt.setp(plt.gca().xaxis.get_majorticklabels(), rotation=45)

plt.tight_layout()
plt.savefig('dynamodb_wcu_timeseries.png', dpi=300, bbox_inches='tight')
plt.close()
```

### 分析スクリプトの基本構造
### データ品質チェックスクリプト
```python
# 時系列データの品質確認
def check_data_quality(df):
    df['StartTime'] = pd.to_datetime(df['StartTime'], format='mixed')
    df = df.sort_values('StartTime')
    
    print(f"データ期間: {df['StartTime'].min()} ~ {df['StartTime'].max()}")
    print(f"データ件数: {len(df)}")
    
    # 時間間隔の確認
    time_diffs = df['StartTime'].diff().dropna()
    print(f"時間間隔の種類: {time_diffs.unique()[:5]}")
    
    # 異常な時間の飛びを検出
    large_gaps = time_diffs[time_diffs > pd.Timedelta(days=1)]
    if len(large_gaps) > 0:
        print(f"警告: 大きな時間の飛びが{len(large_gaps)}箇所検出されました")
        print("対象期間のデータのみを抽出することを推奨します")

# 使用例
apn1_wcu = df[df['UsageType'] == 'APN1-WriteCapacityUnit-Hrs']
check_data_quality(apn1_wcu)
```

## 注意事項

### データの制約
- 分析期間が短い場合は季節性を考慮できない
- 過去データのため将来の成長を反映していない
- 異常値やメンテナンス期間の影響を除外する必要がある場合がある
- **時系列データの品質**: データに大きな時間の飛びがある場合は対象期間のみを抽出する

### グラフ作成時の注意点
- **時系列グラフ**: 対象期間のデータのみを使用し、連続した時間軸で表示する
- **日付軸の設定**: X軸の日付表示を適切に設定(例:2日間隔表示)
- **統計値の表示**: 現在のRI購入量と推奨ベースラインを明確に区別表示
- **グラフの解像度**: エビデンス用途のため高解像度(300dpi)で保存

### 運用上の考慮点
- RIは1年または3年の契約期間がある
- 途中解約や変更には制限がある
- リージョン間でのRI移動はできない

## まとめ

1. **データ確認**: 対象リージョンと期間を明確化、データ品質をチェック
2. **ベースライン分析**: 10-25パーセンタイル値を基準とする
3. **複数案比較**: 現状維持、データ分析推奨、外部推奨の比較グラフ作成
4. **チーム議論資料**: 意思決定支援のためのメリット・デメリット整理
5. **エビデンス作成**: 時系列・分布・統計サマリーグラフを作成
6. **安全マージン**: 成長予測と季節性を考慮
7. **段階的実施**: 大幅変更は段階的に実行
8. **継続監視**: 定期的な見直しと最適化

この手順により、コスト効率と運用安定性を両立したRI購入量を決定し、チーム議論を通じた合意形成とともに適切なエビデンスを提示できます。

見積もり手順

上記の手順書をベースとした、リザーブドキャパシティ見積もり手順を説明します

DynamoDBの使用状況レポートCSVファイルをダウンロードする

これまでの手順と同じくCSVファイルをダウンロードします。自動化も検討しましたが、年次作業であるため手動対応としました。今回は2025/8 ~ 2025/11までの期間のCSVファイルで見積もりのグラフを作成します。

手順書とCSVファイルを同じディレクトリに配置する

Kiro-CLIに指示する

Kiro-CLIに以下のように指示します。

dynamodb_ri_estimation_guide.mdをもとに、リザーブドキャパシティ見積もり検討のためのグラフ出力をお願いします。着手する前に、まずは計画を教えて

おおむね以下のような計画になると思います。

> ファイルの内容を確認しました。DynamoDBリザーブドキャパシティ見積もり検討のためのグラフ出力の計画を以下に示します:

## 計画

### 1. データ準備・確認
- CSVファイルの読み込みと構造確認
- 対象リージョン(APN1)のデータ抽出
- 時系列データの品質チェック
- 分析対象期間の設定

### 2. 統計分析
- WCU/RCU使用量の基本統計算出(最小値、10/25パーセンタイル、平均値、最大値)
- ベースライン戦略による推奨購入量の計算
- 現在のRI利用率確認

### 3. グラフ作成(6種類)
- **時系列グラフ**: WCU/RCU使用量の推移と推奨ライン表示
- **分布グラフ**: 使用量のヒストグラムと統計値マーカー
- **統計サマリーグラフ**: ボックスプロットによる統計値可視化
- **複数案比較グラフ**: 現状維持・データ分析推奨・外部推奨の比較
- **利用率比較グラフ**: 各購入案での利用率比較
- **総合ダッシュボード**: 全情報を1枚にまとめた資料

### 4. 分析レポート生成
- 推奨購入量の算出結果
- コスト影響の試算
- 意思決定支援資料の作成

この計画で進めてよろしいでしょうか?まず現在のディレクトリにあるCSVファイルを確認してから実装を開始します。

計画通り進めるよう指示します。Kiro-CLIがグラフ出力スクリプトを作成しますが内容を確認して承認します。

スクリプトを実行し、グラフが出力されることを確認します。

おわりに

本記事では、Kiro-CLI を用いて DynamoDB のリザーブドキャパシティ購入量を効率的に見積もる手順を紹介しました。
この手順により、作業時間の短縮や見積もり精度の向上だけでなく、「どれくらいリザーブドキャパシティを購入すべきか」というチーム内の議論を、データに基づいて進めやすくなった点も大きな効果だと感じています。

使用量の分布や傾向を可視化することで、「なぜこの購入量なのか」を共通認識として持てるようになり、感覚的な判断や属人性を減らすことができました。
リザーブドキャパシティの検討に悩んでいる方にとって、本手順が議論の土台づくりの一助になれば幸いです。