性能要件定義書とは

性能要件定義書(Performance Requirements Document)は、システムが満たすべき速度・処理量・容量を数値目標として定義した設計書です。インフラ設計(サーバースペック・ネットワーク帯域)と性能テスト(負荷テスト・ストレステスト)の合否基準として使用されます。

💡 性能要件を数値化する重要性

「速いこと」「重くならないこと」といった曖昧な要件は、インフラ設計の根拠にも性能テストの合否判定にもなりません。性能要件はレスポンスタイム・スループット・同時接続数・データ量など、具体的な数値で定義することで初めて設計・テストの基準として機能します。

① レスポンスタイム要件

画面・APIごとのレスポンスタイム(応答時間)目標値を定義します。「平均」だけでなく「ピーク時(95パーセンタイル)」の目標値も設定します。

対象測定条件平均レスポンスタイム95%tile最大(許容上限)
画面表示(参照系)通常負荷時(30同時接続)1秒以内3秒以内5秒以内
画面表示(検索・フィルタ)通常負荷時(30同時接続)2秒以内5秒以内10秒以内
データ登録・更新通常負荷時(30同時接続)3秒以内5秒以内10秒以内
API(参照系)通常負荷時(100 req/s)200ms以内500ms以内1秒以内
API(登録・更新系)通常負荷時(50 req/s)500ms以内1秒以内3秒以内
帳票PDF出力単一実行5秒以内10秒以内30秒以内
CSVエクスポート(1万件)単一実行10秒以内20秒以内60秒以内

⚠️ 「3秒以内」はユーザビリティの基準値

一般的に「Webページは3秒以内に表示されないとユーザーが離脱する」と言われています。重い処理(帳票出力・大量CSV)は3秒を超えても許容されますが、画面遷移やAPI応答は可能な限り3秒以内を目指します。Google の Core Web Vitals(LCP: Largest Contentful Paint)も2.5秒を良好、4秒以上を不良としています。

② スループット・同時接続数要件

システムが処理できる同時ユーザー数・秒間リクエスト数(TPS/RPS)を定義します。ピーク時のアクセス集中を見越した設計値を設定します。

指標通常時ピーク時(通常の3倍想定)根拠
同時接続ユーザー数30ユーザー100ユーザー全ユーザー数200名の50%が業務ピーク時間帯に利用
秒間リクエスト数(画面)50 req/s150 req/s
秒間リクエスト数(API)100 req/s300 req/s
1日あたりリクエスト数約50万リクエスト/日9時〜18時の業務時間帯に集中

③ 容量設計

ストレージ容量・ネットワーク帯域・DBサイズの設計値を定義します。データ項目定義書のデータ量見積と連携します。

項目初期容量5年後推定容量対策
DBデータサイズ2GB(移行分含む)約15GB3年でアーカイブ処理。DB容量は50GBを確保
DBログ・バックアップ5GB50GB週次バックアップ(12ヶ月保持)
アプリログ1GB/月累計60GB1年経過後にS3/ファイルサーバーに退避
帳票PDFストレージ0GB約3GB/年S3管理(ライフサイクルポリシーで5年後削除)
ネットワーク帯域(上り)10Mbps100Mbpsクラウド環境でオートスケール対応

④ 可用性・RTO/RPO

システムの稼働率目標と、障害発生時の復旧目標時間(RTO)・復旧目標時点(RPO)を定義します。

指標目標値根拠・備考
稼働率(SLA)99.9%以上(月間)月間43.8分以内のダウンタイムを許容
計画停止時間毎月第2日曜02:00〜04:00(2時間)月次メンテナンス。ユーザーへ事前通知必須
RTO(目標復旧時間)4時間以内業務開始前(9:00)までに復旧完了が目標
RPO(目標復旧時点)1時間以内DBバックアップ頻度:1時間ごとのWALアーカイブ
バックアップ頻度フルバックアップ:週1回 / 差分:日次WALアーカイブによるPoint-in-Time Recovery(PITR)対応

⑤ スケーラビリティ方針

ユーザー増加・データ増加に備えたスケーラビリティ方針を定義します。

  • 水平スケーリング(Webサーバー):APサーバーは最大10台までオートスケール。ロードバランサーでラウンドロビン分散
  • 垂直スケーリング(DBサーバー):DBはRead Replicaを追加して参照クエリをオフロード。5年でスケールアップを検討
  • キャッシュ戦略:マスタデータ(商品カテゴリ等)はRedisにキャッシュ(TTL: 5分)。DB負荷を軽減
  • CDN活用:静的ファイル(CSS/JS/画像)はCDN経由で配信。APサーバー負荷を低減

