3
3

More than 5 years have passed since last update.

dockerでuwsgiのthunder lock設定を検証

Last updated at Posted at 2017-12-14

アプリサーバーを構築する時、少しニッチな問題があります。それがThundering Herd問題。ここでは、docker環境で、uwsgiのthunder-lock設定を有効にしたらこの問題をどのくらい改善できるかを検証して見たいです。

Thundering Herd問題とは

Thundering Herd問題、またの名はZeeg問題はマルチプロセス&マルチスレッドでリクエストを処理する時起こる問題です。もし一つのリクエストが届いて、用意した全部のスレッドが起こされたらリソースが無駄に消費されます。なぜなら結局一リクエストをサーブするのは普通1スレッドです、他のスレッドはただ起きて、タスクがないことを確認して、また寝てしまいます。現在のカーネルでは同じプロセス内のスレッドにロックを待たせて、同プロセス内の全スレッドを起こされる事態を防げました。しかし、プロセス数が多い時プロセス間ロックをかけなかったら、また競合と無駄が発生します。

検証してみます

自社のアプリサーバーがnginx+uwsgi+Djangoで構築されていますので、nginx+uwsgiの構成で問題検証を行いたいです。また、ローカル環境普段色々いじってますので、今回はdockerを使って環境構築します。
検証プラン:
A:環境構築(uwsgiのthunder-lock設定入れない)+k6で負荷をかける+uwsgitopで性能測定
B:環境構築(uwsgiのthunder-lock設定入れる)+k6で負荷をかける+uwsgitopで性能測定

環境構築

まずはdocker-composeを使ってdocker環境を構築する、ワーキングディレクトリに以下のようなymlファイルを用意します

docker-compose.yml
version: '2'

services:
  web:
    image: tiangolo/uwsgi-nginx:python2.7
    ports: 
      - "30301:80"
    networks:
      - "mynet"
    volumes:
      - ./workingD:/var/workingD
networks: 
  mynet:

次にdocker-compose upコマンドdockerのイメージなどをプルして設定したnginx+uwsgiが入ってたwebという名のサービスをスタートします。
サービスが起動されたあと(uwsgiのログがプリントされるのが確認できます)、下のコマンドでdockerに入ります

docker exec -it dockusginginx_web_1 /bin/bash

これで当該ディレクトリに行ってnginxとuwsgiの設定を変更できます。

nginxの環境設定(/etc/nginx/conf.d/nginx.conf)

ここはデフォルトの設定でおっけーです。

root@1c30155afc5e:/etc/nginx/conf.d# cat nginx.conf
server {
    location / {
        include uwsgi_params;
        uwsgi_pass unix:///tmp/uwsgi.sock;
    }

uwsgiの環境設定(/etc/uwsgi/uwsgi.ini)

マルチスレッドを4と設定した上、プロセス数を16にしました。この方(プロセス数多め)がthundering herd問題を確認しやすいと思いました。

[uwsgi]
socket = /tmp/uwsgi.sock
chown-socket = nginx:nginx
chmod-socket = 664
processes = 16
threads = 4

uwsgiのappの設定

ここではuwsgitopが性能のログを読めるようにするためstatsの吐き出し先を設定しました。
/var/workingD/stats.sockはローカル環境からマウントされたディレクトリです(docker内のファイルを指定しても書き込めないためです)。検証プランBへ変更する時はここにthunder-lock = trueを入れれば変更完了です。

[uwsgi]
wsgi-file=/app/main.py
stats = /var/workingD/stats.sock
memory-report = true

アプリのロジック

uwsgiで走るアプリの内容はほぼハローワールドを返すだけですが、それだと軽すぎなのでCPUバウンドなジョブを任せました。

def application(env, start_response):
    workload()
    start_response('200 OK', [('Content-Type', 'text/html')])
    return ["NEWWWWWWW Hello World from a default Nginx uWSGI Python 2.7 app in a\
            Docker container (default)"]

def workload():
    """
    provide CPU pound workload
    """
    counter = 1000000
    while counter > 0:
        counter -= 1

uwsgitopについて

uwsgitopはuwsgiのパーフォマンスを観測するためのツールです。pythonのパッケージなので

pip install uwsgitop

をdocker内で入力すればインストールできます。

k6について

今回負荷をかけるのにk6を使いました、python発のlocustでもいいですが、新しいものには試したくなったのでk6に〜
k6を使うにあたりまずスクリプトを用意します

loadtest.js
import http from "k6/http";

export default function(){
    http.get("http://localhost:30301/");
};

ここではk6のエントリーとしてdefault関数を定義します。やることはdockerが開放&リッスンしているポートにgetリクエストを送るだけです。
ロードテストを開始したい時、loadtest.jsがあるディレクトリで下記コマンドを入力すればおっけーです

k6 run --vus 64 --duration 0 loadtest.js

ここでは64virtual user(疑似ユーザー)に同時テストスクリプトを実行させます。durationを0に指定すれば中止させるまでテストが続行されます。

検証結果

検証プランA:
約90秒間k6を走らせて計測結果を取ってみると:

uwsgitop

planA.png

k6

planAk6.png

検証プランB:
同じく約90秒間k6を走れせた計測結果:

uwsgitop

planB.png

k6

planBk6.png

結果分析

attention:環境ローカルが4コアなので、dockerで走ってるuwsgiとk6とも実運用環境とかなり違いがあります。
uwsgitop:プランAのRPS(Request Per Second)がプロセス間にばらつきが大きいです、RPSが0なプロセスが二つあります。(とはいえ一瞬の結果をスクショした結果だけです)
他に大きな違いは無いようです。
k6のメトリックス:
http_req_blocked:tcpコネクションが初期化されるまでの時間
http_req_connecting:リモートホストとコネクションを立てる時間
以上二項目はプランBの方が平均とマックスも大きいです。
http_req_duration:ブロックなどを除いたリクエストの処理時間
http_req_waiting:リクエストの初めてレスポンスを返すまでの時間("time to first byte")
の二項目では平均こそ大きな差は無いが、プランBの方がマックスが小さいです。

複数のメトッリクスを比較してみるとthunder-lockを入れたプランBが入れないプランAに全面圧勝できたわけではありません。しかし、レスポンスタイムの外れ値が小さい、uwsgiのプロセス間処理するリクエスト数のばらつきが小さいなどメリットが見れます。

3
3
0

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
3
3