結論

Locust(ローカスト)は、「仮想ユーザーが何をするか」を Python のクラスで書き、何人分でも同時に動かして Web サーバーや API の負荷テストをするオープンソースのツールです。pip install locust で入り、locust --headless(画面なし)と locust(ブラウザの画面)の2通りで動かせます。

この記事でわかること

  • locustfile の書き方(HttpUser・@task・wait_time・on_start)と、headless・Web UI で実行した実物
  • RPS・p50/p95/p99・失敗率の読み方と、1〜100ユーザーで限界を探した実測の表
  • Connection refused・Read timed out・404/500 の原文、分散実行・CSV・CI での合否判定、k6・JMeter との判断表

2026年10月29日更新|Locust 2.47.0・Python 3.13・FastAPI 0.162・Chromium 153 で実行確認

Locust は「locustfile(Python)に書いた行動」を、指定した人数ぶん同時に繰り返し、結果を集計して表・グラフ・CSV にします。全体の流れは次のとおりです。

flowchart TD
    F[locustfile.py<br>HttpUser と @task] --> L[locust コマンド<br>-u 人数 -r 速さ -t 時間]
    L --> U[仮想ユーザー 1〜N<br>タスクを繰り返す]
    U --> S[(テスト対象の<br>サーバー)]
    S --> U
    U --> R[集計<br>表・Web UI・CSV]

よく調べられることの答えを先にまとめます。

知りたいこと 答え 詳しい節
Locust とは Python でシナリオを書く負荷テストツール(MIT ライセンス) Locustとは
locust 使い方(最短) pip install locust → locustfile.py を書く → locust --headless -u 10 -r 2 -t 30s インストール/headless
locustfile の書き方 HttpUser を継承し、@task のメソッドで self.client.get() を呼ぶ locustfileの書き方
Web UI の開き方 locust で起動し、ブラウザで http://localhost:8089 Web UIで実行
結果の見方 平均ではなく p95・p99 と失敗率を見る 結果の読み方
RPS が上がらない 対象サーバーの限界か、Locust 側の CPU(1プロセス≒1コア) 限界を探す/分散実行
Connection refused が出る 対象サーバーが起動していない・ホスト名やポートが違う 失敗の原文
k6・JMeter との違い Python で書くなら Locust、JS なら k6、GUI なら JMeter 判断表

