19
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ZOZOテクノロジーズ #4Advent Calendar 2019

Day 22

FastAPIの負荷実験環境を作ってみる

Last updated at Posted at 2019-12-21

自己紹介

こんにちは,ZOZOテクノロジーズの内定者さっとです.
APIフレームワークの中ではFastAPIは結構好きで、個人で使うならほぼほぼこれを使っています。
FastAPIについては、以前に何個か記事を書います!
興味ある方は最後にリンクを貼っているので、そちらも見ていただければ思います!

※本記事はZOZOテクノロジーズ#4の22日目です.

概要(3行)

  • Dockerを使ってFastAPIとMySQLの環境を構築
  • 負荷実験ツールとしてLocustをローカルにインストール
  • Locustを使ってコンテナへ向けて負荷実験

FastAPIとは?

Python3.6以上で動作する高速なAPIフレームワークです。
https://fastapi.tiangolo.com/
2019年初頭頃、少し話題になったと思います。

特徴としては、Node.jsGoと同等のパフォーマンス性能でかつFlaskライクな書き方で簡単にAPIを生やすことができます。ハッカソンなど時間が限られた開発で有効なフレームワークだと思います。

また、Swaggerが自動で生成されるため、動作チェックやドキュメント作成の時間が大幅に短縮されます。

Locustとは?

Pythonで書かれた、負荷テストツールです。
https://locust.io/

負荷実験のシナリオはPythonで記述し、簡単に実装することができます。また、実行結果はWebで確認することができ、攻撃対象へアクセスするユーザ数の設定や実験の停止などテストツールを制御することができます。

事前準備

  • Docker及びDocker Composeがインストール済み
  • Python3.6以上がインストール済み
  • 以下のファイル構成を用意(GitHubに完成版があります)
.
├── api
│   ├── db.py
│   ├── main.py
│   └── model.py
├── docker
│   ├── mysql
│   │   ├── Dockerfile
│   │   ├── conf.d
│   │   │   └── my.cnf
│   │   └── initdb.d
│   │       └── schema.sql
│   └── uvicorn
│       ├── Dockerfile
│       └── requirements.txt
├── docker-compose.yml
└── locustfile.py

APIサーバとMySQLの用意

docker-composeを使って必要なAPIサーバとDBを構築します。

version: '3'
services:
  # MySQL
  db:
    container_name: "db"
    # path配下のDockerfile読み込み
    build: ./docker/mysql
    # コンテナが落ちたら再起動する
    restart: always
    tty: true
    environment:
      MYSQL_DATABASE: sample_db
      MYSQL_USER: user
      MYSQL_PASSWORD: password # ユーザのパスワード
      MYSQL_ROOT_PASSWORD: password # ルートパスワード
    ports:
      - "3306:3306"
    volumes:
      - ./docker/mysql/initdb.d:/docker-entrypoint-initdb.d # 定義通りテーブルを作成
      - ./docker/mysql/conf.d:/etc/mysql/conf.d # MySQLの基本設定(文字化け対策)
    networks:
      - local-net

  # FastAPI
  api:
    # db起動後に立ち上げる
    links:
      - db
    container_name: "api"
    build: ./docker/uvicorn
    restart: always
    tty: true
    ports:
      - 8000:8000
    volumes:
      - ./api:/usr/src/api
    networks:
      - local-net

# コンテナ間で通信を行うためのネットワークブリッジ
networks:
  local-net:
    driver: bridge

詳しい内容については、以下の記事で書いているので参考にしてください。
FastAPIをMySQLと接続してDockerで管理してみる

Locustのインストール

以下のコマンドで、ローカルにLocustをインストールします。

$ python -m pip install locustio

負荷を受けるAPIサーバのメインコード

  • ユーザの情報を取得や登録する簡単なAPIを用意しました。
  • ORMであるSQLAlchemyでMySQLを操作しています。
  • 各エンドポイントの定義(def)の前にasyncとつけて非同期化しています。
./api/main.py
# -*- coding: utf-8 -*-
from fastapi import FastAPI
from db import session  # DBと接続するためのセッション
from model import UserTable  # Userテーブルのモデル


# fastapiでエンドポイントを定義するために必要
app = FastAPI()

# ユーザ情報を返す GET
@app.get("/users/{user_id}")
async def read_user(user_id: int):
    # DBからuser_idを元にユーザを検索
    user = session.query(UserTable).\
        filter(UserTable.id == user_id).first()
    # ユーザが見つからなかった場合
    if (user is None):
        return {"code": 404, "message": "User Not Found"}
    return user

# ユーザ情報を登録 POST
@app.post("/users")
# クエリでnameとageを受け取る
async def create_user(name: str, age: int):
    user = UserTable()
    user.name = name
    user.age = age
    session.add(user)
    session.commit()

負荷実験シナリオ

  • UserTaskSetクラスに実験シナリオを定義していきます。
  • on_startメソッドに初期設定を定義します。
  • taskに書かれた数字は重みで、@task(2)@task(1)の2倍実行されることを表しています。
./locustfile.py
from locust import HttpLocust, TaskSet, task, between
import random
import string


class UserTaskSet(TaskSet):
    def on_start(self):
        self.client.headers = {'Content-Type': 'application/json;'}

    @task(1)
    def fetch_user(self):
        self.client.get("/users/{}".format(random.randint(0, 100)))

    @task(2)
    def create_user(self):
        # ランダムに適当に名前を生成
        name = ''.join(random.choices(string.ascii_letters, k=10))
        age = random.randint(0, 80)
        self.client.post("/users?name={}&age={}".format(name, age))


class UserLocust(HttpLocust):
    task_set = UserTaskSet

    wait_time = between(0.100, 1.500)

実行

  • コンテナを立ち上げます。
$ docker-compose up -d --build
  • Locustを起動します。

APIサーバはlocalhost:8000で待ち受けているので、これに向けて負荷をかけるように指定してあげます。

$ locust -f locustfile.py --host=http://localhost:8000

LocustをWebで監視

  • Locustを起動すると以下のログが通知されます。
[2019-12-02 02:13:58,632] hoge/INFO/locust.main: Starting web monitor at *:8089
[2019-12-02 02:13:58,633] hoge/INFO/locust.main: Starting Locust 0.13.2
  • ログでは、*:8089でリッスンしているみたいなので、http://localhost:8089 へアクセスします。

Screenshot from 2019-12-02 02-17-49.png
初回起動時では、Number of users to simulateでどのぐらいのユーザ数でアクセスするかと
Hatch rateで毎秒何人ユーザを増やすかを設定できます。

  • テストが実行されると、Statisticsページではログが流れます。

ログでは、リクエスト数やレスポンスタイム、失敗した数などが見れます。

Screenshot from 2019-12-02 02-25-58.png

  • Chartsページでは、一行ごとのリクエスト数やレスポンスタイム、現在のユーザ数がチャートで表示されます。

Screenshot from 2019-12-02 02-29-53.png

  • 最後に、上部のSTOPで簡単に負荷を停止することができます。

おわりに

今回は、Locustを使った負荷実験環境の構築を行いました。
今後は、FastAPIって本当に早いの?を検証するために、この環境を使い様々なAPIフレームワークと比較したいと思います。

今回使ったコードは、GitHubにあげているので、興味ある方はぜひ触ってみてくださいね!
https://github.com/sattosan/stress_fastapi

その他FastAPIの記事

19
8
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?