システム開発入門の第28週では、前週に実装した生成AI組込システムを対象に、運用テストとログ解析に取り組みました。
運用テストとは、実際の利用に近い条件でシステムを動かし、応答の遅れや失敗の起こり方を確かめる作業です。
授業は、負荷をかけて測る、記録できる形でログを残す、集計と可視化から改善案を立てる、という順で進みました。
授業のねらい
- 簡易負荷テストを実行し、応答時間と成功率を計測する。
- タイムスタンプ、ステータス、プロンプトハッシュなどのログ項目を統一し、集計できる状態にする。
- エラー率、多い入力パターン、再生成の頻度を可視化し、改善候補を見つける。
一回のテストで合否を決めるのではなく、観測した結果を次の変更と再テストにつなげることが、この授業の中心でした。
運用テストを繰り返す理由
田中先生:「実際に人が使い始めると、想定外の操作や入力の組み合わせが出てきます。運用テストで“どんな失敗が起きるか”を前もって見つけ、ログで検証できる仕組みを作ることが重要です。」
画面上で一度正しく動くだけでは、連続した利用や急なアクセス増加に耐えられるかは判断できません。
そこで、利用状況を変えながらテストし、そのときの応答時間や状態をログに残します。
同じ条件で再テストできれば、対策後に何が変わったのかを数字で比べられます。
負荷テストの設計
負荷テストは、一定数のリクエストをシステムへ送り、負荷が変わったときの応答や失敗を観測するテストです。
今回は学習環境の制限内で、班ごとに三つのシナリオを作りました。
| シナリオ | 実行条件 | 確かめる点 |
|---|---|---|
| ライト負荷 | 1秒あたり1リクエストを5分間 | 小さな負荷が続く間の応答 |
| ピーク負荷 | 30秒間に1秒あたり10リクエスト | 短時間に負荷が増えたときの遅延と失敗 |
| スパイク後回復 | 急増、待機、通常負荷への復帰を複数回 | 負荷の急増後に通常の状態へ戻るか |
テスト対象と送信量を先に決めたのは、結果をシナリオごとに比べるためです。
外部の本番サービスへ無断で負荷をかける実習ではなく、許可された学習環境で実施しました。
測定項目の読み方
授業では、速さだけでなく、遅い応答や失敗も一緒に確認しました。
| 測定項目 | 読み取れること |
|---|---|
| 平均応答時間 | すべての応答時間を合計し、件数で割った値 |
| 95パーセンタイル応答時間 | 応答時間を短い順に並べたとき、全体の95%が収まる境界 |
| 成功率 | 授業では、全リクエストに占めるHTTP 200の割合 |
| タイムアウトと例外の回数 | 応答を待ち切れなかった回数や、処理中に例外が起きた回数 |
平均値だけでは、一部の応答が大きく遅れた状態を把握しにくいため、95パーセンタイルや最大値も並べて確認しました。
簡易スクリプトが測る範囲
次のコードは、指定した間隔でPOSTリクエストを送り、HTTPステータスコードと応答時間を記録する学習用の例です。
import time, requests
def send_requests(url, n, interval):
results = []
for i in range(n):
start = time.time()
try:
r = requests.post(url, json={"prompt": "テスト"}, timeout=5)
latency = (time.time() - start) * 1000
results.append(("ok", r.status_code, latency))
except Exception as e:
latency = (time.time() - start) * 1000
results.append(("error", str(e), latency))
time.sleep(interval)
return results
この例のokは「HTTPレスポンスを受け取れた」ことを示す印であり、HTTP 200だったことを直接示す値ではありません。
授業で定めた成功率を求めるには、タプルの二つ目に保存したステータスコードを別途集計します。
実習では、ピーク負荷時にタイムアウトが増えた班があり、キャッシュやリトライを検討するきっかけになりました。
集計できるログの設計
ログは、出来事を後から追跡し、条件ごとに比べられる形で残す必要があります。
クラスでは、次の項目を最小構成として採用しました。
| 項目 | 授業での役割 |
|---|---|
timestamp |
ISO 8601形式で発生時刻を記録する |
request_id |
リクエストを一意に識別する |
prompt_hash |
同じ入力をまとめて数える |
user_id |
匿名化またはマスクした利用者識別子を記録する |
status |
ok、retry、fallback、errorを区別する |
latency_ms |
処理にかかった時間をミリ秒で記録する |
error_type |
エラーがある場合に種類を記録する |
note |
要確認などの補足を残す |
保存形式にはJSONLを使いました。
JSONLは、一行に一つのJSONデータを記録する形式です。
一件ずつ読み込めるため、今回のようにリクエスト単位で集計する処理へ渡しやすくなります。
Pythonによるログ集計
次の学習用コードでは、JSONLを読み込み、状態の内訳、応答時間、エラー種別、入力パターンの件数を確認します。
import json
from collections import Counter
from statistics import mean, median
logs = []
with open("app_logs.jsonl", "r", encoding="utf-8") as f:
for line in f:
logs.append(json.loads(line))
total = len(logs)
status_counts = Counter(log.get("status", "unknown") for log in logs)
print("status:", status_counts)
if total:
ok_rate = status_counts["ok"] / total
print(f"total={total}, ok_rate={ok_rate:.2%}")
else:
print("ログは0件です")
latencies = [
log["latency_ms"]
for log in logs
if isinstance(log.get("latency_ms"), (int, float))
]
if latencies:
print(
f"avg={mean(latencies):.1f}ms, "
f"median={median(latencies):.1f}ms, "
f"max={max(latencies):.1f}ms"
)
error_types = Counter(
log["error_type"]
for log in logs
if log.get("error_type")
)
print("error types:", error_types.most_common(10))
prompt_counts = Counter(
log["prompt_hash"]
for log in logs
if log.get("prompt_hash")
)
print("top prompts:", prompt_counts.most_common(10))
retryやfallbackをすべてエラーへ含めると、状態ごとの違いが見えなくなります。
そこで、まずstatusの内訳を表示し、そのうえでokの割合を計算する形に整理しました。
生徒からは「特定のプロンプトハッシュで失敗が集中している。入力パターンの問題か、モデル側の制約かを切り分けよう」という気づきが出ました。
同じハッシュに失敗が集まっている事実だけでは原因を決められないため、入力条件と処理側の条件を分けて確かめる必要があります。
グラフで確かめた傾向
授業ではmatplotlibを使ったプロトタイプを作り、集計結果を三種類のグラフで確認しました。
- 時系列グラフ:リクエスト数とエラー数が時間とともにどう変わったかを確認する。
- ヒストグラム:応答時間がどの範囲に分布しているかを確認する。
- 棒グラフ:エラー種別ごとの件数を比べる。
グラフは色を指定せず、標準スタイルで作成しました。
観測結果から改善案を立てる
クラスでは、ログから見えた現象ごとに対策案を整理しました。
| 観測した現象 | 授業で挙がった対策案 | 再テストで確かめること |
|---|---|---|
| 特定の入力でタイムアウトが多い | 入力長の制限、呼び出しのタイムアウト短縮、フォールバックの強化 | 対象の入力でタイムアウトが減るか |
| ピーク時に成功率が下がる | キャッシュTTLの延長、リトライのバックオフ、簡易レートリミッター | 同じピーク条件で成功率と応答時間がどう変わるか |
| 再生成の要求が多い | プロンプトの明確化、生成結果の形式チェック、再生成条件の整理 | 再生成の回数が減り、必要な形式を保てるか |
これらはログから直ちに確定した解決策ではなく、次のテストで確かめる仮説です。
変更を一度に重ねると、どの対策が結果に影響したのかを分けにくくなるため、授業では小さな改善へ分解して試す考え方を学びました。
生徒の振り返り
- 「ログを見て、問題点を客観的に語れるようになった」
- 「負荷テストで初めてユーザー視点の不便さがわかった」
- 「解析で見つかった『多い入力パターン』を前処理で弾けたのが嬉しかった」
田中先生:「ログは“過去の声”。数字を見れば、どこがおかしいかが見えてきます。重要なのは、発見した課題を小さな改善に分解して、すぐに試すことです。」
次週につながる再テストとA/Bテスト
次週は、今回の改善案を実装して同じ条件で再テストし、変更前後の差を定量的に確認します。
A/Bテストは、二つの案を同じ評価基準で比べ、どちらが目的に合うかを確かめる方法です。
実習の続きは、第29週の改善実装とA/Bテストで扱います。
第28週の学び
運用テストとログ解析を通じて、生徒たちはシステムを作るだけでなく、使われた結果を測り、原因を切り分け、改善案を再検証する流れを経験しました。
システムの状態を印象だけで判断せず、同じ条件で測れる記録を残すことが、継続的な改善の出発点になります。
この記事に関連する株式会社greedenの取り組み
運用中の数値を読み、原因を切り分けて改善を重ねる姿勢は、長く使えるWebシステムに欠かせません。株式会社greedenは、要件整理からテスト、リリース後の保守と運用改善まで一気通貫で支援しています。

