前回、「JenkinsがじわじわGitクローンできなくなっていった話」と「プロビジョンドスループットで1万円使った反省」の2つの記事で、EFSのメタデータIOPS枯渇問題とその対応について書きました。
月曜の朝にJenkinsが重くなり、調査の結果、Git一時ファイル(tmp_pack_*)が約15GB蓄積していたことが原因だと突き止めました。応急処置としてプロビジョンドスループットを300 MiB/sに変更し、その後Elastic Throughputに切り替えて、定期クリーンアップジョブも作成しました。
「これで解決だ」と思っていました。
でも実は、まだ終わっていませんでした。
グラフが語る「本当の始まり」
トラブル対応後、念のためEFSのBurst Credit Balanceのグラフを確認していたところ、1/13頃から明確な減少トレンドが始まっていることに気づきました。

問題が顕在化したのは1/26-1/27でしたが、根本原因は2週間前の1/13前後に発生していたことになります。
つまり、前回の記事で書いた「tmp_pack_*の蓄積」は症状の悪化であり、真の根本原因は別にあるということです。
正直ここで詰まりました。1/13前後で何が変わったのか、思い当たる節がいくつかありましたが、確定的な情報ではありません。
1/13前後の変更を洗い出す
思い当たる変更点は2つありました。
1. エージェント方式の変更
1/13前後に、Jenkinsエージェントの運用方式を大きく変更していました。
従来の方式: 共有エージェント
- EC2インスタンス: c5.large など
- 複数のジョブで共有して使用
- ワークスペースを再利用
git pullで差分のみ取得
新しい方式: 使い捨てエージェント
- EC2インスタンス: t3.small など
- 1ジョブ1エージェント
- 使い終わったら破棄
- 毎回
git cloneでフルクローン
コスト削減のための変更でしたが、これがメタデータIOPSに与える影響は考えていませんでした。
2. 年始の開発加速
年始なので各チームの開発が加速し、Jenkins全体の負荷が上がった可能性もあります。
仮説: 使い捨て方式がメタデータIOPSを激増させた
ここで立てた仮説は、使い捨てエージェント方式への変更が、メタデータIOPSを激増させたのではないかというものでした。
簡単な計算をしてみます:
ビルド回数: 50回/日(推定)
1クローンあたりのファイル作成: 5,000個
共有エージェント方式:
初回のみクローン = 5,000回のメタデータ操作
使い捨てエージェント方式:
50ビルド × 5,000ファイル = 250,000回のメタデータ操作/日
50倍のメタデータIOPS増加です。
年始で開発が加速すれば、この数字はさらに増えます。コスト最適化のつもりが、別の問題を引き起こしていた可能性が高いと思いました。
キャッシュディレクトリの謎
調査を進める中で、/mnt/efs/jenkins/caches/ というディレクトリに気づきました。
/mnt/efs/jenkins/caches/git-3e9b32912840757a720f39230c221f0e
このようなディレクトリが大量にあります。これは何でしょうか?
実は、これはJenkinsのGitプラグインがリポジトリをキャッシュするためのbare repositoryです。
Gitキャッシュの仕組み
JenkinsのGitプラグインは、効率化のために以下のような仕組みを持っています:
- リモートリポジトリを
/mnt/efs/jenkins/caches/git-{hash}/にbare repositoryとしてキャッシュ - 各ジョブのワークスペースには、このキャッシュから
git clone --referenceでクローン - ハッシュ値はリポジトリURL + ブランチなどから生成
ここで新たな疑問が浮かびました: 使い捨てエージェント方式に変更したことで、このキャッシュが無効化されているのではないか?
- 共有エージェント: キャッシュが有効活用される
- 使い捨てエージェント: 毎回新しいエージェントなので、キャッシュの恩恵を受けられず、毎回フルクローン
tmp_pack_*の正体を再確認
前回の記事で見つけた tmp_pack_* ファイルの場所を改めて確認してみます:
jobs/sample-job/jobs/sample-pipeline/builds/104/libs/
└── 335abf.../root/.git/objects/pack/
└── tmp_pack_WqmOyE ← 100-300MB
これはビルドごとのディレクトリに存在しています。つまり:
jobs/sample-job/jobs/sample-pipeline/
└── builds/
├── 104/
│ └── libs/.../tmp_pack_WqmOyE
├── 105/
│ └── libs/.../tmp_pack_XYZ123
└── 106/
└── libs/.../tmp_pack_ABC456
毎ビルドごとにPipeline Shared Libraryが再チェックアウトされ、それぞれで tmp_pack_* が残っている状態です。
ここで新しい疑問が出てきます: なぜ毎ビルドごとにShared Libraryが再チェックアウトされているのか?
通常、Shared Libraryもキャッシュされるはずです。
根本原因の発見: キャッシュ設定がOFFだった
Jenkinsの設定を確認したところ、決定的な設定ミスを見つけました。
Shared Libraryの設定で、Cache fetched versions on controller for quick retrieval のチェックが外れていました。
つまり:
- Shared Libraryのキャッシュが完全に無効
- 毎ビルドごとにリモートリポジトリからフルフェッチ
.git/objects/pack/に一時ファイルが生成される- メタデータIOPSが激増
これで点と点が線になりました:
- 使い捨てエージェント方式への変更(1/13)→ エージェント側で毎回フルクローン
- Shared Libraryキャッシュ無効(以前から)→ Controller側で毎回フルフェッチ
- 年始の開発加速 → ビルド回数増加
この3つが重なり、メタデータIOPSが激増。2週間かけてBurst Creditが枯渇し、1/26に症状が顕在化しました。
対策: キャッシュを有効化する
すぐに設定を変更しました:
Cache fetched versions on controller for quick retrievalをONRefresh time in minutesを180分に設定
Refresh timeをどう決めたか
ここは意外と肝です。選択肢としては:
- 60-120分: 積極的な開発期向け。変更が早く反映されるがIOPS削減効果は中程度
- 180分(3時間): バランス型。1日8回程度の更新チェック
- 360分(6時間): 安定運用向け。1日4回の更新チェック
- 1440分(24時間): 最大限のIOPS削減。変更頻度が低い場合
今回は**180分(3時間)**を選びました。理由は:
- 1日8回程度の更新チェック(9時、12時、15時、18時...)
- Shared Libraryの変更は通常半日以内に反映されれば問題ない
- IOPSを大幅に削減できる(毎ビルド → 3時間に1回)
- 緊急時は手動でキャッシュクリアも可能
実は、Jenkinsには「強制的にリフレッシュ」する機能があるので、急ぎの変更があっても問題ありません。ただ、その存在を忘れてしまいそうなので、運用ドキュメントに書いておくことにしました。
設定変更後の効果測定
設定変更後、以下のタイムラインで効果を確認する予定です:
短期(24-48時間後)
- 新しい
tmp_pack_*が生成されなくなったか - EFSのメタデータIOPSが減少しているか
中期(1週間後)
- Burst Credit Balanceが回復傾向にあるか
- ビルドのパフォーマンスが安定しているか
長期(1ヶ月後)
- クレジットが安定して維持されているか
- 問題が再発していないか
振り返りと学び
根本原因は複合的だった
今回の問題は、単一の原因ではなく、複数の要因が重なって発生していました:
- Shared Libraryキャッシュ無効化(以前から存在)
- 使い捨てエージェント方式への変更(1/13前後)
- 年始の開発加速(ビルド回数増加)
それぞれ単体では大きな問題にならなかったかもしれませんが、3つが重なることで、メタデータIOPSが臨界点を超えました。
症状と根本原因の時期は異なる
- 症状発現: 1/26-1/27
- 根本原因: 1/13前後
- クレジット枯渇: 2週間かけて進行
グラフを時系列で見る重要性を痛感しました。目の前の症状だけを見て対応すると、表面的な修正に終わってしまいます。
アーキテクチャ変更のリスク
使い捨てエージェント方式への変更は、コスト最適化が目的でした。確かにEC2のコストは削減できましたが、別の場所でコストとパフォーマンスの問題を引き起こしていました。
アーキテクチャを変更する際は:
- パフォーマンス影響を事前評価する
- 監視指標を設定してから変更する
- 変更後も継続的にメトリクスを追跡する
これらが重要だと改めて感じました。
メタデータIOPSの特性
EFSのメタデータIOPSについて、重要な特性がわかりました:
- 小さいファイルの大量作成・削除が致命的
- ストレージ容量よりファイル数が重要
- バーストモードはクレジット管理が鍵
- クレジット枯渇は段階的に進行する
特に、.git/objects/ 配下のような「小さなファイルが大量にある」ケースでは、通常のファイルI/Oとは全く異なる挙動を示します。
次の問い
今回の対応で、Shared Libraryのキャッシュを有効にしました。しかし、使い捨てエージェント方式自体は継続しています。
エージェント側のGitキャッシュは、使い捨て方式でも有効活用できるのか?
これについては、もう少し調査が必要です。可能性としては:
- EFS上のGitキャッシュを全エージェントで共有する
- エージェントのライフサイクルを少し長くして、複数ジョブで再利用する
- S3などにキャッシュを配置し、起動時に同期する
コストとパフォーマンスのバランスをどう取るか。これは次の課題として残ります。
関連記事
この記事は、以下の記事の続編です:
/ja/tech/sre/jenkins-efs-metadata-iops-issue
/ja/tech/sre/efs-provisioned-throughput-cost-reflection
関連書籍
今回のようなインフラ運用やトラブルシューティングについてさらに学びたい方におすすめの書籍です。
[📦 商品リンク: moshimo-book-sre-google]
[📦 商品リンク: moshimo-book-jenkins-jissen]
[📦 商品リンク: moshimo-book-aws-operations]