確認環境:Ubuntu 24.04.5 LTS(2コア・メモリ 7GB)の上で、Python 3.13.18 の venv に Locust 2.47.0(gevent 26.9.0・geventhttpclient 2.6.1・requests 2.34.2)を pip で入れました。テスト対象は FastAPI 0.162.0 を uvicorn 0.54.0(1プロセス)で 127.0.0.1:8000 に起動した自作 API で、負荷はこの自作サーバーにだけ向けています。headless 実行・分散実行(--processes と master/worker)・CSV・HTML レポート・locust.conf はこの環境で実行し、Web UI は Chromium 153.0.8010.12(playwright 1.63.0)で開いて画面の文言と数値を読み取りました。Locust の仕様(オプション・FastHttpUser・終了コード)は公式ドキュメント(docs.locust.io の stable 版)と PyPI で確かめています。Locust と対象サーバーが同じ 2コアの機械で動いているため、RPS の絶対値はこの環境の値で、対象サーバーの性能そのものではありません(確認日 2026年10月29日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Locustとは・使い方|Pythonで書く負荷テストを実行結果と失敗の原文で解説」

Locustとは(Pythonで書く負荷テストツール)

この節では、Locust が何をするツールで、ほかの負荷テストツールと何が違うのかが分かります。

Locust は、HTTP などのサーバーに「たくさんの利用者が同時に使っている状態」を作り、応答時間と失敗率を測るための負荷テストツールです。 PyPI の説明では「HTTP やほかのプロトコルのための、オープンソースの性能/負荷テストツール」で、「テストを普通の Python コードで定義できる」のが特徴とされています。名前の locust は「群れで飛ぶバッタ」から来ていて、仮想ユーザーの群れがサーバーに押し寄せる様子をたとえたものです(公式ドキュメント「What is Locust?」)。

1シナリオを Python で書く

XML や GUI の設定ではなく、クラスとメソッドで「ログインして一覧を見て詳細を開く」を書く。if や for、ランダムな値、外部ライブラリがそのまま使える。

21プロセスで数千ユーザー

gevent(軽量スレッド)で動くため、1プロセスでも数千の仮想ユーザーを持てる。足りなければ複数プロセス・複数台に分散できる。

3画面ありと画面なしの両方

ブラウザの Web UI でグラフを見ながら人数を変えられる。CI では --headless で画面なしに動かし、終了コードで合否を返せる。

項目 内容(2026年10月29日時点)
最新版 2.47.0(2026年10月21日公開)
対応する Python 3.11 以上(PyPI の Requires: Python)
ライセンス MIT License
HTTP クライアント HttpUser(requests)と FastHttpUser(geventhttpclient)
対象 主に Web サイト・HTTP API。クライアントを自作すればほかのプロトコルも

負荷テストそのものの進め方(スモーク→負荷→ストレス→耐久)と k6 での書き方は、関連記事「負荷テストの進め方とk6の使い方|スクリプト例と結果の読み方」で扱っています。この記事は「Locust というツールの使い方と、出力の読み方」に絞ります。

Locustのインストール(pip・venv)と動作確認

この節では、Locust を入れて locust -V が通るまでの手順と、入る依存パッケージが分かります。

仮想環境を作って pip install locust を実行するだけです。 手元では venv の大きさが 71MB(FastAPI・uvicorn を含む)になりました。

1

仮想環境を作る

python3 -m venv venv → source venv/bin/activate(Windows は venv\Scripts\activate)。Python は 3.11 以上。

1分

2

Locust を入れる

pip install locust。gevent・geventhttpclient・requests・Flask(Web UI 用)・pyzmq(分散実行用)などが一緒に入る。

数十秒〜

3

版を確かめる

locust -V。版の数字と Python の版が出れば完了。

数秒

4

テスト対象を用意する

自分が管理するサーバーを 127.0.0.1 に起動する。この記事では FastAPI の自作 API を uvicorn で 8000 番に起動した。

対象が必要

$ python3 -m venv venv
$ source venv/bin/activate
$ pip install locust
$ locust -V
locust 2.47.0 from /dev/shm/t115/venv/lib/python3.13/site-packages/locust (Python 3.13.18)
$ pip list | grep -iE "^(locust|gevent|geventhttpclient|requests)"
gevent             26.9.0
geventhttpclient   2.6.1
locust             2.47.0
requests           2.34.2

venv の作り方と有効化の違い(OS 別)は、関連記事「Python venvの使い方|仮想環境の作成・有効化・削除をOS別にコピペ」で扱っています。

テスト対象にした自作 API

負荷をかける先として、FastAPI で次の API を作り、uvicorn app:app --host 127.0.0.1 --port 8000 で起動しました。/slow はわざと 100〜300ms 待ち、/flaky は 10% の確率で 500 を返します。

# app.py(抜粋)
import asyncio, random
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()
ITEMS = {i: {"id": i, "name": f"item-{i}", "price": i * 100} for i in range(1, 101)}

class Login(BaseModel):
    username: str
    password: str

@app.post("/login")
async def login(body: Login):
    if body.username == "demo" and body.password == "demo":
        return {"token": "demo-token"}
    raise HTTPException(status_code=401, detail="invalid credentials")

@app.get("/items")
async def list_items():
    return list(ITEMS.values())

@app.get("/items/{item_id}")
async def get_item(item_id: int):
    if item_id not in ITEMS:
        raise HTTPException(status_code=404, detail="item not found")
    return ITEMS[item_id]

@app.get("/slow")
async def slow():
    await asyncio.sleep(random.uniform(0.1, 0.3))
    return {"message": "slow done"}

@app.get("/flaky")
async def flaky():
    if random.random() < 0.1:
        raise HTTPException(status_code=500, detail="unlucky")
    return {"message": "ok"}

FastAPI の作り方そのものは、関連記事「FastAPI入門|uvで作るCRUD API完成コードと実行結果」で扱っています。

locustfileの書き方(HttpUser・@task・wait_time・on_start)

この節では、Locust のシナリオファイル(locustfile)の部品と、それぞれが何を決めるのかが分かります。

locustfile は「1人の仮想ユーザーの行動」をクラスで書いたものです。 HttpUser を継承したクラスを作り、@task を付けたメソッドが「その人がする操作」、wait_time が「操作と操作の間の待ち時間」、on_start が「その人が最初に1回だけすること」です。この記事で使った locustfile は次のとおりです。

# locustfile.py
import random
from locust import HttpUser, task, between


class ShopUser(HttpUser):
    # 各タスクの間に 1〜3 秒待つ(人の操作の間隔のつもり)
    wait_time = between(1, 3)
    host = "http://127.0.0.1:8000"

    def on_start(self):
        # ユーザーが生まれたときに1回だけログインしてトークンを持つ
        res = self.client.post("/login", json={"username": "demo", "password": "demo"})
        self.token = res.json()["token"]
        self.client.headers["Authorization"] = f"Bearer {self.token}"

    @task(5)
    def list_items(self):
        self.client.get("/items")

    @task(3)
    def view_item(self):
        item_id = random.randint(1, 100)
        # URL が毎回変わるので、name で1つの行にまとめる
        self.client.get(f"/items/{item_id}", name="/items/[id]")

    @task(1)
    def create_item(self):
        self.client.post("/items", json={"name": "new", "price": 500})

    @task(1)
    def slow_page(self):
        # 応答が 500ms を超えたら失敗として数える
        with self.client.get("/slow", catch_response=True) as res:
            if res.elapsed.total_seconds() > 0.5:
                res.failure(f"too slow: {res.elapsed.total_seconds():.3f}s")
部品 役割 上の例での値
HttpUser HTTP を話す仮想ユーザーの土台。self.client(requests.Session の包み)を持ち、Cookie を保つ ShopUser
host リクエストの前に付く URL。--host や Web UI の Host 欄で上書きできる http://127.0.0.1:8000
wait_time タスクとタスクの間の待ち時間 between(1, 3)
@task(重み) その人がする操作。重みが大きいほど選ばれやすい 5:3:1:1
on_start/on_stop ユーザーの開始時・終了時に1回だけ実行 ログイン
name= URL が変わるリクエストを1つの統計行にまとめる /items/[id]
catch_response=True 自分で成功・失敗を決める(res.failure()/res.success()) 500ms 超を失敗に

重みは「タスクが選ばれる割合」です。Web UI の「Current ratio」タブでは、上の locustfile が listItems 50.0%・viewItem 30.0%・createItem 10.0%・slowPage 10.0% と表示されました(5:3:1:1 を百分率にしたものと一致)。

wait_time の4種類

待ち時間は「人の操作の間隔を真似る」ためのものです。公式ドキュメントの4種類を、使いどころと一緒にまとめます。

1

between(a, b)

a〜b 秒のランダム。人がページを読む間隔に近い。

いちばんよく使う

2

constant(s)

毎回 s 秒。constant(0) で「応答が返ったら即次」。限界を探すときに。

この記事の RPS 測定

3

constant_throughput(n)

1ユーザーが毎秒 n 回まで実行するよう待ちを調整。人数×n が目標 RPS。

RPS を決めたいとき

4

constant_pacing(s)

タスク開始から次の開始までを s 秒に揃える。応答が遅くても間隔を保つ。

周期的なポーリング

「成功」の既定は「ステータスコードが 400 未満」です(公式ドキュメント「Requests are considered successful if the HTTP response code is OK (<400)」)。200 が返っても内容が違う・遅すぎる、を失敗にしたいときは catch_response=True で自分で決めます。

headlessで実行する(locust --headless の出力の読み方)

この節では、画面なし(headless)で Locust を動かすコマンドと、流れてくる表の読み方が分かります。

locust -f locustfile.py --headless -u 20 -r 5 -t 30s で、20ユーザーを毎秒5人ずつ増やし、30秒で止まります。 手元の実行の先頭と末尾は次のとおりです(表の幅は詰めています)。

$ locust -f locustfile.py --headless -u 20 -r 5 -t 30s --csv result/run1 --html result/run1.html
[2026-10-30 20:12:06,934] vm/INFO/locust.main: Starting Locust 2.47.0
[2026-10-30 20:12:06,935] vm/INFO/locust.main: Run time limit set to 30 seconds
[2026-10-30 20:12:06,950] vm/INFO/locust.runners: Ramping to 20 users at a rate of 5.00 per second
[2026-10-30 20:12:09,956] vm/INFO/locust.runners: All users spawned: {"ShopUser": 20} (20 total users)
Type     Name            # reqs   # fails |  Avg  Min  Max  Med | req/s failures/s
--------|---------------|--------|---------|-----|----|-----|-----|------|----------
GET      /items             17   0(0.00%) |    7    2   43    3 |  1.50     0.00
POST     /items              6   0(0.00%) |   19    2   43   10 |  1.50     0.00
GET      /items/[id]         8   0(0.00%) |   11    2   42    8 |  1.00     0.00
POST     /login             20   0(0.00%) |   13    4   99    9 |  5.00     0.00
GET      /slow               2   0(0.00%) |  150  115  185  185 |  0.00     0.00
--------|---------------|--------|---------|-----|----|-----|-----|------|----------
         Aggregated         53   0(0.00%) |   17    2  185    8 |  9.00     0.00
(2秒ごとに同じ表が流れる)
[2026-10-30 20:12:36,935] vm/INFO/locust.main: --run-time limit reached, shutting down
[2026-10-30 20:12:37,067] vm/INFO/locust.main: Shutting down (exit code 0)
Type     Name            # reqs   # fails |  Avg  Min  Max  Med | req/s failures/s
--------|---------------|--------|---------|-----|----|-----|-----|------|----------
GET      /items            151   0(0.00%) |    4    2   43    3 |  5.10     0.00
POST     /items             27   0(0.00%) |    6    1   43    2 |  0.91     0.00
GET      /items/[id]        80   0(0.00%) |    3    1   42    2 |  2.70     0.00
POST     /login             20   0(0.00%) |   13    4   99    9 |  0.68     0.00
GET      /slow              31   0(0.00%) |  199  104  302  200 |  1.05     0.00
--------|---------------|--------|---------|-----|----|-----|-----|------|----------
         Aggregated        309   0(0.00%) |   24    1  302    3 | 10.43     0.00

Response time percentiles (approximated)
Type     Name           50%  66%  75%  80%  90%  95%  98%  99%  99.9% 100% # reqs
--------|--------------|----|----|----|----|----|----|----|----|------|----|------
GET      /items           3    3    4    4    6    8   15   40    44   44    151
POST     /login           9   11   12   13   14  100  100  100   100  100     20
GET      /slow          200  230  260  260  280  290  300  300   300  300     31
         Aggregated       3    4    6    8  100  200  260  280   300  300    309
列 意味 読み方
# reqs/# fails リクエスト数と失敗数(割合) 失敗が 0 でない行から見る
Avg/Min/Max/Med 応答時間の平均・最小・最大・中央値(ms) Avg と Med が離れていたら、遅い応答が混ざっている
req/s 直近の秒間リクエスト数(RPS) ユーザーを増やしても伸びなくなった点が限界
percentiles の 95%・99% 95%・99% の応答がこの値以下(ms) 合否はここで決める(平均では決めない)
exit code 終了コード。失敗が1件でもあると 1 CI の合否に使える(--exit-code-on-error で変更可)

POST /login が 20 回ちょうどなのは、on_start が「ユーザー1人につき1回」だからです。/slow の中央値 200ms はサーバーの sleep(0.1〜0.3) のとおりで、Locust 自身が遅延を足していないことも分かります。

オプション 意味
-f/--locustfile locustfile のパス。省略すると ./locustfile.py
-H/--host 対象の URL。クラスの host より優先
-u/--users 最大ユーザー数
-r/--spawn-rate 毎秒何人ずつ増やすか
-t/--run-time 実行時間(30s・5m・1h30m)。開始時点から数える
--only-summary 2秒ごとの表を出さず、最後だけ出す
-s/--stop-timeout 停止時に実行中のタスクを待つ秒数
--csv/--html CSV と HTML レポートの出力先

Web UI(ブラウザ)で実行する:画面の見方と操作

この節では、Locust の Web UI を起動して、画面で人数を決めて開始・停止するまでと、各タブに何があるかが分かります。

locust -f locustfile.py と打つと http://localhost:8089 に画面が立ち上がり、ブラウザから人数・増やす速さ・対象 URL を入れて START を押します。 手元では --web-host 127.0.0.1 を付け、自分の機械からだけ開けるようにしました(既定は全てのアドレスで待ち受けます)。

$ locust -f locustfile.py --web-host 127.0.0.1
[2026-10-30 20:18:35,181] vm/INFO/locust.main: Starting Locust 2.47.0
[2026-10-30 20:18:35,182] vm/INFO/locust.main: Starting web interface at http://127.0.0.1:8089, press enter to open your default browser.

Chromium で開いた最初の画面(Start new load test)には、次の欄がありました。

画面の欄 意味 コマンドの対応
Number of users (peak concurrency) 最大ユーザー数(既定 1) -u
Ramp up (users started/second) 毎秒増やす人数(既定 1)。以前の版の Spawn rate・Hatch rate -r
Host 対象の URL。locustfile の host が入っていた -H
Advanced options → Run time 実行時間(20、20s、3m、1h20m など) -t
Advanced options → Profile 実行に付けるラベル(任意) --profile

20ユーザー・毎秒5人・30秒で START を押すと、上部のヘッダーが Status RUNNING/Users 20/RPS 10.75/Failures 0% に変わり、EDIT(人数の変更)・STOP・RESET のボタンが出ました。タブは STATISTICS・CHARTS・FAILURES・EXCEPTIONS・CURRENT RATIO・DOWNLOAD DATA・LOGS の7つです。

タブ 見えるもの(手元の実物)
STATISTICS 行ごとに # Requests・# Fails・Median・95%ile・99%ile・Average・Min・Max・Average size・Current RPS・Current Failures/s。/items は 51 件・中央値 4ms・95%ile 14ms
CHARTS 3つの折れ線:Total Requests per Second(RPS と Failures/s)、Response Times(50th と 95th percentile)、Number of Users。人数が 0→20 に上がる線と、RPS が 10 前後で平らになる線が見えた
FAILURES # Failures・Method・Name・Message・First Seen・Last Seen。失敗 0 件なので表は空
EXCEPTIONS locustfile の中で起きた Python の例外(# occurrences・Message・Traceback)
CURRENT RATIO クラスとタスクの割合。ShopUser 100%、listItems 50.0%・viewItem 30.0%・createItem 10.0%・slowPage 10.0%
DOWNLOAD DATA Download requests CSV・failures CSV・exceptions CSV・Report(HTML)の4つのリンク
LOGS Master Logs。「Ramping to 20 users at a rate of 5.00 per second」などコンソールと同じ行

STOP を押すと Status が STOPPED になり、ボタンが NEW・RESET に変わります。止めた時点の STATISTICS は、/items が 149 件・中央値 3ms・95%ile 11ms・99%ile 24ms、/slow が 26 件・中央値 180ms・95%ile 280ms で、headless で同じ条件を走らせた結果とほぼ同じでした。

Web UI が向く場面

初めてのシナリオで、動き方をグラフで見たい

走らせながら人数を変えて限界を探したい(EDIT)

失敗の文言をその場で見て locustfile を直したい

headless が向く場面

CI やスケジュールで毎回同じ条件で回す

結果を CSV・終了コードで機械的に扱う

サーバーにブラウザが無い・ポート 8089 を開けたくない

Web UI は /stats/requests という JSON の API も持っていて、手元で curl http://127.0.0.1:8089/stats/requests を叩くと state: running, user_count: 20, total_rps: 10.54 と各行の統計が返りました。自作の監視に取り込むときに使えます。

結果の読み方:RPS・p50/p95/p99・失敗率(負荷テスト 結果の見方)

この節では、Locust が出す数字のうち、何を見て「速い・遅い・合格・不合格」を決めるのかが分かります。

見る順番は「失敗率 → p95/p99 → RPS → 平均」です。 平均は少数の遅い応答に引っ張られ、逆に多数の速い応答に隠されるため、合否の基準には使いません。

  1. 1失敗率(# fails・Failures/s)1件でもあれば原因を見る。失敗した応答の時間は統計に混ざらないことがある
  2. 2p95・p99(95%ile・99%ile)「20人に1人」「100人に1人」が体験する遅さ。合否はここ
  3. 3RPS(req/s)とユーザー数の関係人数を増やしても RPS が伸びなくなった点が、その構成の限界
  4. 4中央値(p50)と平均(Avg)の差差が大きいほど分布が偏っている。Max だけ大きいのは最初の接続や GC のことが多い
  5. 5対象サーバー側の CPU・メモリ・ログLocust の数字だけでは「どこが」遅いかは分からない

上の headless 実行の Aggregated 行を見ると、中央値 3ms・平均 24ms・p95 200ms と大きくずれています。これは速い /items と遅い /slow が混ざっているためで、エンドポイント別の行で見ないと意味が取れない例です。Locust の percentile は「approximated(近似)」と書かれているとおり、応答時間を丸めて数えているので、1ms 単位の差を議論する用途には向きません。

p99 がベンチマークで持つ意味や、ウォームアップ・外れ値の扱いは、関連記事「ベンチマークの落とし穴8選|JMH・p99・ウォームアップ」で扱っています。

ユーザー数を増やして限界を探す(1〜100ユーザーの実測)

この節では、同じ API にユーザー数だけ変えて負荷をかけ、「RPS が伸びなくなる点」と「応答時間が伸び始める点」がどう見えるかが分かります。

待ち時間なし(wait_time = constant(0))で /items/7 を叩き続け、1・10・50・100 ユーザーで各20秒ずつ測りました。 50 ユーザーから 100 ユーザーに倍にしても RPS はほぼ同じで、代わりに応答時間が倍になっています。これが「限界に達した」形です。

# locust_rps.py
from locust import HttpUser, task, constant

class HammerUser(HttpUser):
    wait_time = constant(0)   # 応答が返ったらすぐ次を送る
    host = "http://127.0.0.1:8000"

    @task
    def item(self):
        self.client.get("/items/7", name="/items/[id]")
$ for u in 1 10 50 100; do
    locust -f locust_rps.py --headless -u $u -r 50 -t 20s --only-summary --csv result/rps_$u
  done
ユーザー数 RPS p50(ms) p95(ms) p99(ms)
1 575.8 1 4 6
10 628.3 9 28 65
50 1,025.6 31 66 110
100 1,084.1 59 110 160
1,084req/sこの構成の天井uvicorn 1プロセス+Locust 1プロセス、2コア共用
×1.9倍50→100人で p50 が伸びた倍率31ms → 59ms。RPS は 1.06 倍しか増えない
0件失敗100人でも 500 や接続エラーは出なかった

公式ドキュメントのクイックスタートも同じ見方を説明しています。「ユーザーを増やしても RPS が上がらず応答時間だけ上がる」なら対象が飽和しており、「応答時間が平らなまま」なら、まだ人数を増やせる、という読み方です。ただし伸びない原因が Locust 側のこともあります。Python は1プロセスで1コアしか使えないため、Locust が CPU を使い切ると「CPU usage above 90%」の警告をログに出します(公式ドキュメント)。手元の実測ではこの警告は出ず、対象サーバー(uvicorn 1プロセス)が先に飽和していました。

「限界が分かったあと、サーバーを増やすか大きくするか」の考え方は、関連記事「スケールアップとスケールアウトの違い|負荷分散方式の一覧と選び方」で扱っています。

失敗の原文と原因(Connection refused・Read timed out・404・500)

この節では、Locust の Error report に出る文言を実際に起こして、それぞれ何が起きているのかが分かります。

失敗は Error report(headless の末尾)・FAILURES タブ・_failures.csv の3か所に同じ文言で出ます。 次の locustfile で、サーバーを止めた状態と、起動した状態の両方を走らせました。

# locust_fail.py
from locust import HttpUser, task, constant

class FailUser(HttpUser):
    wait_time = constant(0.5)
    host = "http://127.0.0.1:8000"

    @task
    def flaky(self):
        self.client.get("/flaky")            # 10% の確率で 500

    @task
    def not_found(self):
        self.client.get("/items/999", name="/items/[missing]")   # 404

    @task
    def timeout(self):
        self.client.get("/verslow", timeout=1)   # サーバーは 2 秒かかる

サーバーを止めたとき:ConnectionRefusedError

$ locust -f locust_fail.py --headless -u 2 -r 2 -t 5s --only-summary
         Aggregated          20  20(100.00%) |    1    0    5    2 |  4.40   4.40
Error report
# occurrences      Error
------------------|------------------------------------------------------------------
9                  GET /verslow: ConnectionRefusedError(111, 'Connection refused')
4                  GET /flaky: ConnectionRefusedError(111, 'Connection refused')
7                  GET /items/[missing]: ConnectionRefusedError(111, 'Connection refused')
[2026-10-30 20:16:39,147] vm/INFO/locust.main: Shutting down (exit code 1)

サーバーを起動したとき:ReadTimeout・404・500

Error report
# occurrences      Error
------------------|------------------------------------------------------------------
11                 GET /items/[missing]: HTTPError('404 Client Error: Not Found for url: /items/[missing]')
9                  GET /verslow: ReadTimeout(ReadTimeoutError("HTTPConnectionPool(host='127.0.0.1', port=8000): Read timed out. (read timeout=1)"))
1                  GET /flaky: HTTPError('500 Server Error: Internal Server Error for url: /flaky')
原文 起きていること 見直す場所
ConnectionRefusedError(111, 'Connection refused') そのホスト・ポートで誰も待ち受けていない 対象サーバーの起動、host の綴り・ポート、127.0.0.1 と外向きアドレスの違い
ReadTimeout(... Read timed out. (read timeout=1)) 接続はできたが、timeout 秒以内に応答が返らない timeout の値、対象の処理時間、負荷で詰まっていないか
HTTPError('404 Client Error: Not Found ...') URL が存在しない パスの綴り、name= で隠れている実際の URL
HTTPError('500 Server Error: Internal Server Error ...') 対象サーバーの内部エラー 対象サーバーのログ(Locust 側には理由は出ない)
GET /slow: too slow: 0.282s res.failure() で自分が付けた文言 基準値と、対象の設計

Error report の「GET /verslow」には name= で付けた名前が出るため、404 のときは「本当の URL が何だったか」が分かりません。手元でも、サーバーを前の版のまま起動していたせいで /verslow が 404 になり、最初は原因に気づきませんでした。name で丸めた URL の失敗は、locustfile 側で実際の URL をログに出すと追いやすくなります。

接続が拒否されるときの TCP の仕組みと切り分けは、既存の「Connection refused」の記事に詳しく、503・502 を返すサーバー側の事情は既存の「503 Service Unavailable」「502 Bad Gateway」の記事に譲ります。

分散実行(--processes と master/worker)とFastHttpUser

この節では、Locust 1プロセスで足りないときに、同じ機械で複数プロセスに分ける方法と、クライアントを軽いものに替える方法が分かります。

Locust は master(集計と画面)と worker(実際にリクエストを送る)に分かれ、同じ機械でも別の機械でも動きます。 同じ機械なら --processes N の1行で master と N 個の worker が立ちます。公式ドキュメントは「1コアにつき worker 1つ」を勧めています(--processes -1 でコア数を自動検出)。

--processes で同じ機械に2プロセス

$ locust -f locust_rps.py --headless -u 100 -r 50 -t 20s --processes 2 --only-summary
[2026-10-30 20:15:56,649] vm/INFO/locust.runners: vm_b40a07c1... (index 0) reported as ready. 1 workers connected.
[2026-10-30 20:15:56,652] vm/INFO/locust.runners: vm_198da546... (index 1) reported as ready. 2 workers connected.
[2026-10-30 20:15:57,562] vm/INFO/locust.runners: Sending spawn jobs of 100 users at 50.00 spawn rate to 2 ready workers
...
[2026-10-30 20:16:17,720] vm/INFO/locust.runners: The last worker quit, stopping test.
         Aggregated       28970   0(0.00%) |   50    7  436   43 | 1440.11     0.00

master と worker を別々に起動(別の機械と同じ手順)

# 端末1:master(画面なし・worker 2つを待ってから開始)
$ locust -f locust_rps.py --master --master-bind-host 127.0.0.1 --headless -u 100 -r 50 -t 15s --expect-workers 2
# 端末2・3:worker(別の機械なら --master-host に master のアドレス)
$ locust -f locust_rps.py --worker --master-host 127.0.0.1
$ locust -f locust_rps.py --worker --master-host 127.0.0.1

# master のログ
[2026-10-30 20:20:14,310] vm/INFO/locust.runners: vm_6fc3e286... (index 0) reported as ready. 1 workers connected.
[2026-10-30 20:20:14,364] vm/INFO/locust.runners: vm_eee61cbb... (index 1) reported as ready. 2 workers connected.
[2026-10-30 20:20:15,334] vm/INFO/locust.runners: Sending spawn jobs of 100 users at 50.00 spawn rate to 2 ready workers
[2026-10-30 20:20:16,476] vm/INFO/locust.runners: All users spawned: {"HammerUser": 100} (100 total users)
         Aggregated       21069   0(0.00%) |   50    4  347   43 | 1374.20     0.00
# worker のログ
[2026-10-30 20:20:30,372] vm/INFO/locust.runners: Got quit message from master, shutting down...

FastHttpUser(requests の代わりに geventhttpclient)

HttpUser を FastHttpUser に替えるだけで、Locust 側の CPU の使い方が軽くなります。公式ドキュメントは「1コアで HttpUser 約4,000 req/s に対し FastHttpUser 約16,000 req/s」の例を挙げつつ、「個々のリクエストが速くなるわけではない」と注意しています。

from locust import task, constant
from locust.contrib.fasthttp import FastHttpUser

class FastHammerUser(FastHttpUser):
    wait_time = constant(0)
    host = "http://127.0.0.1:8000"

    @task
    def item(self):
        self.client.get("/items/7", name="/items/[id]")
構成(100ユーザー・20秒) RPS p50(ms) p95(ms) p99(ms)
HttpUser・1プロセス 1,084.1 59 110 160
HttpUser・--processes 2 1,440.1 43 100 150
HttpUser・master+worker 2(15秒) 1,374.2 43 120 200
FastHttpUser・1プロセス 1,854.8 47 87 170

この環境では Locust と対象サーバーが同じ2コアを取り合っているため、プロセスを増やしても対象サーバーの取り分が減り、伸びは 1.3 倍ほどに留まりました。本来は負荷をかける側と受ける側を別の機械に分け、Locust 側の CPU が足りているかを先に確かめます。 それでも、クライアントを軽くした FastHttpUser が最も RPS を出したのは、Locust 側の CPU がボトルネックの一部だったことを示しています。

  • --processes は fork() を使うため Windows では使えません(公式ドキュメント)。Windows では master と worker を別々に起動します。
  • worker 側の -f - で locustfile を master から受け取れますが、1ファイルのときだけです。
  • FastHttpUser は「ほぼ差し替え可能」ですが、Cookie を手で扱うなど requests の内部に触れているコードでは違いが出ることがあります。

CSV・HTMLレポート・locust.conf・CIでの合否判定

この節では、結果をファイルに残す方法と、毎回同じ条件で走らせる設定ファイル、CI で合否を終了コードにする方法が分かります。

--csv result/run1 で4つの CSV、--html result/run1.html で1ファイルの HTML レポートが出ます。 手元の出力は次のとおりでした。

$ ls -la result/
-rw-r--r-- 1 root root 954400 Oct 30 20:12 run1.html            # グラフ入りの単体 HTML
-rw-r--r-- 1 root root     31 Oct 30 20:12 run1_exceptions.csv  # locustfile の例外
-rw-r--r-- 1 root root     52 Oct 30 20:12 run1_failures.csv    # 失敗の文言と回数
-rw-r--r-- 1 root root   1065 Oct 30 20:12 run1_stats.csv       # 行ごとの集計(最終)
-rw-r--r-- 1 root root   5122 Oct 30 20:12 run1_stats_history.csv  # 1秒ごとの推移

$ head -3 result/run1_stats.csv
Type,Name,Request Count,Failure Count,Median Response Time,Average Response Time,Min Response Time,Max Response Time,Average Content Size,Requests/s,Failures/s,50%,66%,75%,80%,90%,95%,98%,99%,99.9%,99.99%,100%
GET,/items,143,0,3,4.018183930068094,2.0376530000021376,43.720411999970565,4508.188811188811,4.944207919612717,0.0,3,3,3,4,6,7,15,40,44,44,44
POST,/items,25,0,3,6.692363880003995,1.7286199999944074,43.728340999962256,35.0,0.8643720139183071,0.0,3,3,5,6,10,43,44,44,44,44,44

$ head -3 result/run1_stats_history.csv
Timestamp,User Count,Type,Name,Requests/s,Failures/s,50%,66%,75%,80%,90%,95%,98%,99%,99.9%,99.99%,100%,Total Request Count,...
1793358726,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,...
1793358727,5,,Aggregated,0.000000,0.000000,42,43,44,44,100,100,100,...

_stats_history.csv は既定で Aggregated 行だけが1秒ごとに記録されます。エンドポイント別の推移が要るときは --csv-full-history を足します。Timestamp は UNIX 時刻(秒)です。

locust.conf で条件を固定する

オプションは locust.conf(または pyproject.toml の [tool.locust])に書けます。キーは長いオプション名と同じです。

# locust.conf
locustfile = locustfile.py
host = http://127.0.0.1:8000
headless = true
users = 10
spawn-rate = 2
run-time = 10s
csv = result/conf
html = result/conf_{u}u_{r}r_{t}.html
only-summary = true

$ locust --config locust.conf
[2026-10-30 20:20:47,786] vm/INFO/locust.main: Run time limit set to 10 seconds
[2026-10-30 20:20:47,787] vm/INFO/locust.runners: Ramping to 10 users at a rate of 2.00 per second
$ ls result/ | grep conf
conf_10u_2r_10.html      # {u}{r}{t} が人数・速さ・時間に置き換わる
conf_stats.csv ...

優先順位は「pyproject.toml → ./locust.conf → --config のファイル → 環境変数(LOCUST_USERS など) → コマンドライン」で、後のものが勝ちます。カレントディレクトリの locust.conf は指定しなくても読まれます。手元でも、別の locustfile を -f で指定した実行に locust.conf の html = が効いて、意図しない HTML が増えました。

CI で合否を終了コードにする

既定では「失敗が1件でもあれば終了コード 1」です。「失敗率 1% 以内かつ p95 が 50ms 以内」のような基準で判定したいときは、quitting イベントで process_exit_code を決めます(公式ドキュメントの例を元にした手元のコード)。

# locust_ci.py(抜粋)
from locust import events

@events.quitting.add_listener
def check_thresholds(environment, **kwargs):
    stats = environment.runner.stats.total
    if stats.fail_ratio > 0.01:
        print("NG: failure ratio over 1%")
        environment.process_exit_code = 1
    elif stats.get_response_time_percentile(0.95) > 50:
        print("NG: 95th percentile over 50 ms")
        environment.process_exit_code = 1
    else:
        print("OK: thresholds met")
        environment.process_exit_code = 0
$ locust -f locust_ci.py --headless -u 100 -r 100 -t 10s --only-summary; echo "exit=$?"
NG: 95th percentile over 50 ms
[2026-10-30 20:22:00,238] vm/INFO/locust.main: Shutting down (exit code 1)
         Aggregated       16026   0(0.00%) |   45   19  187   41 | 1590.25     0.00
exit=1

失敗 0 件でも p95 が 82ms だったため終了コード 1 になり、CI ならこのジョブは落ちます。性能要件(p95 を何 ms にするか)の決め方と合格基準の書き方は、関連記事「性能テストの進め方|性能要件の決め方と合格基準のテンプレート」で扱っています。

よくあるエラー・つまずき(原文と直し方)

この節では、Locust を動かし始めたときに出やすいメッセージと、その直し方が分かります。

次の表は、すべてこの環境で実際に出した原文です。

原文 原因 直し方
Could not find 'locustfile.py'. Ensure your locustfile ends with '.py' or is a directory with locustfiles. カレントディレクトリに locustfile.py が無い -f ファイル名 を付けるか、そのディレクトリで実行する
locust.exception.StopTest: You must specify the base host. Either in the host attribute in the User class, or on the command line using the --host option. 対象 URL をどこにも書いていない クラスに host = を書くか -H を付ける。headless だとこの原文のあと止まる
ERROR/locust.user.task: No tasks defined on U. Use the @task decorator or set the 'tasks' attribute of the User (or mark it as abstract = True if you only intend to subclass it) @task を付け忘れた メソッドに @task を付ける。土台にするだけのクラスは abstract = True
OSError: [Errno 98] Address already in use: ('127.0.0.1', 8089) 前の Locust が 8089 番を使ったまま 前のプロセスを止めるか、--web-port 8090 で別のポートにする。終了コードは 2
ConnectionRefusedError(111, 'Connection refused') 対象が起動していない・ポート違い 先に curl で対象に届くか確かめる
ValueError: I/O operation on closed file.(stats_writer の Traceback、終了時) 終了のタイミングで CSV 書き込みの greenlet が閉じたファイルに触った 手元では 10 回超の実行のうち1回だけ出て、終了コードは 0・CSV も揃っていた。繰り返すなら --stop-timeout を付けて再現を確かめる

平均だけで判断する

やってはいけないAggregated の Avg が 24ms だから速い、と結論づける。手元では p95 が 200ms だった
→
代わりにエンドポイント別の行で p95・p99 と失敗率を見る。合否基準もそこで決める

Avg と Med(中央値)が離れていたら分布が偏っている合図

Locust 側の限界を対象の限界と取り違える

やってはいけないRPS が伸びない=サーバーの限界、と決める
→
代わりにLocust のログに CPU の警告が無いか見る。FastHttpUser・--processes・別の機械で負荷側を増やして同じ天井か確かめる

手元では FastHttpUser に替えるだけで RPS が 1.7 倍になった

Locust・k6・JMeter・ab・wrk の判断表(どれを使うか)

この節では、代表的な負荷テストツールの違いと、場面ごとにどれを選ぶかが分かります。

「シナリオをどの言語で書きたいか」と「GUI が要るか」で決まります。 事実関係は各ツールの公式サイトで確かめた範囲です。

ツール シナリオの書き方 画面 向く場面
Locust Python のクラス Web UI(ブラウザ)と headless ログイン→操作のような流れを Python で書きたい。Python のチーム
k6(Grafana Labs) JavaScript/TypeScript headless 中心(結果は Grafana などへ) JS のチーム。しきい値(thresholds)を宣言的に書いて CI で回す
JMeter(Apache) GUI で組む(JMX ファイル)。Java GUI と CLI HTTP 以外(JDBC・JMS など)も含む大がかりなテスト計画
ab(Apache Bench) コマンドの引数(1 URL) CLI のみ 1つの URL に N 回・同時 C で叩く簡易な計測
wrk コマンドの引数+Lua CLI のみ 少ない CPU で高い RPS を出す。1 URL の限界測定
場面 選ぶもの 理由
API 1本の限界 RPS を手早く知りたい ab・wrk シナリオを書かずに1行で測れる
ログインして一覧→詳細→登録、を何百人で Locust・k6 状態(トークン・Cookie)を持つ流れをコードで書ける
チームの言語が Python Locust 既存のライブラリ・テストコードを流用できる
チームの言語が JavaScript k6 同じ言語でシナリオとしきい値を書ける
GUI で組みたい・DB や MQ も対象 JMeter プロトコルの対応が広い
走らせながら人数を変えて様子を見たい Locust Web UI の EDIT で実行中に変えられる

負荷テストで気をつけること(自分のサーバーだけに向ける)

この節では、Locust を走らせる前に確かめることと、やってはいけないことが分かります。

負荷テストは、見た目は「大量のアクセス」そのものです。向ける先は、自分が管理していて、負荷をかける許可があるサーバーだけです。 他人のサイト・共用のステージング・SaaS の API に向けると、相手のサービスを止めたり、利用規約に反したりします。

  • 1対象は自分が管理するサーバーだけhost を 127.0.0.1 や自分のテスト環境にする。本番に向けるときは関係者の合意と時間帯を決める
  • 2小さく始める(-u 1 → 10 → 100)locustfile の誤りで本番データが増えることがある。手元の create_item も POST で登録している
  • 3外部サービス・メール送信・決済はモックにする対象アプリが外部 API を呼ぶなら、負荷が外部に伝わる
  • 4負荷をかける側の CPU を見る1プロセス≒1コア。CPU の警告が出たら --processes か FastHttpUser
  • 5Web UI を外に公開しない既定は全アドレスで待ち受け。--web-host 127.0.0.1 か --web-login を使う
  • 6結果は CSV と条件(人数・時間・版)をセットで残す--csv と --html、locust.conf を一緒に保存する

負荷の受け手が落ちたときに返るべき 503 や、呼び出し側の再試行・サーキットブレーカーの設計は、負荷テストの結果を受けて見直すところです。既存の 503・サーキットブレーカーの記事で扱っています。

よくある質問

Q. Locust とは何ですか? A. Python でシナリオを書くオープンソースの負荷テストツールです。仮想ユーザーの行動を HttpUser のクラスで書き、指定した人数ぶん同時に動かして、応答時間・RPS・失敗率を集計します。MIT ライセンスで、pip install locust で入ります。

Q. Locust の読み方は? A. 「ローカスト」です。群れで飛ぶバッタの意味で、仮想ユーザーの群れがサーバーに押し寄せる様子からの名前です。

Q. locust 使い方の最短手順は? A. pip install locust → HttpUser を継承したクラスに @task のメソッドを書いた locustfile.py を作る → locust --headless -u 10 -r 2 -t 30s -H http://対象。画面で見たいときは locust だけで起動して http://localhost:8089 を開きます。

Q. Number of users と Ramp up(spawn rate)の違いは? A. Number of users は「最大で何人にするか」、Ramp up は「毎秒何人ずつ増やすか」です。100 人・毎秒 10 人なら 10 秒かけて 100 人になります。いきなり最大にしたいときは両方を同じ値にします。

Q. RPS が上がりません。サーバーの限界ですか? A. 2つの可能性があります。対象サーバーが飽和している(応答時間が伸びている)か、Locust 側の CPU が足りない(ログに CPU の警告が出る)かです。FastHttpUser に替える・--processes で増やす・別の機械から負荷をかける、で同じ天井なら対象の限界です。

Q. 平均応答時間が速いのに利用者が遅いと言います。 A. 平均は多数の速い応答に隠されます。p95・p99 を見ると「20人に1人」「100人に1人」が体験している遅さが分かります。手元でも平均 24ms に対して p95 は 200ms でした。

Q. URL にID が入るので統計の行が増えすぎます。 A. self.client.get(f"/items/{id}", name="/items/[id]") のように name= を付けると1行にまとまります。

Q. ログインが要る API はどう書きますか? A. on_start でログインしてトークンを受け取り、self.client.headers["Authorization"] に入れます。HttpUser の client は requests.Session なので、Cookie も自動で保たれます。

Q. 200 が返っても失敗にしたいときは? A. with self.client.get(url, catch_response=True) as res: の中で条件を見て res.failure("理由") を呼びます。遅すぎる・本文が想定と違う、を失敗に数えられます。

Q. CI で合否を判定できますか? A. できます。既定では失敗が1件でもあれば終了コード 1 です。失敗率や p95 で判定したいときは quitting イベントで environment.process_exit_code を設定します。

Q. Windows でも動きますか? A. Locust 本体は動きます。ただし --processes は fork() を使うため Windows では使えず、master と worker を別々に起動します(公式ドキュメント)。

Q. k6 と Locust はどちらがよいですか? A. シナリオを Python で書きたいなら Locust、JavaScript なら k6 です。k6 はしきい値を宣言的に書いて CI で回す用途に強く、Locust は Web UI で実行中に人数を変えられる点と、Python のライブラリをそのまま使える点が強みです。

まとめ

  • Locust は、仮想ユーザーの行動を Python のクラス(HttpUser・@task・wait_time・on_start)で書く負荷テストツール。pip install locust で入り、headless と Web UI の両方で動く。
  • 結果は「失敗率 → p95/p99 → RPS → 平均」の順に見る。手元では Aggregated の平均 24ms に対して p95 が 200ms で、エンドポイント別に見ないと意味が取れなかった。
  • ユーザー数を 50→100 にしても RPS は 1,026→1,084 とほぼ変わらず、p50 が 31→59ms に伸びた。これが「限界に達した」形。
  • 失敗の原文は ConnectionRefusedError(対象が起動していない)・ReadTimeout(timeout 秒以内に返らない)・HTTPError 404/500(URL 違い・サーバー内部エラー)。Error report・FAILURES タブ・_failures.csv に同じ文言で出る。
  • RPS が伸びないときは Locust 側の CPU も疑う。FastHttpUser で 1.7 倍、--processes 2 で 1.3 倍になった。負荷は自分が管理するサーバーにだけ向ける。

参考・出典

いずれも 2026年10月29日に確認しました。

  • Locust「Your first test」(Quickstart):https://docs.locust.io/en/stable/quickstart.html
  • Locust「Writing a locustfile」:https://docs.locust.io/en/stable/writing-a-locustfile.html
  • Locust「Running without the web UI」:https://docs.locust.io/en/stable/running-without-web-ui.html
  • Locust「Distributed load generation」:https://docs.locust.io/en/stable/running-distributed.html
  • Locust「Increase performance with a faster HTTP client」:https://docs.locust.io/en/stable/increase-performance.html
  • Locust「Configuration」:https://docs.locust.io/en/stable/configuration.html
  • Locust「What is Locust?」:https://docs.locust.io/en/stable/what-is-locust.html
  • PyPI「locust」:https://pypi.org/project/locust/
  • Grafana k6 ドキュメント:https://grafana.com/docs/k6/latest/
  • Apache JMeter:https://jmeter.apache.org/

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Locustとは・使い方|Pythonで書く負荷テストを実行結果と失敗の原文で解説」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ
参加受付中

AI駆動開発、 無料セミナー