##uWSGIのmax-requets-delta効いてない説
正直、なんかの間違いかと思っていた。
あなたの使ってるuWSGI、本当にmax-requests-delta効いてますか?という記事があったのは知っていたし読んでいた。
でも、あんまり記事にいいねついてないし、公式のドキュメントにもmax-requests-delta載ってるし、多分そんなことないんだろうなーと思っていた(失礼)。
Apacheにかわるwebサーバ: uWSGIパフォーマンスチューニング
こっちのいいね100以上ついてる記事ではmax-requests-deltaが紹介されているし、delta効いているでしょう?と思っていた。
しかし、負荷テストの際、max-requests-deltaが効いているとは思えない事象が発生した。
max-requests: 30000
max-requests-delta: 3000 #確か3000だったはず
worker: 50
こんな設定で3h200rpsを流す負荷テストを実施した時、2時間を超えたあたりで毎回エラーが起きていた。
150万リクエストあたりでちょうど
The work of process 18 is done. Seeya!"
...
"worker 6 killed successfully (pid: 16)"
...
"Respawned uWSGI worker 6 (new pid: 263)"
...
というログが出ていて、そのすぐ後に
uWSGI listen queue of socket \":9090\" (fd: 3) full !!! (101/100)
というエラーが出ていて、その時間辺りで30秒以上ALBのヘルスチェックに応答できなくなりALBによってTaskが落とされていた。(Flask+Nginx+Fargate構成で開発しています。)
1,500,000 / 200 / 60 = 125
200rps流すと、125分超えた辺りでuWSGIのプロセスが一斉に再起動し、その間にもリクエストは200rpsで送られるから、その間にqueueにリクエストが溜まり過ぎて
uWSGI listen queue of socket \":9090\" (fd: 3) full !!! (101/100)
というエラーメッセージが出たと判断。max-requests-deltaが効いていないとすると全て繋がる。
ドキュメントにもしっかり書いてあるのに、そんなことってあるのかな。。と思いながらstrictモードの存在を知る。
This option tells uWSGI to fail to start if any parameter in the configuration file isn’t explicitly understood by uWSGI.
strict: true
max-requests: 3000
max-requests-delta: 300
こんな感じに設定して、docker-compose down && docker-compose build && docker-compose up -d
してみた。
uWSGIのログを見ようとコンテナ内に入ろうとする。
❯ docker exec -it uwsgi-app bash
Error response from daemon: Container b951a8b21f946ad980aedc9792622b6d3c08a91e6aa095fa8fd5ebd3fbe56b8e is not running
?
起動してないのでコンテナのログを見る。
❯ docker logs -f uwsgi-app
[uWSGI] getting YAML configuration from /etc/uwsgi/uwsgi.yml
[strict-mode] unknown config directive: max-requests-delta
マジだった。いいねの数に目が眩んでいた。。。ドキュメントにも書いてあったのに。。。
uWSGIのIssue確認
ここでも2.1からサポートされているとあります。
https://github.com/unbit/uwsgi/issues/1488
https://github.com/unbit/uwsgi/issues/2037
↑のIssueから
質問
When will the new release be tagged? I'm waiting for the max-requests-delta option, and I'm reading things about the version 2.1 since 2017. Any news here?
答え
Hi, unfortunately i do not think there will be a 'supported 2.1' any time soon. Currently the objective is to leave the 2.1 as the 'edge' branch and backport requested features to 2.0. This is completely a fault of mine, not having a proper test suite since the beginning caused a stall in 'commercially supportable' releases after 10 years. @xrmx is doing a great work in backporting. So back to gevent, has someone found the technical issue ? (sorry, maybe there is already a patch but i did not find it)
max-requests-deltaを待望する声は多そうですが、まだ公式にサポートされるのは先みたいです。
##ではどうする?
uWSGIのプロセスをちゃんと再起動してやらないと、メモリリークが発生したり、後自分が観測した限りでは負荷がかかっていない時でもuWSGIのプロセスがCPUを一定値使用してしまい、CPU使用率を基準にしたAuto Scalingがちょっとやりづらくなるという問題もあったので、uWSGIプロセスの再起動はちゃんとしたいところ。
自分の中でまだ明確に答えは出ていないですが、uWSGIプロセスの稼働率に応じて動的にuWSGIのプロセス数を調節する設定で対応可能なのではないかと思っています。
コンテナ内のプロセス数を動的に増減するよりも、そこは固定しておいて、ECSのオートスケーリングによってコンテナの数を変える方が良いという意見もあり、確かにそうだなと思う一方で、でもプロセスの一斉再起動問題もあるしな。。といった感じでまだ明確な答えは出ていないです。
cheaper-algo: busyness
processes: 100 #最大プロセス数
cheaper: 10 #最低プロセス数
cheaper-initial: 50 #uWSGI起動時のプロセスの数
cheaper-overload: 3 #プロセスの数を調節するために、この時間ごとにプロセスの稼働率が計算される。単位は秒。
cheaper-step: 30 #一度に増やすプロセスの数
cheaper-busyness-multiplier: 30 #プロセスを減らす前に何秒待つか指定
cheaper-busyness-min: 20 #稼働率がこの値以下になった場合、プロセスを減らす
cheaper-busyness-max: 70 #稼働率がこの値以上になった場合、プロセスを増やす
cheaper-busyness-backlog-alert: 16 #待ち状態のリクエストがこの値以上になった場合、緊急のプロセスを増やす
cheaper-busyness-backlog-step: 8 #作成する緊急のプロセスの数
cheaper-busyness-penalty: 2
こうして動的にプロセスを増減してやれば、ずっと同じプロセスがメモリ上に存在することをある程度ß防げるので、一応問題の解決にはなるのではないかと現時点では考えています。
(これと合わせればmax-worker-lifetime
も使えそうだけど、それでも全てのプロセスが一斉に再起動する可能性もあるかなと、心配で踏み切れない。)
uWSGIの情報は公式のドキュメントの説明があまり充実していなかったりするせいでしっかりとした情報が見つかりづらいですが、その中でもBloombergの記事はしっかりと書いてあって参考になったのでもしよかったら読んでみてください。
Configuring uWSGI for Production Deployment
もうちょっと負荷テストして、答えを出せたら追記します。
##解決策(追記)
解決策として、uWSGIの設定ファイルを使用せずに、引数で全ての設定をするようにすることで解決できました。
CMD ["uwsgi", "--yaml", "uwsgi.yml", \
"--max-worker-lifetime", "`awk -v min=1800 -v max=5400 'BEGIN{srand(); print int(min+rand()*(max-min+1))}'`" ]
設定ファイルに書かず、コマンドライン引数として--max-worker-lifetime
に1800から5400までの数をランダムに渡すことで、
プロセスが再起動する間隔がそれぞれのコンテナごとにコンテナ起動の度に30分から90分の範囲内で無作為に決まることになり、全てのFargate TaskのコンテナのuWSGIプロセスが一斉に再起動する問題の発生を防ぐことができました。
これにより、ダウンタイムが発生することもなくなると思われます。