Python Tips — Locustで負荷テストシナリオを作る

Python — Locust負荷テストシナリオ
"""
Locustを使った負荷テストシナリオ。性能要件の数値を合否基準として検証する。
pip install locust

実行: locust -f locustfile.py --host=http://localhost:8000
     --users 30 --spawn-rate 3 --run-time 5m --headless
"""
from locust import HttpUser, task, between, events
import json
import time


class AppUser(HttpUser):
    """通常ユーザーの操作シナリオ"""
    wait_time = between(1, 3)  # 操作間隔: 1〜3秒

    def on_start(self):
        """セッション開始: ログイン"""
        resp = self.client.post("/api/v1/auth/login/", json={
            "email": "testuser@example.com",
            "password": "testpass123"
        })
        self.token = resp.json().get("access_token", "")

    @property
    def auth_headers(self):
        return {"Authorization": f"Bearer {self.token}"}

    @task(5)  # 重み: 5(最も頻繁)
    def view_product_list(self):
        """商品一覧表示(参照系 - 最頻アクセス)"""
        with self.client.get(
            "/api/v1/products/",
            headers=self.auth_headers,
            name="/api/v1/products/ [一覧]",
            catch_response=True
        ) as resp:
            if resp.elapsed.total_seconds() > 3.0:
                # 性能要件: 3秒以内
                resp.failure(f"レスポンスタイム超過: {resp.elapsed.total_seconds():.2f}s")
            elif resp.status_code != 200:
                resp.failure(f"HTTP {resp.status_code}")

    @task(2)
    def view_product_detail(self):
        """商品詳細表示"""
        product_id = 1  # テストデータのID
        with self.client.get(
            f"/api/v1/products/{product_id}/",
            headers=self.auth_headers,
            name="/api/v1/products/{id}/ [詳細]",
            catch_response=True
        ) as resp:
            if resp.elapsed.total_seconds() > 3.0:
                resp.failure(f"レスポンスタイム超過: {resp.elapsed.total_seconds():.2f}s")

    @task(1)
    def create_product(self):
        """商品登録(更新系 - 低頻度)"""
        payload = {
            "product_code": f"LOAD-{int(time.time() * 1000) % 100000:05d}",
            "product_name": "負荷テスト商品",
            "category_id": 1,
            "unit_price": 1000,
            "tax_type": 10,
        }
        with self.client.post(
            "/api/v1/products/",
            json=payload,
            headers=self.auth_headers,
            name="/api/v1/products/ [登録]",
            catch_response=True
        ) as resp:
            if resp.elapsed.total_seconds() > 5.0:
                # 性能要件: 5秒以内(更新系)
                resp.failure(f"レスポンスタイム超過: {resp.elapsed.total_seconds():.2f}s")


@events.quitting.add_listener
def on_quitting(environment, **kwargs):
    """テスト終了時に性能要件に対する合否を判定"""
    stats = environment.runner.stats.total
    print(f"\n=== 性能テスト結果 ===")
    print(f"総リクエスト数: {stats.num_requests}")
    print(f"失敗率: {stats.fail_ratio:.1%}")
    print(f"平均レスポンスタイム: {stats.avg_response_time:.0f}ms")
    print(f"95%tile レスポンスタイム: {stats.get_response_time_percentile(0.95):.0f}ms")

    # 合否判定
    if stats.avg_response_time > 1000:  # 平均1秒以内
        print("❌ 性能要件NG: 平均レスポンスタイムが1秒を超えています")
        environment.process_exit_code = 1
    elif stats.get_response_time_percentile(0.95) > 3000:  # 95%tile 3秒以内
        print("❌ 性能要件NG: 95%tileレスポンスタイムが3秒を超えています")
        environment.process_exit_code = 1
    else:
        print("✅ 性能要件OK")

定義チェックリスト

チェック項目確認ポイント
□ レスポンスタイム目標値が数値で定義されているか画面種別・API種別ごとに平均・95%tile・最大値が定義されているか
□ 同時接続数・スループットが定義されているか通常時・ピーク時のユーザー数・TPS/RPSが定義されているか
□ 容量設計が完了しているか5年後推定のDBサイズ・ストレージ・ネットワーク帯域が定義されているか
□ 可用性目標(SLA)が定義されているか稼働率・計画停止時間・RTO/RPOが数値で定義されているか
□ スケーラビリティ方針が定義されているか水平/垂直スケーリング・キャッシュ・CDN活用方針が定義されているか
□ 性能テスト計画に連動しているか性能要件の数値が性能テストの合否基準として使用されることを確認