Gunicorn の仕組みとワーカーモデル
Gunicorn は「Green Unicorn」の略で、Ruby の Unicorn サーバーの設計思想を Python に移植したものである。プリフォーク型のワーカーモデルを採用し、シンプルながらも堅牢な本番運用を実現している。その内部構造を理解することで、適切な設定とトラブルシューティングが可能になる。
プリフォーク型アーキテクチャ
Gunicorn は、起動時にマスタープロセスが複数のワーカープロセスを「フォーク」して生成する。このアーキテクチャをプリフォーク(pre-fork)と呼ぶ。
# 4 つのワーカーで起動
gunicorn -w 4 app:applicationこの場合、1 つのマスタープロセスと 4 つのワーカープロセスが生成される。プロセス構成を ps コマンドで確認できる。
$ ps aux | grep gunicorn
user 1234 gunicorn: master [app:application]
user 1235 gunicorn: worker [app:application]
user 1236 gunicorn: worker [app:application]
user 1237 gunicorn: worker [app:application]
user 1238 gunicorn: worker [app:application]マスタープロセスはリクエストを直接処理せず、ワーカーの管理に専念する。ワーカープロセスが実際のリクエスト処理を担当する。
ワーカーの起動・監視・再起動を管理。シグナルを受け取り、graceful restart やシャットダウンを制御する。リクエスト処理は行わない。
WSGI アプリケーションを実行し、リクエストを処理する。各ワーカーは独立したプロセスとして動作し、互いに影響を与えない。
ワーカープロセスの動作
各ワーカーは、ソケットを共有してリクエストを受け付ける。OS のカーネルが、複数のワーカー間でリクエストを分配する。
# 概念的な動作イメージ(実際の実装とは異なる)
import socket
import os
def worker_loop(app, sock):
while True:
# ソケットから接続を受け付ける
client, addr = sock.accept()
# リクエストを処理
handle_request(app, client)
client.close()ワーカーがクラッシュした場合、マスタープロセスが検知して新しいワーカーを生成する。この仕組みにより、アプリケーションのバグでワーカーが停止しても、サービス全体は継続できる。
ワーカータイプ
Gunicorn は複数のワーカータイプをサポートしており、アプリケーションの特性に応じて選択できる。
sync ワーカー(デフォルト)
同期的にリクエストを処理する最もシンプルなワーカーだ。1 つのリクエストが完了するまで次のリクエストを受け付けない。
gunicorn -k sync app:applicationCPU バウンドな処理や、シンプルなアプリケーションに適している。外部 API 呼び出しやデータベースアクセスが多い場合は、I/O 待ちの間にワーカーがブロックされてしまう。
gthread ワーカー
各ワーカープロセス内で複数のスレッドを動かすワーカーだ。I/O 待ちの間に他のスレッドがリクエストを処理できる。
# 4 プロセス × 4 スレッド = 16 並行処理
gunicorn -k gthread -w 4 --threads 4 app:applicationGIL(Global Interpreter Lock)の制約はあるが、I/O バウンドなアプリケーションでは効果的だ。sync ワーカーよりも少ないプロセス数で同等のスループットを実現できる。
gevent ワーカー
グリーンスレッド(軽量スレッド)を使った非同期ワーカーだ。コルーチンベースの並行処理により、大量の同時接続を効率的に処理できる。
pip install gevent
gunicorn -k gevent -w 4 app:applicationgevent はモンキーパッチングにより、標準ライブラリの I/O 操作を非同期化する。これにより、既存のコードを変更せずに非同期の恩恵を受けられる。ただし、C 拡張を使うライブラリとの互換性には注意が必要だ。
eventlet ワーカー
gevent と同様にグリーンスレッドを使うが、実装が異なる。
pip install eventlet
gunicorn -k eventlet -w 4 app:applicationgevent と eventlet はどちらも似た用途に使えるが、ライブラリの互換性やコミュニティのサポート状況を考慮して選択する。
ワーカー数の決定
適切なワーカー数は、サーバーの CPU コア数とアプリケーションの特性に依存する。
ワーカー数 = CPU コア数 × 1〜2 が目安。コア数を超えるワーカーを動かしても、コンテキストスイッチのオーバーヘッドが増えるだけ。
ワーカー数 = CPU コア数 × 2〜4 が目安。I/O 待ちの間に他のワーカーが処理できるため、コア数より多くても効果がある。
Gunicorn の公式ドキュメントでは (2 × CPU コア数) + 1 が推奨されている。4 コアのサーバーであれば 9 ワーカーだ。
# CPU コア数を取得して自動設定
gunicorn -w $(( 2 * $(nproc) + 1 )) app:applicationただし、これはあくまで出発点であり、実際の負荷テストを行って調整すべきだ。
タイムアウト設定
ワーカーが一定時間応答しない場合、マスタープロセスがワーカーを強制終了して再起動する。これにより、処理がスタックしたワーカーがリソースを占有し続けることを防ぐ。
# タイムアウトを 60 秒に設定
gunicorn --timeout 60 app:applicationデフォルトは 30 秒だ。長時間かかる処理(レポート生成など)がある場合は、タイムアウトを延長するか、非同期タスクキュー(Celery など)に処理を委譲することを検討する。
sync ワーカー以外では --graceful-timeout も重要だ。graceful restart 時に、処理中のリクエストが完了するまで待つ時間を指定する。
gunicorn --timeout 60 --graceful-timeout 30 app:applicationシグナルによる制御
マスタープロセスは Unix シグナルを受け取り、様々な操作を実行する。
# Graceful restart(新しいコードを読み込む)
kill -HUP $(cat gunicorn.pid)
# ワーカー数を増やす
kill -TTIN $(cat gunicorn.pid)
# ワーカー数を減らす
kill -TTOU $(cat gunicorn.pid)
# Graceful shutdown
kill -TERM $(cat gunicorn.pid)
# 強制終了
kill -QUIT $(cat gunicorn.pid)HUP シグナルによる graceful restart は、ダウンタイムなしでコードを更新できる。マスタープロセスは新しいワーカーを起動し、古いワーカーを徐々に停止する。
設定ファイル
コマンドラインオプションが多くなる場合は、Python ファイルで設定を管理できる。
# gunicorn.conf.py
import multiprocessing
# バインドアドレス
bind = '0.0.0.0:8000'
# ワーカー数
workers = multiprocessing.cpu_count() * 2 + 1
# ワーカータイプ
worker_class = 'gthread'
# スレッド数(gthread の場合)
threads = 4
# タイムアウト
timeout = 60
graceful_timeout = 30
# プロセス名
proc_name = 'myapp'
# ログ
accesslog = '/var/log/gunicorn/access.log'
errorlog = '/var/log/gunicorn/error.log'
loglevel = 'info'
# PID ファイル
pidfile = '/var/run/gunicorn/gunicorn.pid'
# デーモン化
daemon = False# 設定ファイルを指定して起動
gunicorn -c gunicorn.conf.py app:applicationフック関数
設定ファイルでフック関数を定義すると、ライフサイクルイベントに処理を追加できる。
# gunicorn.conf.py
def on_starting(server):
"""マスタープロセス起動時"""
print("Master process starting")
def on_reload(server):
"""設定リロード時"""
print("Configuration reloaded")
def pre_fork(server, worker):
"""ワーカーフォーク前"""
print(f"Worker {worker.pid} forking")
def post_fork(server, worker):
"""ワーカーフォーク後"""
print(f"Worker {worker.pid} forked")
def worker_exit(server, worker):
"""ワーカー終了時"""
print(f"Worker {worker.pid} exiting")これらのフックは、データベース接続の初期化やクリーンアップ処理に活用できる。
Gunicorn のプリフォークモデルとワーカー管理の仕組みを理解すれば、本番環境での安定運用と、パフォーマンスチューニングの基盤が整う。まずはデフォルト設定で始め、負荷テストの結果を見ながら最適な設定を探っていくのがよい。



